forked from mirrors/forgejo
Compare commits
1 commit
forgejo
...
renovate/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee59980b6c |
1187 changed files with 16680 additions and 50603 deletions
|
|
@ -19,6 +19,7 @@ forgejo.org/models/auth
|
|||
forgejo.org/models/db
|
||||
TruncateBeans
|
||||
TruncateBeansCascade
|
||||
InTransaction
|
||||
DumpTables
|
||||
GetTableNames
|
||||
extendBeansForCascade
|
||||
|
|
@ -58,6 +59,7 @@ forgejo.org/models/user
|
|||
IsErrUserSettingIsNotExist
|
||||
GetUserAllSettings
|
||||
DeleteUserSetting
|
||||
GetFederatedUser
|
||||
|
||||
forgejo.org/modules/activitypub
|
||||
NewContext
|
||||
|
|
@ -87,6 +89,7 @@ forgejo.org/modules/eventsource
|
|||
Event.String
|
||||
|
||||
forgejo.org/modules/forgefed
|
||||
NewForgeFollow
|
||||
NewForgeUndoLike
|
||||
ForgeUndoLike.UnmarshalJSON
|
||||
ForgeUndoLike.Validate
|
||||
|
|
@ -132,9 +135,6 @@ forgejo.org/modules/json
|
|||
StdJSON.Indent
|
||||
|
||||
forgejo.org/modules/log
|
||||
eventWriterBuffer.Close
|
||||
eventWriterBuffer.Write
|
||||
eventWriterBuffer.GetString
|
||||
NewEventWriterBuffer
|
||||
|
||||
forgejo.org/modules/markup
|
||||
|
|
@ -225,6 +225,9 @@ forgejo.org/routers/web/org
|
|||
forgejo.org/services/context
|
||||
GetPrivateContext
|
||||
|
||||
forgejo.org/services/federation
|
||||
FollowRemoteActor
|
||||
|
||||
forgejo.org/services/notify
|
||||
UnregisterNotifier
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "forgejo-dev",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
||||
"name": "Gitea DevContainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
||||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ labels:
|
|||
|
||||
## Checklist
|
||||
|
||||
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
|
||||
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
|
||||
|
||||
### Tests for Go changes
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"baseBranchPatterns": [
|
||||
"$default",
|
||||
"/^v11\\.\\d+/forgejo$/",
|
||||
"/^v15\\.\\d+/forgejo$/"
|
||||
"/^v14\\.\\d+/forgejo$/"
|
||||
],
|
||||
"postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths", "npmDedupe"],
|
||||
"prConcurrentLimit": 10,
|
||||
|
|
@ -138,13 +138,6 @@
|
|||
],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Run end-to-end tests for some dependencies",
|
||||
"matchPackageNames": [
|
||||
"code.forgejo.org/forgejo/runner/**"
|
||||
],
|
||||
"addLabels": ["run-end-to-end-tests"]
|
||||
},
|
||||
{
|
||||
"description": "Disable indirect updates for stable branches",
|
||||
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
||||
|
|
@ -159,12 +152,6 @@
|
|||
"matchUpdateTypes": ["major"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Disable updates for old stable branches but still allow security updates",
|
||||
"matchBaseBranches": ["v11.0/forgejo", "v14.0/forgejo"],
|
||||
"matchUpdateTypes": ["minor", "patch", "digest"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Require approval for stable branches (must be last rule to override all others)",
|
||||
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.9.1
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.7
|
||||
with:
|
||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||
strategy: ort
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ name: Integration tests for the release process
|
|||
enable-email-notifications: true
|
||||
|
||||
env:
|
||||
FORGEJO_VERSION: 11.0.13 # renovate: datasource=docker depName=data.forgejo.org/forgejo/forgejo
|
||||
FORGEJO_VERSION: 11.0.10 # renovate: datasource=docker depName=data.forgejo.org/forgejo/forgejo
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
||||
- id: forgejo
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.11
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.6
|
||||
with:
|
||||
user: root
|
||||
password: admin1234
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ jobs:
|
|||
|
||||
- name: build container & release
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
@ -183,7 +183,7 @@ jobs:
|
|||
|
||||
- name: build rootless container
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ jobs:
|
|||
TEST_MINIO_ENDPOINT: minio:9000
|
||||
TEST_LDAP: 1
|
||||
TEST_REDIS_SERVER: cacher:6379
|
||||
- uses: https://data.forgejo.org/forgejo/upload-artifact@v5
|
||||
- uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: ${{ forge.workspace }}/coverage/merged
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.5.1
|
||||
with:
|
||||
from-forgejo: ${{ vars.FORGEJO }}
|
||||
to-forgejo: ${{ vars.FORGEJO }}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ on:
|
|||
|
||||
env:
|
||||
RNA_WORKDIR: /srv/rna
|
||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_VERSION: v1.6.0 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ on:
|
|||
- labeled
|
||||
|
||||
env:
|
||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_VERSION: v1.6.0 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
|
@ -75,7 +75,7 @@ jobs:
|
|||
matrix:
|
||||
version: ['10.6', '11.8']
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -87,10 +87,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- name: event info
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
elasticsearch:
|
||||
|
|
@ -88,6 +88,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- name: test release-notes-assistant.sh
|
||||
run: |
|
||||
apt-get -q install -qq -y jq
|
||||
|
|
@ -144,7 +148,7 @@ jobs:
|
|||
RUN_ALL: ${{steps.run-all.all}}
|
||||
- name: Upload test artifacts on failure
|
||||
if: failure()
|
||||
uses: https://data.forgejo.org/forgejo/upload-artifact@v5
|
||||
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts.zip
|
||||
path: tests/e2e/test-artifacts/
|
||||
|
|
@ -154,7 +158,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks, test-unit]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
||||
strategy:
|
||||
|
|
@ -181,6 +185,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-remote-cacher test-check'
|
||||
|
|
@ -194,7 +202,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -210,10 +218,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
@ -225,7 +233,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
minio:
|
||||
|
|
@ -248,10 +256,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
||||
|
|
@ -265,15 +273,15 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||
|
|
@ -293,7 +301,7 @@ jobs:
|
|||
- test-remote-cacher
|
||||
- test-unit
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -107,7 +107,6 @@ cpu.out
|
|||
/.air
|
||||
/.go-licenses
|
||||
/.cur-deadcode-out
|
||||
/.deadcode.diff
|
||||
|
||||
# Files and folders that were previously generated
|
||||
/public/assets/img/webpack
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ linters:
|
|||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- modernize
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
|
|
@ -46,25 +45,6 @@ linters:
|
|||
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
|
||||
- pkg: gopkg.in/yaml.v3
|
||||
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
|
||||
migration-isolation:
|
||||
list-mode: lax
|
||||
files:
|
||||
- "**/models/forgejo_migrations/**"
|
||||
deny:
|
||||
- pkg: "forgejo.org/models"
|
||||
desc: >
|
||||
Migrations must not import application models. Application models will be the most recent schema for
|
||||
Forgejo, while migrations will be operating against the database schema that existed when they were
|
||||
authored.
|
||||
- pkg: "forgejo.org/services"
|
||||
desc: >
|
||||
Migrations must not import application services. Application services will reference application
|
||||
models which will use the most recent schema for Forgejo, while migrations will be operating against the
|
||||
database schema that existed when they were authored.
|
||||
allow:
|
||||
- "forgejo.org/models/db"
|
||||
- "forgejo.org/models/gitea_migrations/base"
|
||||
- "forgejo.org/models/gitea_migrations/test"
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
|
|
@ -224,6 +204,9 @@ linters:
|
|||
- path: models/dbfs/dbfile.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/forgefed/federationhost_repository.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/forgejo_migrations_legacy/v32.go
|
||||
linters:
|
||||
- nilnil
|
||||
|
|
@ -281,6 +264,9 @@ linters:
|
|||
- path: models/repo/repo.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/user/user_repository.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/git/commit.go
|
||||
linters:
|
||||
- nilnil
|
||||
|
|
@ -347,6 +333,21 @@ linters:
|
|||
- path: services/actions/trust.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/auth/basic.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/auth/httpsign.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/auth/oauth2.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/auth/reverseproxy.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/auth/session.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/contexttest/context_tests.go
|
||||
linters:
|
||||
- nilnil
|
||||
|
|
@ -359,6 +360,9 @@ linters:
|
|||
- path: routers/api/packages/conan/auth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/federation/signature_service.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/issue/commit.go
|
||||
linters:
|
||||
- nilnil
|
||||
|
|
|
|||
17
.mockery.yml
17
.mockery.yml
|
|
@ -1,17 +0,0 @@
|
|||
formatter: gofmt
|
||||
template: testify
|
||||
packages:
|
||||
forgejo.org/modules/nosql:
|
||||
config:
|
||||
filename: mocks.go # make mocks public so that external packages can use
|
||||
forgejo.org/services/auth/method:
|
||||
forgejo.org/services/authz:
|
||||
config:
|
||||
filename: authorization_reducer_mock.go # make mocks public so that external packages can use
|
||||
code.forgejo.org/go-chi/cache:
|
||||
interfaces:
|
||||
Cache:
|
||||
config:
|
||||
pkgname: cache
|
||||
dir: modules/cache
|
||||
filename: mocks.go # make mocks public, not `_test.go`, so that external packages can mock caching
|
||||
|
|
@ -1 +1 @@
|
|||
24.15.0
|
||||
24.13.1
|
||||
|
|
@ -7,7 +7,6 @@ branch-from-version: 'v%[1]d.%[2]d/forgejo'
|
|||
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
||||
supported-release-count: 3
|
||||
branch-known:
|
||||
# replace with v15 when v11 becomes EOL
|
||||
- 'v11.0/forgejo'
|
||||
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
||||
render-header: |
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
rules:
|
||||
- id: forgejo-switch-empty-case
|
||||
pattern-either:
|
||||
- pattern: |-
|
||||
switch $_ {
|
||||
case $_:
|
||||
}
|
||||
- patterns:
|
||||
- pattern: |-
|
||||
switch {
|
||||
case $_:
|
||||
}
|
||||
languages:
|
||||
- go
|
||||
severity: ERROR
|
||||
message: >
|
||||
switch has a case block with no content. This is treated as "break" by Go, but developers may confuse it for
|
||||
"fallthrough". To fix this error, disambiguate by using "break" or "fallthrough".
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
rules:
|
||||
- id: forgejo-logic-suspicious-OwnerID-check
|
||||
pattern: |-
|
||||
$X.OwnerID > 0
|
||||
languages:
|
||||
- go
|
||||
severity: ERROR
|
||||
message: >
|
||||
Many resources like comments or runners cannot only be owned by regular users, which have positive IDs, but also
|
||||
by predefined system users like Ghost or Forgejo Actions that have negative IDs. In those cases, ownership checks
|
||||
should only exclude 0: `OwnerID != 0`.
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
)
|
||||
|
||||
func setPortEmptyCaseBad(port string) error {
|
||||
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
|
||||
setting.HTTPPort = port
|
||||
|
||||
// ruleid:forgejo-switch-empty-case
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
case setting.FCGI:
|
||||
case setting.FCGIUnix:
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
}
|
||||
|
||||
// ok:forgejo-switch-empty-case
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
break
|
||||
case setting.FCGI:
|
||||
break
|
||||
case setting.FCGIUnix:
|
||||
break
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import "xorm.io/builder"
|
||||
|
||||
type FindRunJobOptions struct {
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) Bad() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
// ruleid:forgejo-logic-suspicious-OwnerID-check
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) Good() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
// ok:forgejo-logic-suspicious-OwnerID-check
|
||||
if opts.OwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.26-alpine3.23 AS build-env
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.25-alpine3.23 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.26-alpine3.23 AS build-env
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.25-alpine3.23 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||
|
|
|
|||
54
Makefile
54
Makefile
|
|
@ -39,16 +39,16 @@ XGO_VERSION := go-1.21.x
|
|||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses/v2@v2.0.1 # renovate: datasource=go
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.45.0 # renovate: datasource=go
|
||||
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@43.160.6 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v3@v3.7.0 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.42.0 # renovate: datasource=go
|
||||
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.10 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@43.31.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
|
@ -245,7 +245,7 @@ help:
|
|||
@echo " - generate-license update license files"
|
||||
@echo " - generate-gitignore update gitignore files"
|
||||
@echo " - generate-manpage generate manpage"
|
||||
@echo " - generate-mockery generate mockery files"
|
||||
@echo " - generate-gomock generate gomock files"
|
||||
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
||||
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
||||
@echo " - generate-swagger generate the swagger spec from code comments"
|
||||
|
|
@ -486,23 +486,11 @@ RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Pat
|
|||
|
||||
.PHONY: lint-go
|
||||
lint-go:
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) \
|
||||
|| (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
|
||||
$(RUN_DEADCODE) > .cur-deadcode-out
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out >.deadcode.diff || true
|
||||
@if grep -qE '^[+][^+]' .deadcode.diff ; then \
|
||||
cat .deadcode.diff ; \
|
||||
echo "Looks like you added dead code, please evaluate and remove or use it."; \
|
||||
echo "If you are sure the dead code should stay around, please run 'make lint-go-fix',"; \
|
||||
echo "commit the result and explain the reason in the commit message / PR description."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if grep -qE '^[-][^-]' .deadcode.diff ; then \
|
||||
cat .deadcode.diff ; \
|
||||
echo "Looks like you removed dead code. Thank you!"; \
|
||||
echo "Run 'make lint-go-fix' and commit the result to accept."; \
|
||||
fi
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
|
||||
$(GO) run $(ERRORTYPE_PACKAGE) ./...
|
||||
$(RUN_DEADCODE) > .cur-deadcode-out
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out \
|
||||
|| (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: lint-go-fix
|
||||
lint-go-fix:
|
||||
|
|
@ -562,12 +550,12 @@ test: test-frontend test-backend
|
|||
.PHONY: test-backend
|
||||
test-backend: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: test-remote-cacher
|
||||
test-remote-cacher:
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
||||
|
||||
.PHONY: test-frontend
|
||||
test-frontend: node_modules
|
||||
|
|
@ -592,7 +580,7 @@ test-check:
|
|||
.PHONY: test\#%
|
||||
test\#%: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
|
||||
coverage-merge:
|
||||
rm -fr coverage/merged ; mkdir -p coverage/merged
|
||||
|
|
@ -968,13 +956,17 @@ deps-tools:
|
|||
$(GO) install $(XGO_PACKAGE)
|
||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||
$(GO) install $(GOMOCK_PACKAGE)
|
||||
$(GO) install $(ERRORTYPE_PACKAGE)
|
||||
$(GO) install $(MOCKERY_PACKAGE)
|
||||
|
||||
node_modules: package-lock.json
|
||||
npm install --no-save
|
||||
@touch node_modules
|
||||
|
||||
.venv: poetry.lock
|
||||
poetry install
|
||||
@touch .venv
|
||||
|
||||
.PHONY: fomantic
|
||||
fomantic:
|
||||
rm -rf $(FOMANTIC_WORK_DIR)/build
|
||||
|
|
@ -1024,9 +1016,9 @@ generate-license:
|
|||
generate-gitignore:
|
||||
$(GO) run build/generate-gitignores.go
|
||||
|
||||
.PHONY: generate-mockery
|
||||
generate-mockery:
|
||||
$(GO) run $(MOCKERY_PACKAGE)
|
||||
.PHONY: generate-gomock
|
||||
generate-gomock:
|
||||
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
||||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules
|
||||
|
|
|
|||
79
assets/go-licenses.json
generated
79
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -6,12 +6,32 @@ translation_meta.test
|
|||
# this also gets instantiated as a Messenger once
|
||||
repo.migrate.migrating_failed.error
|
||||
|
||||
# models/system/notice.go: func (n *Notice) TrStr() string
|
||||
admin.notices.type_1
|
||||
admin.notices.type_2
|
||||
|
||||
# modules/setting/ui.go
|
||||
themes.names.
|
||||
|
||||
# services/context/context.go
|
||||
relativetime.
|
||||
|
||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
||||
repo.issues.close
|
||||
repo.pulls.close
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr
|
||||
repo.issues.ref_closing_from
|
||||
repo.issues.ref_issue_from
|
||||
repo.issues.ref_pull_from
|
||||
repo.issues.ref_reopening_from
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type)
|
||||
projects.
|
||||
projects.type-1.display_name
|
||||
projects.type-2.display_name
|
||||
projects.type-3.display_name
|
||||
|
||||
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
||||
# tests/integration/repo_archive_text_test.go
|
||||
repo.settings.
|
||||
|
|
|
|||
|
|
@ -37,172 +37,144 @@ func HandleGoFile(handler llu.Handler, fname string, src any) error {
|
|||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
return HandleGoNode(handler, fset, fname, n)
|
||||
})
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleGoNode(handler llu.Handler, fset *token.FileSet, fname string, n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||
} else if strings.Contains(fname, "models/asymkey/") {
|
||||
lluAsymKey.HandleCompositeErrorReason(handler, fset, n2)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKeyWeak"); matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey"); matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
handler.HandleGoTrArgument(fset, res, *matchInsPrefix)
|
||||
}
|
||||
return false
|
||||
} else if _, ok := n.(*ast.FuncDecl); ok {
|
||||
ast.Inspect(n, func(n2 ast.Node) bool {
|
||||
return HandleGoNode(handler, fset, fname, n2)
|
||||
})
|
||||
// don't search inside nested functions for return stmts
|
||||
return false
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||
} else if strings.Contains(fname, "models/asymkey/") {
|
||||
lluAsymKey.HandleCompositeErrorReason(handler, fset, n2)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
||||
if matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||
}
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
switch n2.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||
// entries can have tags.
|
||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||
// Problem: we don't know which structs are forms, actually.
|
||||
|
||||
for _, spec := range n2.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
structNode, ok := tspec.Type.(*ast.StructType)
|
||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||
(tspec.Doc != nil &&
|
||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||
return c.Text == "// swagger:model"
|
||||
}))) {
|
||||
continue
|
||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||
}
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
switch n2.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, field := range structNode.Fields.List {
|
||||
if field.Names == nil {
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||
// entries can have tags.
|
||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||
// Problem: we don't know which structs are forms, actually.
|
||||
|
||||
for _, spec := range n2.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
structNode, ok := tspec.Type.(*ast.StructType)
|
||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||
(tspec.Doc != nil &&
|
||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||
return c.Text == "// swagger:model"
|
||||
}))) {
|
||||
continue
|
||||
}
|
||||
if len(field.Names) != 1 {
|
||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||
continue
|
||||
}
|
||||
msgidPos := field.Names[0].NamePos
|
||||
msgid := "form." + field.Names[0].Name
|
||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||
for _, field := range structNode.Fields.List {
|
||||
if field.Names == nil {
|
||||
continue
|
||||
}
|
||||
tag := reflect.StructTag(rawTag)
|
||||
if tag.Get("form") == "-" {
|
||||
if len(field.Names) != 1 {
|
||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||
continue
|
||||
}
|
||||
tmp := tag.Get("locale")
|
||||
if len(tmp) != 0 {
|
||||
msgidPos = field.Tag.ValuePos
|
||||
msgid = tmp
|
||||
msgidPos := field.Names[0].NamePos
|
||||
msgid := "form." + field.Names[0].Name
|
||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||
continue
|
||||
}
|
||||
tag := reflect.StructTag(rawTag)
|
||||
if tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
tmp := tag.Get("locale")
|
||||
if len(tmp) != 0 {
|
||||
msgidPos = field.Tag.ValuePos
|
||||
msgid = tmp
|
||||
}
|
||||
}
|
||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||
}
|
||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
|
@ -45,57 +44,12 @@ type StringTrie interface {
|
|||
|
||||
type StringTrieMap map[string]StringTrie
|
||||
|
||||
func printfPatternToRegex(key string) (string, bool) {
|
||||
parts := strings.Split(key, "%")
|
||||
if len(parts) < 2 {
|
||||
return key, false
|
||||
}
|
||||
var pattern strings.Builder
|
||||
pattern.WriteString("^")
|
||||
pattern.WriteString(parts[0])
|
||||
skip := false
|
||||
for _, part := range parts[1:] {
|
||||
if skip {
|
||||
skip = false
|
||||
continue
|
||||
}
|
||||
if len(part) == 0 {
|
||||
// "%%"
|
||||
pattern.WriteString("%")
|
||||
continue
|
||||
}
|
||||
switch part[0] {
|
||||
case 'd':
|
||||
pattern.WriteString("[0-9]+")
|
||||
default:
|
||||
pattern.WriteString("[A-Za-z0-9]*")
|
||||
}
|
||||
pattern.WriteString(part[1:])
|
||||
}
|
||||
pattern.WriteString("$")
|
||||
return pattern.String(), true
|
||||
}
|
||||
|
||||
func (m StringTrieMap) Matches(key []string) bool {
|
||||
if len(key) == 0 || m == nil {
|
||||
return true
|
||||
}
|
||||
value, ok := m[key[0]]
|
||||
if !ok {
|
||||
for altKey, value := range m {
|
||||
// TODO: cache mapping $printfFormatString -> $regexpCompileOutput
|
||||
pattern, found := printfPatternToRegex(altKey)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
matched, err := regexp.MatchString(pattern, key[0])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to compile regexp '%s': %s", pattern, err.Error()))
|
||||
}
|
||||
if matched && (value == nil || value.Matches(key[1:])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if value == nil {
|
||||
|
|
@ -147,7 +101,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if linePrefix, found := strings.CutSuffix(line, "."); found || strings.Contains(line, "%") {
|
||||
if linePrefix, found := strings.CutSuffix(line, "."); found {
|
||||
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
||||
} else {
|
||||
if !chkMsgid(line) {
|
||||
|
|
@ -191,14 +145,9 @@ func Usage() {
|
|||
|
||||
fmt.Fprintf(outp, "\nSpecial Go doc comments:\n")
|
||||
for _, i := range []string{
|
||||
"//llu:returnsTrKeyWeak",
|
||||
"\tcan be used in front of functions to indicate",
|
||||
"\tthat the function returns message IDs (allows nesting inside complicated function calls)",
|
||||
"\tWARNING: this currently doesn't support nested functions properly",
|
||||
"",
|
||||
"//llu:returnsTrKey",
|
||||
"\tcan be used in front of functions to indicate",
|
||||
"\tthat the function returns message IDs (doesn't allow nesting inside complicated function calls)",
|
||||
"\tthat the function returns message IDs",
|
||||
"\tWARNING: this currently doesn't support nested functions properly",
|
||||
"",
|
||||
"//llu:returnsTrKeySuffix prefix.",
|
||||
|
|
@ -311,10 +260,6 @@ func main() {
|
|||
}
|
||||
|
||||
handler := llu.Handler{
|
||||
OnMsgidPattern: func(fset *token.FileSet, pos token.Pos, msgidPattern string) {
|
||||
msgidPatternSplit := strings.Split(msgidPattern, ".")
|
||||
allowedMaskedPrefixes.Insert(msgidPatternSplit)
|
||||
},
|
||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
||||
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
||||
if !truncated {
|
||||
|
|
@ -325,10 +270,6 @@ func main() {
|
|||
}
|
||||
},
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
if strings.Contains(msgid, "%") {
|
||||
fmt.Printf("%s:\tunexpected msgid pattern: %s\n", fset.Position(pos).String(), msgid)
|
||||
return
|
||||
}
|
||||
if !msgids.Contains(msgid) {
|
||||
if weak && allowWeakMissingMsgids {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -34,14 +34,12 @@ func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.Basic
|
|||
}
|
||||
|
||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
switch n := n.(type) {
|
||||
case *ast.BasicLit:
|
||||
handler.HandleGoTrBasicLit(fset, n, prefix)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
if n.Op != token.ADD {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, prefix)
|
||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
||||
if argBinExpr.Op != token.ADD {
|
||||
// pass
|
||||
} else if argLit, ok := n.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
} else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
|
|
@ -55,39 +53,6 @@ func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefi
|
|||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
if selExpr, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||
if xIdent, xok := selExpr.X.(*ast.Ident); !xok || xIdent.Name != "fmt" {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Sprintf" {
|
||||
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function encountered: %s", selExpr.Sel.Name))
|
||||
return
|
||||
}
|
||||
if len(n.Args) == 0 {
|
||||
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function invocation (no arguments) of '%s'", selExpr.Sel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if argLit, ok := n.Args[0].(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.Contains(arg, " ") {
|
||||
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf(
|
||||
"formatting function invocation of '%s' with weird msgid format string: %s",
|
||||
selExpr.Sel.Name,
|
||||
arg,
|
||||
))
|
||||
return
|
||||
}
|
||||
// found interesting strings
|
||||
handler.OnMsgidPattern(fset, argLit.ValuePos, prefix+arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,13 +60,9 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
|
||||
case tmplParser.NodeField:
|
||||
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
||||
if len(nodeField.Ident) != 2 || nodeField.Ident[0] != "locale" {
|
||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
||||
return
|
||||
}
|
||||
resolvedPos := fset.PositionFor(token.Pos(nodeCommand.Pos), false)
|
||||
if !strings.Contains(resolvedPos.Filename, "templates/mail/") {
|
||||
handler.OnWarning(fset, token.Pos(nodeCommand.Pos), "encountered unexpected .locale usage")
|
||||
}
|
||||
funcname = nodeField.Ident[1]
|
||||
|
||||
case tmplParser.NodeVariable:
|
||||
|
|
@ -150,12 +146,16 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
|||
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
||||
} else {
|
||||
if nodeIdent.Ident == "printf" {
|
||||
// found interesting strings
|
||||
if !(strings.HasSuffix(msgidPrefix, ".%s") && strings.Count(msgidPrefix, "%") == 1) {
|
||||
handler.OnMsgidPattern(fset, stringPos, msgidPrefix)
|
||||
parts := strings.SplitN(msgidPrefix, "%", 2)
|
||||
if len(parts) != 2 {
|
||||
handler.OnWarning(
|
||||
fset,
|
||||
stringPos,
|
||||
fmt.Sprintf("unsupported invocation of locate function (format string doesn't match \"prefix%%smth\" pattern): %s", nodeString.String()),
|
||||
)
|
||||
return
|
||||
}
|
||||
msgidPrefix = strings.TrimSuffix(msgidPrefix, "%s")
|
||||
msgidPrefix = parts[0]
|
||||
}
|
||||
|
||||
msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ func InitLocaleTrFunctions() map[string][]uint {
|
|||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnMsgidPattern func(fset *token.FileSet, pos token.Pos, msgidPattern string)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ func subcmdRegenerate() *cli.Command {
|
|||
Name: "regenerate",
|
||||
Usage: "Regenerate specific files",
|
||||
Commands: []*cli.Command{
|
||||
microcmdRegenHooks,
|
||||
microcmdRegenKeys,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,36 @@ import (
|
|||
"context"
|
||||
|
||||
asymkey_model "forgejo.org/models/asymkey"
|
||||
"forgejo.org/modules/graceful"
|
||||
repo_service "forgejo.org/services/repository"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateKeys,
|
||||
var (
|
||||
microcmdRegenHooks = &cli.Command{
|
||||
Name: "hooks",
|
||||
Usage: "Regenerate git-hooks",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateHooks,
|
||||
}
|
||||
|
||||
microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateKeys,
|
||||
}
|
||||
)
|
||||
|
||||
func runRegenerateHooks(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
}
|
||||
|
||||
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func subcmdUser() *cli.Command {
|
|||
microcmdUserChangePassword(),
|
||||
microcmdUserDelete(),
|
||||
microcmdUserGenerateAccessToken(),
|
||||
microcmdUserCreateAuthorizedIntegration(),
|
||||
microcmdUserMustChangePassword(),
|
||||
microcmdUserResetMFA(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,225 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/services/authz"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func microcmdUserCreateAuthorizedIntegration() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create-authorized-integration",
|
||||
Description: `Creates an authorized integration. Authorized integrations allow Forgejo to
|
||||
receive JWTs from external sources, validate their claims against
|
||||
user-defined rules, and grant access to Forgejo's API on behalf of a user.
|
||||
|
||||
The issuer may be set to "urn:forgejo:authorized-integrations:actions"
|
||||
to support JWTs from the local instance's Forgejo Actions, utilizing the
|
||||
enable-openid-connect flag in a workflow.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "Username",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Name of the authorized integration for later identification",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "Optional description for the authorized integration",
|
||||
},
|
||||
|
||||
// JWT validation:
|
||||
&cli.StringFlag{
|
||||
Name: "issuer",
|
||||
Usage: `JWT issuer ('iss' claim), example: https://forgejo.example.org/api/actions`,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringMapFlag{
|
||||
Name: "claim-eq",
|
||||
Value: map[string]string{},
|
||||
Usage: `Zero-or-more claim equality checks, formatted as claim=value, example: "actor=someuser"`,
|
||||
},
|
||||
&cli.StringMapFlag{
|
||||
Name: "claim-glob",
|
||||
Value: map[string]string{},
|
||||
Usage: `Zero-or-more claim glob checks, formatted as claim=value, example: "sub=repo:forgejo/*:pull_request"`,
|
||||
},
|
||||
// nested claim support omitted for now -- pretty complex for a CLI
|
||||
|
||||
// Permissions available on successful auth:
|
||||
&cli.StringSliceFlag{
|
||||
Name: "scope",
|
||||
Value: []string{"all"},
|
||||
Usage: `One-or-more scopes to apply to access token, examples: "all", "read:issue", "write:repository"`,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "repo",
|
||||
Value: []string{"all"},
|
||||
Usage: `Zero-or-more specific repositories that can be accessed, or "all" to allow access to all repositories, example: "owner1/repo1"`,
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runCreateAuthorizedIntegration,
|
||||
}
|
||||
}
|
||||
|
||||
func runCreateAuthorizedIntegration(ctx context.Context, c *cli.Command) error {
|
||||
if !c.IsSet("username") {
|
||||
return errors.New("you must provide a username to generate a token for")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ai := &auth_model.AuthorizedIntegration{
|
||||
UserID: user.ID,
|
||||
Name: c.String("name"),
|
||||
Description: c.String("description"),
|
||||
}
|
||||
|
||||
var rules []auth_model.ClaimRule
|
||||
ai.Issuer = c.String("issuer")
|
||||
for claim, value := range c.StringMap("claim-eq") {
|
||||
rules = append(rules, auth_model.ClaimRule{
|
||||
Claim: claim,
|
||||
Comparison: auth_model.ClaimEqual,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
for claim, value := range c.StringMap("claim-glob") {
|
||||
rules = append(rules, auth_model.ClaimRule{
|
||||
Claim: claim,
|
||||
Comparison: auth_model.ClaimGlob,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
ai.ClaimRules = &auth_model.ClaimRules{Rules: rules}
|
||||
|
||||
scopes := strings.Join(c.StringSlice("scope"), ",")
|
||||
accessTokenScope, err := auth_model.AccessTokenScope(scopes).Normalize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||
}
|
||||
ai.Scope = accessTokenScope
|
||||
|
||||
allRepos := false
|
||||
repos := []*repo.Repository{}
|
||||
for _, repoName := range c.StringSlice("repo") {
|
||||
if repoName == "all" {
|
||||
allRepos = true
|
||||
} else {
|
||||
split := strings.Split(repoName, "/")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid repo name: %q", split)
|
||||
}
|
||||
owner := split[0]
|
||||
name := split[1]
|
||||
repo, err := repo.GetRepositoryByOwnerAndName(ctx, owner, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
}
|
||||
ai.ResourceAllRepos = allRepos
|
||||
|
||||
rr := make([]*auth_model.AuthorizedIntegResourceRepo, len(repos))
|
||||
for i := range repos {
|
||||
rr[i] = &auth_model.AuthorizedIntegResourceRepo{RepoID: repos[i].ID}
|
||||
}
|
||||
if err := authz.ValidateAuthorizedIntegration(ai, rr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := auth_model.InsertAuthorizedIntegration(ctx, ai); err != nil {
|
||||
return err
|
||||
}
|
||||
if !allRepos {
|
||||
if err := auth_model.InsertAuthorizedIntegrationResourceRepos(ctx, ai.ID, rr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type ClaimRuleDescription struct {
|
||||
Description string `json:"description"`
|
||||
Claim string `json:"claim"`
|
||||
Comparison auth_model.ClaimComparison `json:"compare"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
output := struct {
|
||||
Message string `json:"message"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Issuer string `json:"issuer"`
|
||||
Audience string `json:"audience"`
|
||||
ClaimRules []ClaimRuleDescription `json:"claim_rules"`
|
||||
}{
|
||||
Message: "Authorized integration was successfully created.",
|
||||
Name: ai.Name,
|
||||
Description: ai.Description,
|
||||
Issuer: ai.Issuer,
|
||||
Audience: ai.Audience,
|
||||
}
|
||||
for _, cr := range ai.ClaimRules.Rules {
|
||||
var description string
|
||||
switch cr.Comparison {
|
||||
case auth_model.ClaimEqual:
|
||||
description = fmt.Sprintf("%q = %q", cr.Claim, cr.Value)
|
||||
case auth_model.ClaimGlob:
|
||||
description = fmt.Sprintf("%q matches %q", cr.Claim, cr.Value)
|
||||
}
|
||||
output.ClaimRules = append(output.ClaimRules, ClaimRuleDescription{
|
||||
Description: description,
|
||||
Claim: cr.Claim,
|
||||
Comparison: cr.Comparison,
|
||||
Value: cr.Value,
|
||||
})
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var indent bytes.Buffer
|
||||
if err := json.Indent(&indent, raw, "", " "); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(indent.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -150,8 +150,8 @@ func runCert(ctx context.Context, c *cli.Command) error {
|
|||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
hosts := strings.SplitSeq(c.String("host"), ",")
|
||||
for h := range hosts {
|
||||
hosts := strings.Split(c.String("host"), ",")
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
|
|
|
|||
42
cmd/dump.go
42
cmd/dump.go
|
|
@ -12,7 +12,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -84,9 +83,11 @@ func (o outputType) Join() string {
|
|||
}
|
||||
|
||||
func (o *outputType) Set(value string) error {
|
||||
if slices.Contains(o.Enum, value) {
|
||||
o.selected = value
|
||||
return nil
|
||||
for _, enum := range o.Enum {
|
||||
if enum == value {
|
||||
o.selected = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("allowed values are %s", o.Join())
|
||||
|
|
@ -112,10 +113,7 @@ func getArchiverByType(outType string) (archives.ArchiverAsync, error) {
|
|||
var archiver archives.ArchiverAsync
|
||||
switch outType {
|
||||
case "zip":
|
||||
archiver = archives.Zip{
|
||||
Compression: 8,
|
||||
SelectiveCompression: false,
|
||||
}
|
||||
archiver = archives.Zip{}
|
||||
case "tar":
|
||||
archiver = archives.Tar{}
|
||||
case "tar.sz":
|
||||
|
|
@ -252,8 +250,8 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
||||
} else {
|
||||
for _, suffix := range outputTypeEnum.Enum {
|
||||
if before, ok := strings.CutSuffix(fileName, "."+suffix); ok {
|
||||
fileName = before
|
||||
if strings.HasSuffix(fileName, "."+suffix) {
|
||||
fileName = strings.TrimSuffix(fileName, "."+suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -332,12 +330,14 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
|
||||
|
||||
if len(setting.CustomConf) > 0 {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
|
||||
fatal("Failed to include specified app.ini: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||
|
|
@ -361,13 +361,15 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||
log.Info("Skipping attachment data")
|
||||
} else {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump attachments: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||
|
|
@ -375,13 +377,15 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
} else if !setting.Packages.Enabled {
|
||||
log.Info("Package registry not enabled - skipping")
|
||||
} else {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump packages: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||
|
|
@ -395,11 +399,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||
}
|
||||
if isExist {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
|
|||
opts.PullRequests = true
|
||||
opts.ReleaseAssets = true
|
||||
} else {
|
||||
units := strings.SplitSeq(ctx.String("units"), ",")
|
||||
for unit := range units {
|
||||
units := strings.Split(ctx.String("units"), ",")
|
||||
for _, unit := range units {
|
||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||
case "":
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
|
|||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
break
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ func appGlobalFlags() []cli.Flag {
|
|||
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) {
|
||||
command.Flags = append(globalFlags(), command.Flags...)
|
||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||
command.HideHelp = true
|
||||
if command.Name != "help" {
|
||||
command.Commands = append(command.Commands, cmdHelp())
|
||||
}
|
||||
|
|
@ -198,6 +199,7 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
|
|||
}
|
||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
||||
app.Flags = append(app.Flags, globalFlags()...)
|
||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
||||
app.Before = PrepareConsoleLoggerLevel(log.INFO)
|
||||
for i := range subCmdWithConfig {
|
||||
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -63,12 +62,7 @@ func runTestApp(app *cli.Command, args ...string) (runResult, error) {
|
|||
}
|
||||
|
||||
func TestCliCmd(t *testing.T) {
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defaultWorkPath := filepath.Dir(path)
|
||||
defaultWorkPath := filepath.Dir(setting.AppPath)
|
||||
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
|
||||
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
||||
|
||||
|
|
|
|||
|
|
@ -290,9 +290,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||
Op: lfsVerb,
|
||||
UserID: results.UserID,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := setting.LFS.SigningKey.JWT(claims)
|
||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
||||
if err != nil {
|
||||
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,8 @@ func serveInstall(_ context.Context, ctx *cli.Command) error {
|
|||
}
|
||||
|
||||
func serveInstalled(_ context.Context, ctx *cli.Command) error {
|
||||
setting.InitCfgProvider(setting.CustomConf)
|
||||
setting.LoadCommonSettings()
|
||||
setting.MustInstalled()
|
||||
|
||||
showWebStartupMessage("Prepare to run web server")
|
||||
|
|
@ -276,11 +278,8 @@ func setPort(port string) error {
|
|||
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
break
|
||||
case setting.FCGI:
|
||||
break
|
||||
case setting.FCGIUnix:
|
||||
break
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
if setting.HTTPAddr == "0.0.0.0" {
|
||||
|
|
|
|||
|
|
@ -313,9 +313,6 @@ RUN_USER = ; git
|
|||
;LFS_START_SERVER = false
|
||||
;;
|
||||
;;
|
||||
;; see JWT_* under [oauth2]
|
||||
;LFS_JWT_SIGNING_ALGORITHM = HS256
|
||||
;LFS_JWT_SIGNING_PRIVATE_KEY_FILE = jwt/lfs_private.pem
|
||||
;; LFS authentication secret, change this yourself
|
||||
;LFS_JWT_SECRET =
|
||||
;;
|
||||
|
|
@ -460,7 +457,7 @@ INTERNAL_TOKEN =
|
|||
;GLOBAL_TWO_FACTOR_REQUIREMENT = none
|
||||
;;
|
||||
;; Name of cookie used to store authentication information.
|
||||
;COOKIE_REMEMBER_NAME = persistent
|
||||
;COOKIE_REMEMBER_NAME = gitea_incredible
|
||||
;;
|
||||
;; Reverse proxy authentication header name of user name, email, and full name
|
||||
;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
|
||||
|
|
@ -547,7 +544,6 @@ ENABLED = true
|
|||
;; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH.
|
||||
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to RS256, RS384, RS512, ES256, ES384 or ES512.
|
||||
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
||||
;; XXX jwt/ is a misnomer, it should rather be oauth2/, because we use many JWTs
|
||||
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
||||
;;
|
||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
||||
|
|
@ -1899,7 +1895,7 @@ LEVEL = Info
|
|||
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
|
||||
;;
|
||||
;; Session cookie name
|
||||
;COOKIE_NAME = session
|
||||
;COOKIE_NAME = i_like_gitea
|
||||
;;
|
||||
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
;COOKIE_SECURE =
|
||||
|
|
@ -2794,7 +2790,7 @@ LEVEL = Info
|
|||
;; server and database workload due to more complex database queries and more frequent server task querying; this
|
||||
;; feature can be disabled to reduce performance impact
|
||||
;CONCURRENCY_GROUP_QUEUE_ENABLED = true
|
||||
;; Algorithm used to sign ID tokens. Valid values: RS256, RS384, RS512, ES256, ES384, ES512, EdDSA.
|
||||
;; Algorithm used to sign ID tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA.
|
||||
;; RS256 will ensure compatibility with all relying parties.
|
||||
;; If a different algorithm is chosen, verify that relying parties of interest support the signing algorithm.
|
||||
;ID_TOKEN_SIGNING_ALGORITHM = RS256
|
||||
|
|
@ -2823,30 +2819,3 @@ LEVEL = Info
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; storage type
|
||||
;STORAGE_TYPE = local
|
||||
|
||||
;; Authorized integrations are a capability for users to define external systems which can generate JWTs that Forgejo
|
||||
;; will trust in order to perform API access on behalf of that user. While validating a JWT from an external system,
|
||||
;; Forgejo makes outgoing HTTP requests to the JWT issuer.
|
||||
; [authorized_integration]
|
||||
;; Timeout for HTTP requests to remote servers. Default is 10 seconds.
|
||||
;REQUEST_TIMEOUT = 10s
|
||||
;
|
||||
;; Allowed domains for authorized integrations. Default is blank which means all domains will be allowed (except local
|
||||
;; networks, see ALLOW_LOCALNETWORKS).
|
||||
;; Multiple domains can be separated by commas.
|
||||
;; Wildcards are supported: "github.com, *.github.com"
|
||||
;ALLOWED_DOMAINS =
|
||||
;
|
||||
;; Blocklist for authorized integrations, default is blank.
|
||||
;; Multiple domains can be separated by commas.
|
||||
;; Wildcards are supported: "github.com, *.github.com"
|
||||
;BLOCKED_DOMAINS =
|
||||
;
|
||||
;; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291.
|
||||
;; Default is false.
|
||||
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
|
||||
;ALLOW_LOCALNETWORKS = false
|
||||
;
|
||||
;; Remote requests are cached after being received for the cache time-to-live (TTL). Default is 10 minutes.
|
||||
;; Caching uses the configured adapter in the [cache] config section.
|
||||
;CACHE_TTL = 10m
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
# And place the original in /usr/lib/gitea with working files in /data/gitea
|
||||
GITEA="/app/gitea/gitea"
|
||||
WORK_DIR="/var/lib/gitea"
|
||||
APP_INI="/var/lib/gitea/custom/conf/app.ini"
|
||||
APP_INI="/etc/gitea/app.ini"
|
||||
|
||||
APP_INI_SET=""
|
||||
for i in "$@"; do
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,6 @@ export default tseslint.config(
|
|||
|
||||
'playwright/prefer-comparison-matcher': [2],
|
||||
'playwright/prefer-equality-matcher': [2],
|
||||
'playwright/prefer-locator': [0],
|
||||
'playwright/prefer-native-locators': [2],
|
||||
'playwright/prefer-to-contain': [2],
|
||||
'playwright/prefer-to-have-length': [2],
|
||||
|
|
|
|||
155
go.mod
155
go.mod
|
|
@ -1,37 +1,37 @@
|
|||
module forgejo.org
|
||||
|
||||
go 1.26.0
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.26.3
|
||||
toolchain go1.25.7
|
||||
|
||||
require (
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.15
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20260301104140-add494e31dab
|
||||
code.forgejo.org/forgejo/actions-proto v0.7.0
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||
code.forgejo.org/forgejo/reply v1.0.2
|
||||
code.forgejo.org/forgejo/runner/v12 v12.10.1
|
||||
code.forgejo.org/forgejo/runner/v12 v12.7.0
|
||||
code.forgejo.org/go-chi/binding v1.0.1
|
||||
code.forgejo.org/go-chi/cache v1.0.1
|
||||
code.forgejo.org/go-chi/captcha v1.0.2
|
||||
code.forgejo.org/go-chi/session v1.0.4
|
||||
code.forgejo.org/go-chi/session v1.0.2
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.2
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.19.2
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/PuerkitoBio/goquery v1.12.0
|
||||
github.com/ProtonMail/go-crypto v1.3.0
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.0
|
||||
github.com/blevesearch/bleve/v2 v2.5.6
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.25.3
|
||||
github.com/caddyserver/certmagic v0.24.0
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
|
|
@ -41,77 +41,78 @@ require (
|
|||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.10.0
|
||||
github.com/gdgvda/cron v0.7.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.6
|
||||
github.com/go-enry/go-enry/v2 v2.9.4
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-openapi/spec v0.22.3
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/go-webauthn/webauthn v0.16.5
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.14.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/go-github/v81 v81.0.0
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/hashicorp/go-version v1.8.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/inbucket/html2text v1.0.0
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/inbucket/html2text v0.9.0
|
||||
github.com/jackc/pgx/v5 v5.8.0
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/klauspost/compress v1.18.4
|
||||
github.com/klauspost/cpuid/v2 v2.2.11
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/mattn/go-isatty v0.0.21
|
||||
github.com/mattn/go-sqlite3 v1.14.44
|
||||
github.com/meilisearch/meilisearch-go v0.36.2
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
github.com/meilisearch/meilisearch-go v0.36.0
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.1.0
|
||||
github.com/minio/minio-go/v7 v7.0.98
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
github.com/olivere/elastic/v7 v7.0.32
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/redis/go-redis/v9 v9.17.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli/v3 v3.8.0
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
github.com/valyala/fastjson v1.6.10
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||
go.uber.org/mock v0.6.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/image v0.39.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/image v0.36.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.34.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
)
|
||||
|
|
@ -119,46 +120,46 @@ require (
|
|||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.5 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.2 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.5 // indirect
|
||||
github.com/blevesearch/go-faiss v1.1.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.26 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.2.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.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/v17 v17.1.2 // indirect
|
||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
|
|
@ -166,12 +167,15 @@ require (
|
|||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.5 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
||||
|
|
@ -182,16 +186,15 @@ require (
|
|||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.3 // indirect
|
||||
github.com/go-webauthn/x v0.1.25 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/go-tpm v0.9.5 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
|
|
@ -200,21 +203,24 @@ require (
|
|||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/libdns/libdns v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.17 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
|
|
@ -225,7 +231,6 @@ require (
|
|||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.34.1 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
|
|
@ -235,28 +240,28 @@ require (
|
|||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rhysd/actionlint v1.7.10 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zeebo/assert v1.3.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
@ -269,4 +274,6 @@ replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-2024121
|
|||
|
||||
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.12
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.4
|
||||
|
||||
replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.5.0
|
||||
|
|
|
|||
322
go.sum
322
go.sum
|
|
@ -18,10 +18,10 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
|||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.15 h1:/EzJWRQUhVVia1EmCWRZHCuW8qdAX1DEXY0M1n0mECc=
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.15/go.mod h1:k99wl7QiI9LjS3qEd1RXLdcZPE3wZP5gkVhy8/WhzJ4=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20260301104140-add494e31dab h1:g/uf/tNOvCdGL9EuYV9EJQglEH8n572P1ZwH4lBBkjI=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20260301104140-add494e31dab/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/actions-proto v0.7.0 h1:0UA8AIOIWEiMLQvBbnZ6GQ9reh6BbM15Ep8x+oeTCY8=
|
||||
code.forgejo.org/forgejo/actions-proto v0.7.0/go.mod h1:+444hHBs9/qDh5X/AedaTv0Egj3vd/EXP93vg9zFV2E=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0 h1:dw1Dogk9A4V/yrLVkhe9dSZPsqNAIkI1kCXPSqG3tZA=
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0/go.mod h1:+444hHBs9/qDh5X/AedaTv0Egj3vd/EXP93vg9zFV2E=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk=
|
||||
code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA=
|
||||
|
|
@ -30,8 +30,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
|
|||
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.10.1 h1:qRKjWItVDc2lEMl3jGlKyFpqgX4xHeOdEyl/irO/Nk8=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.10.1/go.mod h1:A51GyZJlril5cIpVMvOn3NqE8upOE6ePjwC6s31kRHk=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.7.0 h1:yQWcSe6LQsb0a1iYAoxTXuquGW4cQNehy7870/fEmO0=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.7.0/go.mod h1:es/tNTXl1C2tUYyoIwpe7tUL+5NUH58fSfGxUZ0Zloo=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
||||
|
|
@ -40,27 +40,25 @@ code.forgejo.org/go-chi/cache v1.0.1 h1:w6IsDcPbeEnEYZn7M2HJe3/3/Ehtcw/72VjcVK7+
|
|||
code.forgejo.org/go-chi/cache v1.0.1/go.mod h1:K3aQSyRIN4xiuqV1kanfQ6O4ToDpzDpY3bNOyGjFe3U=
|
||||
code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkAaqZGQ=
|
||||
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
||||
code.forgejo.org/go-chi/session v1.0.4 h1:WQ1NaVxcCpxYwCliEGypKclZnOCjh3p1fk8XciJc62U=
|
||||
code.forgejo.org/go-chi/session v1.0.4/go.mod h1:+sSTiomM5C8AUPtxZyTENIbcTz22kcVottKO0lnmDRk=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.12 h1:EodlU2MGu/EeAdUpEOe+X4cU/qlnJol1ucJ0sHUCM1o=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.12/go.mod h1:ozQINrM8b7uYFMBB/w19nUTTLcda3RKTQ8HspocVKdg=
|
||||
code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc=
|
||||
code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.4 h1:kyJHREXNEIuzpMwQoouTbUldPP6s/UlL3ZAcNlO4C5s=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.4/go.mod h1:5ouTxqMcalQUvlBpQynRpzu/44GwaMpkA1nU+encsDE=
|
||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.2 h1:nkTdaghZb6I0oGYFhTLSALhA2ShgkPnYAJryE5IE9+0=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.2/go.mod h1:/Z+3DHSrefCzzN5ePkGjVYKFErRimoeUf694Gz8Pn/Y=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1 h1:qnujLH4/Yk/CFtFMmtjozbdV6Ry5G3Q/E/mLlWm/gQI=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1/go.mod h1:/Z+3DHSrefCzzN5ePkGjVYKFErRimoeUf694Gz8Pn/Y=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
|
||||
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
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=
|
||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
||||
|
|
@ -73,12 +71,12 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
|||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.5 h1:ckd0o545JqDPeVJDgeFoaM21eBixUnlWfYgjE5VnyWw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.5/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 h1:tgjwQrDH5m6jIYB7kac5IQZmfUzQNseac/e3H4VoCNE=
|
||||
|
|
@ -103,48 +101,47 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
|
|||
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0=
|
||||
github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/blevesearch/bleve/v2 v2.6.0 h1:Cyd3dd4q5tCbOV8MnKUVRUDYMHOir9xn12NZzXVSEd4=
|
||||
github.com/blevesearch/bleve/v2 v2.6.0/go.mod h1:gLmI8lWgHgrIYf7UpUX7JISI1CaqC6VScu46mHThuAY=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
|
||||
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
|
||||
github.com/blevesearch/go-faiss v1.1.0 h1:xM7Jc0ZUCv5lssG9Ohj3Jv0SdTpxcUABU1dDt9XVsc4=
|
||||
github.com/blevesearch/go-faiss v1.1.0/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/bleve/v2 v2.5.6 h1:YdixQmOUuZHojQRe8Te7BY2cRirbzpbcpybAFs0m2DI=
|
||||
github.com/blevesearch/bleve/v2 v2.5.6/go.mod h1:t5WoESS5TDteTdnjhhvpA1BpLYErOBX2IQViTMLK7wo=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
|
||||
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
||||
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
|
||||
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
|
||||
github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 h1:GlMzW08hcsM3DnLUxhyF/1PcDal1qtvvIuytuph5djw=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7/go.mod h1://IJ7tG3QCf0cWW/aVSXqy77tc1AvLu3fcJLYEvOAFs=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.2.0 h1:xkDiOEsHc2t3Cp0NsNZZ36pvc130sCzcGKOPMzXe+e0=
|
||||
github.com/blevesearch/vellum v1.2.0/go.mod h1:uEcfBJz7mAOf0Kvq6qoEKQQkLODBF46SINYNkZNae4k=
|
||||
github.com/blevesearch/zapx/v11 v11.4.3 h1:PTZOO5loKpHC/x/GzmPZNa9cw7GZIQxd5qRjwij9tHY=
|
||||
github.com/blevesearch/zapx/v11 v11.4.3/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||
github.com/blevesearch/zapx/v12 v12.4.3 h1:eElXvAaAX4m04t//CGBQAtHNPA+Q6A1hHZVrN3LSFYo=
|
||||
github.com/blevesearch/zapx/v12 v12.4.3/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 h1:qsdhRhaSpVnqDFlRiH9vG5+KJ+dE7KAW9WyZz/KXAiE=
|
||||
github.com/blevesearch/zapx/v13 v13.4.3/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 h1:GY4Hecx0C6UTmiNC2pKdeA2rOKiLR5/rwpU9WR51dgM=
|
||||
github.com/blevesearch/zapx/v14 v14.4.3/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2lc/0/CU=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2 h1:avbOk2igaASNoiy0BE/jPgcxAnRI2PGeydeP4hg7Ikk=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2/go.mod h1:WQObxKrqUX7cd0G1GMvDfc/bmZzQvoy7APOPimx7DiI=
|
||||
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
|
||||
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 h1:xcgFRa7f/tQXOwApVq7JWgPYSlzyUMmkuYa54tMDuR0=
|
||||
github.com/blevesearch/zapx/v16 v16.2.7/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
|
|
@ -164,10 +161,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/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||
github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A=
|
||||
github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
||||
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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=
|
||||
|
|
@ -194,6 +191,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
||||
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
|
||||
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
|
||||
|
|
@ -250,16 +249,15 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
|||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M=
|
||||
github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gdgvda/cron v0.7.0 h1:LFPZUTbCb5ZpzYxavbQDDbjd6nwTwkiNUWyulOdlY2I=
|
||||
github.com/gdgvda/cron v0.7.0/go.mod h1:caBF+mzTZGtQqFE05T1m6u9OmCASY3EK51XAICf3wio=
|
||||
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc h1:yLe7YJhK+XNjNV4SqDxAjpWAgft+KU+XwKZS4AKEUV0=
|
||||
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc/go.mod h1:jUs8eczo1EAT4ByRpZ4mQmNvjarw9eNf7Nm5udpMRhY=
|
||||
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966 h1:tV+3kZgqFMKVUf+JPKBV400ISM8440+6y/SQCS0WZwQ=
|
||||
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966/go.mod h1:zkp58Q5yXpCxZbh3d0GDvwqiYclfVuHEHjc9SZKAj6I=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI=
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
|
||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77 h1:yHAmoR6avNy84PlLmjHt1z9flAp2Qs2ens5QDE/CNWk=
|
||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77/go.mod h1:4h93IBxgfnE/DEleMLgJ/XCeu/RtQ+MUh3ucANseeXA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
|
|
@ -271,8 +269,8 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
|||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs=
|
||||
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-enry/v2 v2.9.4 h1:DS4l06/NgMzYjsJ2J52wORo6UsfFDjDCwfAn7w3gG44=
|
||||
github.com/go-enry/go-enry/v2 v2.9.4/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
|
|
@ -282,12 +280,16 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
|
|||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
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.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
|
||||
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||
|
|
@ -316,17 +318,15 @@ github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxE
|
|||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.16.5 h1:x+vADHlaiIjta23kGhtwyCIlB5mayKx6SBlpwQ5NF9A=
|
||||
github.com/go-webauthn/webauthn v0.16.5/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
|
||||
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
|
||||
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
|
||||
github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0=
|
||||
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
|
||||
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
|
||||
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
|
|
@ -367,8 +367,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
|||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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 v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
|
|
@ -386,10 +386,8 @@ github.com/google/go-github/v81 v81.0.0 h1:hTLugQRxSLD1Yei18fk4A5eYjOGLUBKAl/VCq
|
|||
github.com/google/go-github/v81 v81.0.0/go.mod h1:upyjaybucIbBIuxgJS7YLOZGziyvvJ92WX6WEBNE3sM=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc=
|
||||
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc=
|
||||
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
|
||||
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
|
@ -398,8 +396,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
|||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
|
@ -439,16 +437,18 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
|
|||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||
github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+qks=
|
||||
github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
|
|
@ -475,12 +475,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
|
|
@ -497,12 +497,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v1.0.0 h1:IvYaz07JNz6jUQ4h/fv2R4sVnRnm77J/aOuC9B+TQTA=
|
||||
github.com/libdns/libdns v1.0.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
|
|
@ -512,34 +510,36 @@ github.com/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ
|
|||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
||||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI=
|
||||
github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
|
||||
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
|
||||
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
|
||||
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
|
||||
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
||||
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
@ -553,8 +553,8 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG
|
|||
github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||
|
|
@ -595,8 +595,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
|
@ -606,8 +606,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
|||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
|
||||
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.7.10 h1:FL3XIEs72G4/++168vlv5FKOWMSWvWIQw1kBCadyOcM=
|
||||
|
|
@ -655,13 +655,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw=
|
||||
github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
|
|
@ -673,8 +674,8 @@ github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBz
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
|
|
@ -683,8 +684,6 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
|||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI=
|
||||
gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
|
|
@ -702,8 +701,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
|||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
|
|
@ -723,8 +722,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -733,12 +732,12 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
|||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
|
||||
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
|
||||
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
|
||||
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
@ -760,8 +759,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -792,15 +791,15 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -814,8 +813,8 @@ 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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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=
|
||||
|
|
@ -850,8 +849,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
@ -861,8 +860,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -876,12 +875,12 @@ 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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
@ -911,8 +910,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -973,6 +972,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
@ -982,6 +983,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
|
|
@ -991,16 +993,16 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
- id: 719931
|
||||
uuid: "9e0eb762-cbdf-4725-9c90-4298568a2a77"
|
||||
name: "runner-1"
|
||||
version: "dev"
|
||||
owner_id: 3 # Owned by org3
|
||||
repo_id: 0
|
||||
description: "A superb runner"
|
||||
agent_labels: ["debian", "gpu"]
|
||||
deleted: 0
|
||||
- id: 719932
|
||||
uuid: "b0a0a168-1b08-4365-95dd-e65040ca384d"
|
||||
name: "runner-2"
|
||||
version: "11.3.1"
|
||||
owner_id: 2 # Owned by user2
|
||||
repo_id: 0
|
||||
description: "An exclusive runner"
|
||||
agent_labels: ["docker"]
|
||||
deleted: 0
|
||||
- id: 719933
|
||||
uuid: "974f9caf-ee64-4022-a56b-67b28ea06a0d"
|
||||
name: "runner-3"
|
||||
version: "12.2.0"
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
description: "A runner for everyone"
|
||||
agent_labels: ["docker"]
|
||||
deleted: 0
|
||||
- id: 719934
|
||||
uuid: "022650a8-e999-4a3d-afb0-21f75233798b"
|
||||
name: "runner-4"
|
||||
version: "12.1.0"
|
||||
owner_id: 0
|
||||
repo_id: 32 # owned by org3
|
||||
description: ""
|
||||
agent_labels: ["debian"]
|
||||
deleted: 0
|
||||
- id: 719935
|
||||
uuid: "f3031695-87ea-41cf-8d48-21948e83ee17"
|
||||
name: "runner-5"
|
||||
version: "12.7.0"
|
||||
owner_id: 0
|
||||
repo_id: 36 # owned by user2
|
||||
description: ""
|
||||
agent_labels: ["arch"]
|
||||
deleted: 0
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
- id: 719931
|
||||
uuid: "9e0eb762-cbdf-4725-9c90-4298568a2a77"
|
||||
name: "runner-1"
|
||||
version: "dev"
|
||||
owner_id: 3 # Owned by org3
|
||||
repo_id: 0
|
||||
description: "A superb runner"
|
||||
agent_labels: ["debian", "gpu"]
|
||||
deleted: 0
|
||||
- id: 719932
|
||||
uuid: "b0a0a168-1b08-4365-95dd-e65040ca384d"
|
||||
name: "runner-2"
|
||||
version: "11.3.1"
|
||||
owner_id: 2 # Owned by user2
|
||||
repo_id: 0
|
||||
description: "An exclusive runner"
|
||||
agent_labels: ["docker"]
|
||||
deleted: 0
|
||||
- id: 719933
|
||||
uuid: "974f9caf-ee64-4022-a56b-67b28ea06a0d"
|
||||
name: "runner-3"
|
||||
version: "12.2.0"
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
description: "A runner for everyone"
|
||||
agent_labels: ["docker"]
|
||||
deleted: 0
|
||||
- id: 719934
|
||||
uuid: "022650a8-e999-4a3d-afb0-21f75233798b"
|
||||
name: "runner-4"
|
||||
version: "12.1.0"
|
||||
owner_id: 0
|
||||
repo_id: 32
|
||||
description: ""
|
||||
agent_labels: ["debian"]
|
||||
deleted: 0
|
||||
|
|
@ -9,7 +9,6 @@ package actions
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
|
@ -89,13 +88,6 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
|||
return artifact, nil
|
||||
}
|
||||
|
||||
// IsV4 reports whether the artifact was uploaded via the v4 backend.
|
||||
// The v4 backend stores the whole artifact as a single zip file;
|
||||
// v1-v3 stores each file as a separate row.
|
||||
func (a *ActionArtifact) IsV4() bool {
|
||||
return a.ArtifactName+".zip" == a.ArtifactPath && a.ContentEncoding == "application/zip"
|
||||
}
|
||||
|
||||
func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) {
|
||||
var art ActionArtifact
|
||||
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art)
|
||||
|
|
@ -158,32 +150,11 @@ type ActionArtifactMeta struct {
|
|||
Status ArtifactStatus
|
||||
}
|
||||
|
||||
// AggregatedArtifact is the aggregated view of a logical artifact
|
||||
// (one or more rows sharing the same run_id + artifact_name), used by the
|
||||
// public API to represent a single artifact to clients.
|
||||
type AggregatedArtifact struct {
|
||||
ID int64 `xorm:"id"`
|
||||
RunID int64 `xorm:"run_id"`
|
||||
RepoID int64 `xorm:"-"`
|
||||
ArtifactName string `xorm:"artifact_name"`
|
||||
FileSize int64 `xorm:"file_size"`
|
||||
Status ArtifactStatus `xorm:"status"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created_unix"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated_unix"`
|
||||
ExpiredUnix timeutil.TimeStamp `xorm:"expired_unix"`
|
||||
}
|
||||
|
||||
// APIDownloadURL returns the download URL for this artifact under the given
|
||||
// repository API URL prefix (e.g. "https://host/api/v1/repos/owner/name").
|
||||
func (a *AggregatedArtifact) APIDownloadURL(repoAPIURL string) string {
|
||||
return fmt.Sprintf("%s/actions/artifacts/%d/zip", repoAPIURL, a.ID)
|
||||
}
|
||||
|
||||
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
|
||||
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
|
||||
arts := make([]*ActionArtifactMeta, 0, 10)
|
||||
return arts, db.GetEngine(ctx).Table("action_artifact").
|
||||
Where(builder.Eq{"run_id": runID}.And(builder.In("status", ArtifactStatusUploadConfirmed, ArtifactStatusExpired))).
|
||||
Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
|
||||
GroupBy("artifact_name").
|
||||
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
|
||||
Find(&arts)
|
||||
|
|
@ -221,85 +192,3 @@ func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
|
|||
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
|
||||
return err
|
||||
}
|
||||
|
||||
// aggregatedArtifactConds returns the common WHERE clause used by aggregated
|
||||
// artifact queries: restrict to visible statuses and apply the caller's filters.
|
||||
// The Status field on opts is ignored — visibility is fixed to UploadConfirmed/Expired.
|
||||
func aggregatedArtifactConds(opts FindArtifactsOptions) builder.Cond {
|
||||
opts.Status = 0
|
||||
return opts.ToConds().And(builder.In("status", ArtifactStatusUploadConfirmed, ArtifactStatusExpired))
|
||||
}
|
||||
|
||||
const aggregatedArtifactSelect = "min(id) as id, run_id, artifact_name, sum(file_size) as file_size, max(status) as status, min(created_unix) as created_unix, max(updated_unix) as updated_unix, max(expired_unix) as expired_unix"
|
||||
|
||||
// ListAggregatedArtifacts returns paginated aggregated artifacts.
|
||||
// Each result represents one logical artifact: a (run_id, artifact_name) group,
|
||||
// with ID = MIN(id), FileSize = SUM(file_size), Status = MAX(status), and
|
||||
// timestamps aggregated accordingly. Status filter in opts is ignored; results
|
||||
// are always restricted to UploadConfirmed and Expired statuses.
|
||||
func ListAggregatedArtifacts(ctx context.Context, opts FindArtifactsOptions) ([]*AggregatedArtifact, int64, error) {
|
||||
cond := aggregatedArtifactConds(opts)
|
||||
|
||||
var countKeys []struct {
|
||||
ID int64 `xorm:"id"`
|
||||
}
|
||||
if err := db.GetEngine(ctx).Table("action_artifact").
|
||||
Where(cond).
|
||||
GroupBy("run_id, artifact_name").
|
||||
Select("min(id) as id").
|
||||
Find(&countKeys); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
total := int64(len(countKeys))
|
||||
|
||||
sess := db.GetEngine(ctx).Table("action_artifact").
|
||||
Where(cond).
|
||||
GroupBy("run_id, artifact_name").
|
||||
Select(aggregatedArtifactSelect).
|
||||
OrderBy("id DESC")
|
||||
|
||||
capacity := 10
|
||||
if opts.PageSize > 0 {
|
||||
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
capacity = opts.PageSize
|
||||
}
|
||||
|
||||
arts := make([]*AggregatedArtifact, 0, capacity)
|
||||
return arts, total, sess.Find(&arts)
|
||||
}
|
||||
|
||||
// GetAggregatedArtifactByID returns the aggregated artifact by its canonical ID
|
||||
// (MIN(id) of the group), scoped to the given repository. Returns util.ErrNotExist
|
||||
// when the ID does not exist, is not canonical for its group, or does not belong to repoID.
|
||||
// The repoID scoping is performed in the query so callers don't need a follow-up check.
|
||||
func GetAggregatedArtifactByID(ctx context.Context, repoID, artifactID int64) (*AggregatedArtifact, error) {
|
||||
var art ActionArtifact
|
||||
has, err := db.GetEngine(ctx).Where(builder.Eq{"id": artifactID, "repo_id": repoID}).Get(&art)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
|
||||
cond := aggregatedArtifactConds(FindArtifactsOptions{
|
||||
RunID: art.RunID,
|
||||
ArtifactName: art.ArtifactName,
|
||||
})
|
||||
|
||||
meta := new(AggregatedArtifact)
|
||||
has, err = db.GetEngine(ctx).Table("action_artifact").
|
||||
Where(cond).
|
||||
GroupBy("run_id, artifact_name").
|
||||
Select(aggregatedArtifactSelect).
|
||||
Get(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has || meta.ID != artifactID {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
|
||||
meta.RepoID = art.RepoID
|
||||
return meta, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ const (
|
|||
ErrorCodeIncompleteWithMissingOutput
|
||||
ErrorCodeIncompleteWithMissingMatrixDimension
|
||||
ErrorCodeIncompleteWithUnknownCause
|
||||
ErrorCodeUnknownJobInNeeds
|
||||
)
|
||||
|
||||
func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string {
|
||||
|
|
@ -70,8 +69,6 @@ func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string
|
|||
return lang.TrString("actions.workflow.incomplete_with_missing_matrix_dimension", run.PreExecutionErrorDetails...)
|
||||
case ErrorCodeIncompleteWithUnknownCause:
|
||||
return lang.TrString("actions.workflow.incomplete_with_unknown_cause", run.PreExecutionErrorDetails...)
|
||||
case ErrorCodeUnknownJobInNeeds:
|
||||
return lang.TrString("actions.workflow.unknown_job_in_needs", run.PreExecutionErrorDetails...)
|
||||
}
|
||||
return fmt.Sprintf("<unsupported error: code=%v details=%#v", run.PreExecutionErrorCode, run.PreExecutionErrorDetails)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,13 +104,6 @@ func (run *ActionRun) Link() string {
|
|||
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
|
||||
}
|
||||
|
||||
func (run *ActionRun) CommitLink() string {
|
||||
if run.Repo == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA)
|
||||
}
|
||||
|
||||
// WorkflowPath returns the path in the git repo to the workflow file that this run was based on
|
||||
func (run *ActionRun) WorkflowPath() string {
|
||||
if run.WorkflowDirectory == "" {
|
||||
|
|
@ -153,9 +146,7 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
|
|||
|
||||
if run.TriggerUser == nil {
|
||||
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
u = user_model.NewGhostUser()
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run.TriggerUser = u
|
||||
|
|
@ -249,41 +240,6 @@ func (run *ActionRun) FindOuterWorkflowCall(ctx context.Context, innerCall *Acti
|
|||
return nil, fmt.Errorf("no workflow call with ID %s found in run %d", parent, run.ID)
|
||||
}
|
||||
|
||||
func (run *ActionRun) IsScheduledRun() bool {
|
||||
return run.TriggerEvent == "schedule"
|
||||
}
|
||||
|
||||
func (run *ActionRun) IsDispatchedRun() bool {
|
||||
return run.TriggerEvent == "workflow_dispatch"
|
||||
}
|
||||
|
||||
// IsValid indicates whether this ActionRun is valid and can be run.
|
||||
func (run *ActionRun) IsValid() bool {
|
||||
return run.PreExecutionErrorCode == 0 && run.PreExecutionError == ""
|
||||
}
|
||||
|
||||
// CanBeRerun indicates whether this ActionRun can be rerun.
|
||||
func (run *ActionRun) CanBeRerun() bool {
|
||||
if !run.IsValid() {
|
||||
return false
|
||||
}
|
||||
return run.Status.IsDone()
|
||||
}
|
||||
|
||||
func (run *ActionRun) PrepareNextAttempt() error {
|
||||
if run.Status != StatusUnknown && !run.Status.IsDone() {
|
||||
return fmt.Errorf("cannot prepare next attempt because run %d is active: %s", run.ID, run.Status.String())
|
||||
}
|
||||
|
||||
run.PreviousDuration = run.Duration()
|
||||
|
||||
run.Status = StatusWaiting
|
||||
run.Started = 0
|
||||
run.Stopped = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func actionsCountOpenCacheKey(repoID int64) string {
|
||||
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
|
||||
}
|
||||
|
|
@ -347,7 +303,7 @@ func UpdateRunApprovalByID(ctx context.Context, id int64, approval ApprovalType,
|
|||
func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, pullRequestPosterID int64) ([]*ActionRun, error) {
|
||||
var runs []*ActionRun
|
||||
// performance relies on indexes on repo_id and status
|
||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
|
||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runs, nil
|
||||
|
|
@ -356,7 +312,7 @@ func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, p
|
|||
func GetRunsNotDoneByRepoIDAndPullRequestID(ctx context.Context, repoID, pullRequestID int64) ([]*ActionRun, error) {
|
||||
var runs []*ActionRun
|
||||
// performance relies on indexes on repo_id and status
|
||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
|
||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runs, nil
|
||||
|
|
@ -427,8 +383,7 @@ func InsertRunJobs(ctx context.Context, run *ActionRun, jobs []*jobparser.Single
|
|||
name, _ = util.SplitStringAtByteN(job.Name, 255)
|
||||
runsOn = job.RunsOn()
|
||||
}
|
||||
|
||||
runJob := &ActionRunJob{
|
||||
runJobs = append(runJobs, &ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
|
|
@ -439,12 +394,8 @@ func InsertRunJobs(ctx context.Context, run *ActionRun, jobs []*jobparser.Single
|
|||
JobID: id,
|
||||
Needs: needs,
|
||||
RunsOn: runsOn,
|
||||
}
|
||||
if err := runJob.PrepareNextAttempt(status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runJobs = append(runJobs, runJob)
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
|
||||
if len(runJobs) > 0 {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import (
|
|||
"forgejo.org/modules/util"
|
||||
|
||||
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
||||
gouuid "github.com/google/uuid"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
|
@ -31,7 +30,6 @@ type ActionRunJob struct {
|
|||
IsForkPullRequest bool
|
||||
Name string `xorm:"VARCHAR(255)"`
|
||||
Attempt int64
|
||||
Handle string `xorm:"unique"`
|
||||
WorkflowPayload []byte
|
||||
JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
|
||||
Needs []string `xorm:"JSON TEXT"`
|
||||
|
|
@ -110,12 +108,6 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
|
|||
return job.Run.LoadAttributes(ctx)
|
||||
}
|
||||
|
||||
// IsRequestedByRunner returns true if this attempt of this ActionRunJob was explicitly requested by the runner or if
|
||||
// the runner expressed no preference.
|
||||
func (job *ActionRunJob) IsRequestedByRunner(handle *string) bool {
|
||||
return handle == nil || job.Handle == *handle
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) ItRunsOn(labels []string) bool {
|
||||
if len(labels) == 0 || len(job.RunsOn) == 0 {
|
||||
return false
|
||||
|
|
@ -125,34 +117,6 @@ func (job *ActionRunJob) ItRunsOn(labels []string) bool {
|
|||
return labelSet.IsSubset(job.RunsOn)
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) PrepareNextAttempt(initialStatus Status) error {
|
||||
if job.Status != StatusUnknown && !job.Status.IsDone() {
|
||||
return fmt.Errorf("cannot prepare next attempt because job %d is active: %s", job.ID, job.Status.String())
|
||||
}
|
||||
|
||||
job.Attempt++
|
||||
job.Started = 0
|
||||
job.Stopped = 0
|
||||
job.TaskID = 0
|
||||
job.Handle = gouuid.New().String()
|
||||
job.Status = initialStatus
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanBeRerun answers whether this ActionRunJob can be rerun. Returns true if it is done and the Run it belongs to
|
||||
// is valid. Returns false in all other cases.
|
||||
func (job *ActionRunJob) CanBeRerun(ctx context.Context) (bool, error) {
|
||||
if err := job.LoadRun(ctx); err != nil {
|
||||
return false, fmt.Errorf("cannot load run %d of job %d: %w", job.RunID, job.ID, err)
|
||||
}
|
||||
|
||||
if !job.Run.IsValid() {
|
||||
return false, nil
|
||||
}
|
||||
return job.Status.IsDone(), nil
|
||||
}
|
||||
|
||||
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
||||
var job ActionRunJob
|
||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
|
||||
|
|
@ -351,17 +315,3 @@ func (job *ActionRunJob) EnableOpenIDConnect() (bool, error) {
|
|||
}
|
||||
return jobWorkflow.EnableOpenIDConnect, nil
|
||||
}
|
||||
|
||||
// AllNeedsExist checks whether this ActionRunJob's Needs can theoretically be met by comparing them with the supplied
|
||||
// list of all job IDs that part of a particular workflow run. Returns the list of unknown job IDs found in Needs
|
||||
// alongside an indicator whether the check was successful.
|
||||
func (job *ActionRunJob) AllNeedsExist(allExistingJobIDs container.Set[string]) ([]string, bool) {
|
||||
unknownJobIDs := []string{}
|
||||
for _, need := range job.Needs {
|
||||
if !allExistingJobIDs.Contains(need) {
|
||||
unknownJobIDs = append(unknownJobIDs, need)
|
||||
}
|
||||
}
|
||||
|
||||
return unknownJobIDs, len(unknownJobIDs) == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,6 @@ func (jobs ActionJobList) GetRunIDs() []int64 {
|
|||
})
|
||||
}
|
||||
|
||||
func (jobs ActionJobList) GetJobIDs() container.Set[string] {
|
||||
jobIDs := container.SetOf[string]()
|
||||
for _, job := range jobs {
|
||||
jobIDs.Add(job.JobID)
|
||||
}
|
||||
return jobIDs
|
||||
}
|
||||
|
||||
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
||||
runIDs := jobs.GetRunIDs()
|
||||
runs := make(map[int64]*ActionRun, len(runIDs))
|
||||
|
|
@ -74,7 +66,7 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.CommitSHA != "" {
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/container"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestActionJobList_GetJobIDs(t *testing.T) {
|
||||
jobs := ActionJobList{
|
||||
&ActionRunJob{JobID: "job 1"},
|
||||
&ActionRunJob{JobID: "job 2"},
|
||||
}
|
||||
|
||||
assert.Equal(t, container.SetOf("job 2", "job 1"), jobs.GetJobIDs())
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -303,202 +301,3 @@ func TestRunHasOtherJobs(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
}
|
||||
|
||||
func TestActionRunJobPrepareNextAttempt(t *testing.T) {
|
||||
lastHandle := "original-handle"
|
||||
job := ActionRunJob{ID: 46, Handle: lastHandle}
|
||||
|
||||
err := job.PrepareNextAttempt(StatusWaiting)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, lastHandle, job.Handle)
|
||||
assert.NotEmpty(t, job.Handle)
|
||||
assert.Equal(t, int64(1), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusWaiting, job.Status)
|
||||
|
||||
lastHandle = job.Handle
|
||||
job.Started = timeutil.TimeStampNow()
|
||||
job.Stopped = timeutil.TimeStampNow()
|
||||
job.TaskID = int64(59)
|
||||
job.Status = StatusFailure
|
||||
|
||||
err = job.PrepareNextAttempt(StatusBlocked)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, lastHandle, job.Handle)
|
||||
assert.NotEmpty(t, job.Handle)
|
||||
assert.Equal(t, int64(2), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusBlocked, job.Status)
|
||||
|
||||
lastHandle = job.Handle
|
||||
|
||||
// The job hasn't finished yet. Preparing a next attempt should not be possible. It should be left untouched.
|
||||
err = job.PrepareNextAttempt(StatusWaiting)
|
||||
require.ErrorContains(t, err, "cannot prepare next attempt because job 46 is active: blocked")
|
||||
|
||||
assert.Equal(t, lastHandle, job.Handle)
|
||||
assert.Equal(t, int64(2), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusBlocked, job.Status)
|
||||
}
|
||||
|
||||
func TestIsRequestedByRunner(t *testing.T) {
|
||||
sameHandle := "4a1ca0be-4470-486d-8504-89b4a5ac00cf"
|
||||
differentHandle := "88423da3-67af-4f2d-9a92-a0db822697e9"
|
||||
emptyHandle := ""
|
||||
|
||||
job := &ActionRunJob{ID: 422, Attempt: 5, Handle: sameHandle}
|
||||
|
||||
assert.True(t, job.IsRequestedByRunner(nil))
|
||||
assert.True(t, job.IsRequestedByRunner(&sameHandle))
|
||||
assert.False(t, job.IsRequestedByRunner(&differentHandle))
|
||||
assert.False(t, job.IsRequestedByRunner(&emptyHandle))
|
||||
|
||||
// Old jobs that were created before the introduction of Handle do not have one.
|
||||
emptyHandleJob := &ActionRunJob{ID: 422, Attempt: 5, Handle: ""}
|
||||
|
||||
assert.True(t, emptyHandleJob.IsRequestedByRunner(nil))
|
||||
assert.True(t, emptyHandleJob.IsRequestedByRunner(&emptyHandle))
|
||||
|
||||
assert.False(t, emptyHandleJob.IsRequestedByRunner(&differentHandle))
|
||||
}
|
||||
|
||||
func TestAllNeedsExist(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
job ActionRunJob
|
||||
existingJobIDs container.Set[string]
|
||||
expectedUnknownIDs []string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "no needs",
|
||||
job: ActionRunJob{Needs: nil},
|
||||
existingJobIDs: container.Set[string]{},
|
||||
expectedUnknownIDs: []string{},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "empty needs",
|
||||
job: ActionRunJob{Needs: []string{}},
|
||||
existingJobIDs: container.Set[string]{},
|
||||
expectedUnknownIDs: []string{},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "satisfied needs",
|
||||
job: ActionRunJob{Needs: []string{"job1", "job2"}},
|
||||
existingJobIDs: container.SetOf("job2", "job1"),
|
||||
expectedUnknownIDs: []string{},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "unsatisfied needs",
|
||||
job: ActionRunJob{Needs: []string{"unknown", "job2"}},
|
||||
existingJobIDs: container.SetOf("job2", "job1"),
|
||||
expectedUnknownIDs: []string{"unknown"},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "comparison is case-sensitive",
|
||||
job: ActionRunJob{Needs: []string{"Job1", "job2"}},
|
||||
existingJobIDs: container.SetOf("job2", "job1"),
|
||||
expectedUnknownIDs: []string{"Job1"},
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
unknownIDs, ok := testCase.job.AllNeedsExist(testCase.existingJobIDs)
|
||||
|
||||
assert.Equal(t, testCase.ok, ok)
|
||||
assert.Equal(t, testCase.expectedUnknownIDs, unknownIDs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionRunJob_CanBeRerun(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
job ActionRunJob
|
||||
canBeRerun bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "job with unknown status",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusUnknown},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "successful job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSuccess},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "failed job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusFailure},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "cancelled job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusCancelled},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "skipped job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSkipped},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "waiting job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusWaiting},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "blocked job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusBlocked},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "ActionRun is nil",
|
||||
job: ActionRunJob{ID: 12, Run: nil, Status: StatusSuccess},
|
||||
expectedError: "cannot load run 0 of job 12",
|
||||
},
|
||||
{
|
||||
name: "with busy run but completed job",
|
||||
job: ActionRunJob{Run: &ActionRun{Status: StatusRunning}, Status: StatusSuccess},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "with run that cannot be run",
|
||||
job: ActionRunJob{
|
||||
Run: &ActionRun{Status: StatusRunning, PreExecutionErrorCode: ErrorCodeEventDetectionError},
|
||||
Status: StatusSuccess,
|
||||
},
|
||||
canBeRerun: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
result, err := testCase.job.CanBeRerun(t.Context())
|
||||
|
||||
if testCase.expectedError == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, testCase.expectedError)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.canBeRerun, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
|||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.WorkflowID != "" {
|
||||
|
|
|
|||
|
|
@ -11,14 +11,15 @@ import (
|
|||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/cache"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRunBefore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetConcurrencyGroup(t *testing.T) {
|
||||
run := ActionRun{}
|
||||
run.SetConcurrencyGroup("abc123")
|
||||
|
|
@ -52,127 +53,6 @@ func TestGetWorkflowPath(t *testing.T) {
|
|||
assert.Equal(t, ".some/path/to/workflows/ci.yml", run.WorkflowPath())
|
||||
}
|
||||
|
||||
func TestGetCommitLink(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
|
||||
run := ActionRun{
|
||||
Repo: repo,
|
||||
CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77",
|
||||
}
|
||||
assert.Equal(t, "/sub/user2/repo1/commit/a356d1f1f82945a039cd16d4ce0137bd55284e77", run.CommitLink())
|
||||
}
|
||||
|
||||
func TestIsScheduledRun(t *testing.T) {
|
||||
scheduledRun := ActionRun{
|
||||
CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77",
|
||||
TriggerEvent: "schedule",
|
||||
}
|
||||
pushRun := ActionRun{
|
||||
CommitSHA: "8f9b5c6ab342eb11d7422deecef7195b18058b90",
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
|
||||
assert.True(t, scheduledRun.IsScheduledRun())
|
||||
assert.False(t, pushRun.IsScheduledRun())
|
||||
}
|
||||
|
||||
func TestIsManualRun(t *testing.T) {
|
||||
manualRunRun := ActionRun{
|
||||
CommitSHA: "a356d1f1f82945a039cd16d4ce0137bd55284e77",
|
||||
TriggerEvent: "workflow_dispatch",
|
||||
}
|
||||
pushRun := ActionRun{
|
||||
CommitSHA: "8f9b5c6ab342eb11d7422deecef7195b18058b90",
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
|
||||
assert.True(t, manualRunRun.IsDispatchedRun())
|
||||
assert.False(t, pushRun.IsDispatchedRun())
|
||||
}
|
||||
|
||||
func TestActionRun_IsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
run ActionRun
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
name: "valid run",
|
||||
run: ActionRun{},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
name: "with pre-execution error",
|
||||
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput},
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.isValid, testCase.run.IsValid())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionRun_CanBeRerun(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
run ActionRun
|
||||
canBeRerun bool
|
||||
}{
|
||||
{
|
||||
name: "run with unknown status",
|
||||
run: ActionRun{Status: StatusUnknown},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "successful run",
|
||||
run: ActionRun{Status: StatusSuccess},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "failed run",
|
||||
run: ActionRun{Status: StatusFailure},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "cancelled run",
|
||||
run: ActionRun{Status: StatusCancelled},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "skipped run",
|
||||
run: ActionRun{Status: StatusSkipped},
|
||||
canBeRerun: true,
|
||||
},
|
||||
{
|
||||
name: "waiting run",
|
||||
run: ActionRun{Status: StatusWaiting},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "blocked run",
|
||||
run: ActionRun{Status: StatusBlocked},
|
||||
canBeRerun: false,
|
||||
},
|
||||
{
|
||||
name: "with pre-execution error",
|
||||
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput, Status: StatusSuccess},
|
||||
canBeRerun: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.canBeRerun, testCase.run.CanBeRerun())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoNumOpenActions(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
err := cache.Init()
|
||||
|
|
@ -622,73 +502,3 @@ func TestComputeRunStatus(t *testing.T) {
|
|||
assert.Contains(t, columns, "stopped")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertRunJobs(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
pullRequestPosterID := int64(4)
|
||||
repoID := int64(10)
|
||||
pullRequestID := int64(2)
|
||||
actionRun := &ActionRun{
|
||||
RepoID: repoID,
|
||||
PullRequestID: pullRequestID,
|
||||
PullRequestPosterID: pullRequestPosterID,
|
||||
CommitSHA: "1421f75bc5474c69fdb1dc176bcb96d381f935dd",
|
||||
}
|
||||
|
||||
workflowRaw := []byte(`
|
||||
jobs:
|
||||
build:
|
||||
runs-on: fedora
|
||||
test:
|
||||
runs-on: debian
|
||||
steps: []
|
||||
`)
|
||||
jobs, err := jobparser.Parse(workflowRaw, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, InsertRun(t.Context(), actionRun, jobs))
|
||||
|
||||
insertedJobs, err := db.Find[ActionRunJob](t.Context(), FindRunJobOptions{RunID: actionRun.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, insertedJobs, 2)
|
||||
|
||||
assert.Equal(t, actionRun.ID, insertedJobs[0].RunID)
|
||||
assert.Equal(t, actionRun.RepoID, insertedJobs[0].RepoID)
|
||||
assert.Equal(t, actionRun.OwnerID, insertedJobs[0].OwnerID)
|
||||
assert.Equal(t, actionRun.CommitSHA, insertedJobs[0].CommitSHA)
|
||||
assert.Equal(t, actionRun.IsForkPullRequest, insertedJobs[0].IsForkPullRequest)
|
||||
assert.Equal(t, "build", insertedJobs[0].Name)
|
||||
assert.Equal(t, "build", insertedJobs[0].JobID)
|
||||
assert.Empty(t, insertedJobs[0].Needs)
|
||||
assert.Equal(t, []string{"fedora"}, insertedJobs[0].RunsOn)
|
||||
assert.Equal(t, int64(1), insertedJobs[0].Attempt)
|
||||
assert.Zero(t, insertedJobs[0].Started)
|
||||
assert.Zero(t, insertedJobs[0].Stopped)
|
||||
assert.Zero(t, insertedJobs[0].TaskID)
|
||||
assert.Equal(t, StatusWaiting, insertedJobs[0].Status)
|
||||
|
||||
assert.Equal(t, actionRun.ID, insertedJobs[1].RunID)
|
||||
assert.Equal(t, actionRun.RepoID, insertedJobs[1].RepoID)
|
||||
assert.Equal(t, actionRun.OwnerID, insertedJobs[1].OwnerID)
|
||||
assert.Equal(t, actionRun.CommitSHA, insertedJobs[1].CommitSHA)
|
||||
assert.Equal(t, actionRun.IsForkPullRequest, insertedJobs[1].IsForkPullRequest)
|
||||
assert.Equal(t, "test", insertedJobs[1].Name)
|
||||
assert.Equal(t, "test", insertedJobs[1].JobID)
|
||||
assert.Empty(t, insertedJobs[1].Needs)
|
||||
assert.Equal(t, []string{"debian"}, insertedJobs[1].RunsOn)
|
||||
assert.Equal(t, int64(1), insertedJobs[1].Attempt)
|
||||
assert.Zero(t, insertedJobs[1].Started)
|
||||
assert.Zero(t, insertedJobs[1].Stopped)
|
||||
assert.Zero(t, insertedJobs[1].TaskID)
|
||||
assert.Equal(t, StatusWaiting, insertedJobs[1].Status)
|
||||
}
|
||||
|
||||
func TestActionRunLoadAttributes(t *testing.T) {
|
||||
run := &ActionRun{
|
||||
RepoID: 10,
|
||||
TriggerUserID: 1000,
|
||||
}
|
||||
require.NoError(t, run.LoadAttributes(t.Context()))
|
||||
assert.Equal(t, "ghost", run.TriggerUser.LowerName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,18 +127,6 @@ func (r *ActionRunner) IsOnline() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (r *ActionRunner) IsActive() bool {
|
||||
return r.Status() == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
|
||||
}
|
||||
|
||||
func (r *ActionRunner) IsIdle() bool {
|
||||
return r.Status() == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
|
||||
}
|
||||
|
||||
func (r *ActionRunner) IsOffline() bool {
|
||||
return r.Status() == runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
|
||||
}
|
||||
|
||||
// Editable checks if the runner is editable by the user
|
||||
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
|
||||
if ownerID == 0 && repoID == 0 {
|
||||
|
|
@ -152,7 +140,6 @@ func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
|
|||
|
||||
// LoadAttributes loads the attributes of the runner
|
||||
func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
|
||||
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
|
||||
if r.OwnerID > 0 {
|
||||
var user user_model.User
|
||||
has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
|
||||
|
|
@ -197,12 +184,12 @@ func init() {
|
|||
|
||||
type FindRunnerOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64 // it will be ignored if RepoID is set
|
||||
Sort string
|
||||
Filter string
|
||||
IsOnline optional.Option[bool]
|
||||
WithVisible bool // include all runners that are visible to the repository, owner, or instance
|
||||
RepoID int64
|
||||
OwnerID int64 // it will be ignored if RepoID is set
|
||||
Sort string
|
||||
Filter string
|
||||
IsOnline optional.Option[bool]
|
||||
WithAvailable bool // not only runners belong to, but also runners can be used
|
||||
}
|
||||
|
||||
func (opts FindRunnerOptions) ToConds() builder.Cond {
|
||||
|
|
@ -210,23 +197,21 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
|||
|
||||
if opts.RepoID > 0 {
|
||||
c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
|
||||
if opts.WithVisible {
|
||||
if opts.WithAvailable {
|
||||
c = c.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": opts.RepoID})})
|
||||
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
}
|
||||
cond = cond.And(c)
|
||||
} else if opts.OwnerID != 0 { // OwnerID is ignored if RepoID is set
|
||||
} else if opts.OwnerID > 0 { // OwnerID is ignored if RepoID is set
|
||||
c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
if opts.WithVisible {
|
||||
if opts.WithAvailable {
|
||||
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
}
|
||||
cond = cond.And(c)
|
||||
} else if !opts.WithVisible {
|
||||
cond = cond.And(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
}
|
||||
|
||||
if opts.Filter != "" {
|
||||
cond = cond.And(builder.Like{"name", opts.Filter}).Or(builder.Like{"uuid", opts.Filter})
|
||||
cond = cond.And(builder.Like{"name", opts.Filter})
|
||||
}
|
||||
|
||||
if has, value := opts.IsOnline.Get(); has {
|
||||
|
|
@ -281,31 +266,6 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
|
|||
return &runner, nil
|
||||
}
|
||||
|
||||
// GetVisibleRunnerByID is like GetRunnerByID, but it only finds the runner if it is visible to the given owner or
|
||||
// repository. If it is not, util.ErrNotExist will be returned even if the runner exists.
|
||||
func GetVisibleRunnerByID(ctx context.Context, id, ownerID, repoID int64) (*ActionRunner, error) {
|
||||
query := db.GetEngine(ctx).Where("id=?", id)
|
||||
|
||||
if repoID > 0 {
|
||||
cond := builder.NewCond().And(builder.Eq{"repo_id": repoID})
|
||||
cond = cond.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": repoID})})
|
||||
cond = cond.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
query = query.And(cond)
|
||||
} else if ownerID > 0 { // ownerID is ignored if repoID is set
|
||||
cond := builder.NewCond().And(builder.Eq{"owner_id": ownerID}).Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||
query = query.And(cond)
|
||||
}
|
||||
|
||||
var runner ActionRunner
|
||||
has, err := query.Get(&runner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("runner with ID %d: %w", id, util.ErrNotExist)
|
||||
}
|
||||
return &runner, nil
|
||||
}
|
||||
|
||||
// UpdateRunner updates runner's information.
|
||||
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
for _, runner := range runners {
|
||||
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
|
||||
if runner.OwnerID > 0 && runner.Owner == nil {
|
||||
runner.Owner = users[runner.OwnerID]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
|
|
@ -236,246 +235,3 @@ func TestRunnerEditable(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_GetVisibleRunnerByID(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/actions/TestRunner_GetVisibleRunnerByID")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repository32 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 32, OwnerID: 3})
|
||||
repository1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1, OwnerID: 2})
|
||||
|
||||
runner1 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719931, OwnerID: 3, RepoID: 0}) // Owned by org3
|
||||
runner2 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719932, OwnerID: 2, RepoID: 0}) // Owned by user2
|
||||
runner3 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719933, OwnerID: 0, RepoID: 0})
|
||||
runner4 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719934, OwnerID: 0, RepoID: repository32.ID})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
runner *ActionRunner
|
||||
ownerID int64
|
||||
repoID int64
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Organization runner",
|
||||
runner: runner1,
|
||||
ownerID: 3,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Organization runner visible to admins",
|
||||
runner: runner1,
|
||||
ownerID: 0,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Organization runner invisible to different owner",
|
||||
runner: runner1,
|
||||
ownerID: 2,
|
||||
repoID: 0,
|
||||
expectedError: fmt.Sprintf("runner with ID %d: resource does not exist", runner1.ID),
|
||||
},
|
||||
{
|
||||
name: "Organization runner visible to its repositories",
|
||||
runner: runner1,
|
||||
ownerID: 0,
|
||||
repoID: repository32.ID,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Organization runner invisible to repositories owned by somebody else",
|
||||
runner: runner1,
|
||||
ownerID: 0,
|
||||
repoID: repository1.ID,
|
||||
expectedError: fmt.Sprintf("runner with ID %d: resource does not exist", runner1.ID),
|
||||
},
|
||||
{
|
||||
name: "User runner",
|
||||
runner: runner2,
|
||||
ownerID: 2,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "User runner invisible to different user",
|
||||
runner: runner2,
|
||||
ownerID: 1,
|
||||
repoID: 0,
|
||||
expectedError: fmt.Sprintf("runner with ID %d: resource does not exist", runner2.ID),
|
||||
},
|
||||
{
|
||||
name: "User runner visible to repository owned by user",
|
||||
runner: runner2,
|
||||
ownerID: 0,
|
||||
repoID: repository1.ID,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "User runner invisible to repository owned by different user",
|
||||
runner: runner2,
|
||||
ownerID: 0,
|
||||
repoID: repository32.ID,
|
||||
expectedError: fmt.Sprintf("runner with ID %d: resource does not exist", runner2.ID),
|
||||
},
|
||||
{
|
||||
name: "Global runner",
|
||||
runner: runner3,
|
||||
ownerID: 0,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Global runner is visible to any user",
|
||||
runner: runner3,
|
||||
ownerID: 2,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Global runner is visible to any repository",
|
||||
runner: runner3,
|
||||
ownerID: 0,
|
||||
repoID: repository32.ID,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Repository runner",
|
||||
runner: runner4,
|
||||
ownerID: 0,
|
||||
repoID: repository32.ID,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Repository runner is visible to admins",
|
||||
runner: runner4,
|
||||
ownerID: 0,
|
||||
repoID: 0,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Repository runner is invisible to repository owner",
|
||||
runner: runner4,
|
||||
ownerID: repository32.OwnerID,
|
||||
repoID: 0,
|
||||
expectedError: fmt.Sprintf("runner with ID %d: resource does not exist", runner4.ID),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
_, err := GetVisibleRunnerByID(t.Context(), testCase.runner.ID, testCase.ownerID, testCase.repoID)
|
||||
if testCase.expectedError == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, testCase.expectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_FindRunnerOptionsToConds(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/actions/TestRunner_FindRunnerOptionsToConds")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
runner1 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719931, OwnerID: 3, RepoID: 0}) // Owned by org3
|
||||
runner2 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719932, OwnerID: 2, RepoID: 0}) // Owned by user2
|
||||
runner3 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719933, OwnerID: 0, RepoID: 0})
|
||||
runner4 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719934, OwnerID: 0, RepoID: 32})
|
||||
runner5 := unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: 719935, OwnerID: 0, RepoID: 36})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts FindRunnerOptions
|
||||
expectedRunners RunnerList
|
||||
unexpectedRunners RunnerList
|
||||
}{
|
||||
{
|
||||
name: "Only runners owned by instance",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 0, WithVisible: false},
|
||||
expectedRunners: RunnerList{runner3},
|
||||
unexpectedRunners: RunnerList{runner1, runner2, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "All runners on instance",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 0, WithVisible: true},
|
||||
expectedRunners: RunnerList{runner1, runner2, runner3, runner4, runner5},
|
||||
unexpectedRunners: RunnerList{},
|
||||
},
|
||||
{
|
||||
name: "Only runners owned by organization",
|
||||
opts: FindRunnerOptions{OwnerID: 3, RepoID: 0, WithVisible: false},
|
||||
expectedRunners: RunnerList{runner1},
|
||||
unexpectedRunners: RunnerList{runner2, runner3, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "Runners available to organization",
|
||||
opts: FindRunnerOptions{OwnerID: 3, RepoID: 0, WithVisible: true},
|
||||
expectedRunners: RunnerList{runner1, runner3},
|
||||
unexpectedRunners: RunnerList{runner2, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "Only runners owned by user",
|
||||
opts: FindRunnerOptions{OwnerID: 2, RepoID: 0, WithVisible: false},
|
||||
expectedRunners: RunnerList{runner2},
|
||||
unexpectedRunners: RunnerList{runner1, runner3, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "Runners available to user",
|
||||
opts: FindRunnerOptions{OwnerID: 2, RepoID: 0, WithVisible: true},
|
||||
expectedRunners: RunnerList{runner2, runner3},
|
||||
unexpectedRunners: RunnerList{runner1, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "Only runners owned by organization repository",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 32, WithVisible: false},
|
||||
expectedRunners: RunnerList{runner4},
|
||||
unexpectedRunners: RunnerList{runner1, runner2, runner3, runner5},
|
||||
},
|
||||
{
|
||||
name: "Runners available to organization repository",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 32, WithVisible: true},
|
||||
expectedRunners: RunnerList{runner1, runner3, runner4},
|
||||
unexpectedRunners: RunnerList{runner2, runner5},
|
||||
},
|
||||
{
|
||||
name: "Only runners owned by user repository",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 36, WithVisible: false},
|
||||
expectedRunners: RunnerList{runner5},
|
||||
unexpectedRunners: RunnerList{runner1, runner2, runner3, runner4},
|
||||
},
|
||||
{
|
||||
name: "Runners available to user repository",
|
||||
opts: FindRunnerOptions{OwnerID: 0, RepoID: 36, WithVisible: true},
|
||||
expectedRunners: RunnerList{runner2, runner3, runner5},
|
||||
unexpectedRunners: RunnerList{runner1, runner4},
|
||||
},
|
||||
{
|
||||
name: "Runners with partially matching name",
|
||||
opts: FindRunnerOptions{Filter: "er-3"},
|
||||
expectedRunners: RunnerList{runner3},
|
||||
unexpectedRunners: RunnerList{runner1, runner2, runner4, runner5},
|
||||
},
|
||||
{
|
||||
name: "Runners with partially matching UUID",
|
||||
opts: FindRunnerOptions{Filter: "21f75233798b"},
|
||||
expectedRunners: RunnerList{runner4},
|
||||
unexpectedRunners: RunnerList{runner1, runner2, runner3, runner5},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
runners, err := db.Find[ActionRunner](t.Context(), testCase.opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, expectedRunner := range testCase.expectedRunners {
|
||||
assert.Contains(t, runners, expectedRunner)
|
||||
}
|
||||
for _, unexpectedRunner := range testCase.unexpectedRunners {
|
||||
assert.NotContains(t, runners, unexpectedRunner)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
|
|
@ -32,9 +31,9 @@ import (
|
|||
type ActionRunnerToken struct {
|
||||
ID int64
|
||||
Token string `xorm:"UNIQUE"`
|
||||
OwnerID optional.Option[int64] `xorm:"index REFERENCES(user, id)"`
|
||||
OwnerID int64 `xorm:"index REFERENCES(user, id)"`
|
||||
Owner *user_model.User `xorm:"-"`
|
||||
RepoID optional.Option[int64] `xorm:"index REFERENCES(repository, id)"`
|
||||
RepoID int64 `xorm:"index REFERENCES(repository, id)"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
IsActive bool // true means it can be used
|
||||
|
||||
|
|
@ -72,11 +71,21 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
|
|||
|
||||
// NewRunnerToken creates a new active runner token and invalidate all old tokens
|
||||
// ownerID will be ignored and treated as 0 if repoID is non-zero.
|
||||
func NewRunnerToken(ctx context.Context, ownerID, repoID optional.Option[int64]) (*ActionRunnerToken, error) {
|
||||
if ownerID.Has() && repoID.Has() {
|
||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
// It's trying to create a runner token 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 = optional.None[int64]()
|
||||
ownerID = 0
|
||||
}
|
||||
|
||||
// To ensure that NULL values are used for the unused columns, rather than attempting to insert 0 values which will
|
||||
// cause FK violation, manage the list of columns that xorm will insert.
|
||||
cols := []string{"is_active", "token"}
|
||||
if ownerID != 0 {
|
||||
cols = append(cols, "owner_id")
|
||||
}
|
||||
if repoID != 0 {
|
||||
cols = append(cols, "repo_id")
|
||||
}
|
||||
|
||||
token := util.CryptoRandomString(util.RandomStringHigh)
|
||||
|
|
@ -94,33 +103,33 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID optional.Option[int64])
|
|||
return err
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Insert(runnerToken)
|
||||
_, err := db.GetEngine(ctx).Cols(cols...).Insert(runnerToken)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func runnerTokenCond(ownerID, repoID optional.Option[int64]) builder.Cond {
|
||||
func runnerTokenCond(ownerID, repoID int64) builder.Cond {
|
||||
var condOwnerID builder.Cond
|
||||
if has, value := ownerID.Get(); !has {
|
||||
if ownerID == 0 {
|
||||
condOwnerID = builder.IsNull{"owner_id"}
|
||||
} else {
|
||||
condOwnerID = builder.Eq{"owner_id": value}
|
||||
condOwnerID = builder.Eq{"owner_id": ownerID}
|
||||
}
|
||||
var condRepoID builder.Cond
|
||||
if has, value := repoID.Get(); !has {
|
||||
if repoID == 0 {
|
||||
condRepoID = builder.IsNull{"repo_id"}
|
||||
} else {
|
||||
condRepoID = builder.Eq{"repo_id": value}
|
||||
condRepoID = builder.Eq{"repo_id": repoID}
|
||||
}
|
||||
return builder.And(condOwnerID, condRepoID)
|
||||
}
|
||||
|
||||
// GetLatestRunnerToken returns the latest runner token
|
||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID optional.Option[int64]) (*ActionRunnerToken, error) {
|
||||
if ownerID.Has() && repoID.Has() {
|
||||
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
|
||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
// It's trying to get a runner token 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 = optional.None[int64]()
|
||||
ownerID = 0
|
||||
}
|
||||
|
||||
var runnerToken ActionRunnerToken
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -17,16 +16,16 @@ import (
|
|||
func TestGetLatestRunnerToken(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3})
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, optional.Some[int64](1), optional.None[int64]())
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedToken, token)
|
||||
}
|
||||
|
||||
func TestNewRunnerToken(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
token, err := NewRunnerToken(db.DefaultContext, optional.Some[int64](1), optional.None[int64]())
|
||||
token, err := NewRunnerToken(db.DefaultContext, 1, 0)
|
||||
require.NoError(t, err)
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, optional.Some[int64](1), optional.None[int64]())
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedToken, token)
|
||||
}
|
||||
|
|
@ -36,7 +35,7 @@ func TestUpdateRunnerToken(t *testing.T) {
|
|||
token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3})
|
||||
token.IsActive = true
|
||||
require.NoError(t, UpdateRunnerToken(db.DefaultContext, token, "is_active"))
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, optional.Some[int64](1), optional.None[int64]())
|
||||
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedToken, token)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
|
@ -20,7 +21,7 @@ import (
|
|||
type ActionSchedule struct {
|
||||
ID int64
|
||||
Title string
|
||||
Specs []*ActionScheduleSpec `xorm:"-"`
|
||||
Specs []string
|
||||
RepoID int64 `xorm:"index"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
OwnerID int64 `xorm:"index"`
|
||||
|
|
@ -72,12 +73,25 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Loop through each schedule spec and create a new spec row
|
||||
now := time.Now()
|
||||
|
||||
for _, spec := range row.Specs {
|
||||
spec.ScheduleID = row.ID
|
||||
spec.RepoID = row.RepoID
|
||||
specRow := &ActionScheduleSpec{
|
||||
RepoID: row.RepoID,
|
||||
ScheduleID: row.ID,
|
||||
Spec: spec,
|
||||
}
|
||||
// Parse the spec and check for errors
|
||||
schedule, err := specRow.Parse()
|
||||
if err != nil {
|
||||
continue // skip to the next spec if there's an error
|
||||
}
|
||||
|
||||
specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix())
|
||||
|
||||
// Insert the new schedule spec row
|
||||
if err = db.Insert(ctx, spec); err != nil {
|
||||
if err = db.Insert(ctx, specRow); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +130,7 @@ func (opts FindScheduleOptions) ToConds() builder.Cond {
|
|||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/gdgvda/cron"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
// ActionScheduleSpec represents a schedule spec of a workflow file
|
||||
|
|
@ -28,58 +27,36 @@ type ActionScheduleSpec struct {
|
|||
// started or this entry's schedule is unsatisfiable
|
||||
Next timeutil.TimeStamp `xorm:"index"`
|
||||
// Prev is the last time this job was run, or the zero time if never.
|
||||
Prev timeutil.TimeStamp
|
||||
Spec string
|
||||
TimeZone optional.Option[string]
|
||||
Prev timeutil.TimeStamp
|
||||
Spec string
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func NewActionScheduleSpec(cron string, tz optional.Option[string], referenceTime time.Time) (*ActionScheduleSpec, error) {
|
||||
spec := &ActionScheduleSpec{
|
||||
Spec: cron,
|
||||
TimeZone: tz,
|
||||
}
|
||||
cronSchedule, err := spec.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec.Next = timeutil.TimeStamp(cronSchedule.Next(referenceTime).Unix())
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// Parse parses the spec and returns a cron.Schedule
|
||||
// Unlike the default cron parser, Parse uses UTC timezone as the default if none is specified.
|
||||
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
|
||||
parser, err := cron.NewDefaultParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
schedule, err := parser.Parse(s.Spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If `timezone` is not defined in the workflow, but the spec includes a timezone, use it.
|
||||
if !s.TimeZone.Has() && (strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=")) {
|
||||
// If the spec has specified a timezone, use it
|
||||
if strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=") {
|
||||
return schedule, nil
|
||||
}
|
||||
|
||||
var location *time.Location
|
||||
if present, tz := s.TimeZone.Get(); present {
|
||||
location, err = time.LoadLocation(tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// UTC is the default time zone.
|
||||
location = time.UTC
|
||||
specSchedule, ok := schedule.(*cron.SpecSchedule)
|
||||
// If it's not a spec schedule, like "@every 5m", timezone is not relevant
|
||||
if !ok {
|
||||
return schedule, nil
|
||||
}
|
||||
|
||||
return schedule.WithLocation(location), nil
|
||||
// Set the timezone to UTC
|
||||
specSchedule.Location = time.UTC
|
||||
return specSchedule, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -7,50 +7,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"forgejo.org/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionScheduleSpec_NewActionScheduleSpec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
refTime time.Time
|
||||
cronPattern string
|
||||
timeZone string
|
||||
want string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "without timezone",
|
||||
refTime: time.Date(2026, 4, 6, 11, 56, 0, 0, time.UTC),
|
||||
cronPattern: "58 14 * * *",
|
||||
want: "2026-04-06T14:58:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "with separate timezone",
|
||||
refTime: time.Date(2026, 4, 6, 11, 56, 0, 0, time.UTC),
|
||||
cronPattern: "58 14 * * *",
|
||||
timeZone: "Europe/Tallinn", // +03 (EEST)
|
||||
want: "2026-04-06T11:58:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
s, err := NewActionScheduleSpec(test.cronPattern, optional.FromNonDefault(test.timeZone), test.refTime)
|
||||
test.wantErr(t, err)
|
||||
|
||||
if err == nil {
|
||||
assert.Equal(t, test.want, s.Next.AsTime().UTC().Format(time.RFC3339))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionScheduleSpec_Parse(t *testing.T) {
|
||||
// Mock the local timezone is not UTC
|
||||
local := time.Local
|
||||
|
|
@ -61,105 +21,50 @@ func TestActionScheduleSpec_Parse(t *testing.T) {
|
|||
}()
|
||||
time.Local = tz
|
||||
|
||||
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
refTime time.Time
|
||||
spec string
|
||||
timeZone string
|
||||
want string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
name string
|
||||
spec string
|
||||
want string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "regular",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
spec: "0 10 * * *",
|
||||
want: "2024-07-31T10:00:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
spec: "0 10 * *",
|
||||
want: "",
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "with TZ in cron schedule",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
name: "with timezone",
|
||||
spec: "TZ=America/New_York 0 10 * * *",
|
||||
want: "2024-07-31T14:00:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "with CRON_TZ in cron schedule",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
spec: "CRON_TZ=America/New_York 0 10 * * *",
|
||||
want: "2024-07-31T14:00:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "with separate time zone",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
spec: "0 10 * * *",
|
||||
timeZone: "America/New_York",
|
||||
want: "2024-07-31T14:00:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "separate time zone takes precedence over inlined time zone",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
spec: "CRON_TZ=Europe/Berlin 0 10 * * *",
|
||||
timeZone: "America/New_York",
|
||||
want: "2024-07-31T14:00:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "time zone irrelevant",
|
||||
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||
name: "timezone irrelevant",
|
||||
spec: "@every 5m",
|
||||
want: "2024-07-31T07:52:55Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
// The various cron implementations handle the DST jump forwards differently. The most popular approaches
|
||||
// are (a) scheduling all jobs at 3 o'clock that were supposed to run between 2 and 3 o'clock, or (b)
|
||||
// skipping the execution on that day because any time between 2 and 3 o'clock never happened. Forgejo uses
|
||||
// option B because the code it inherited already did that and was exposed to users.
|
||||
name: "skips execution during DST jump forwards",
|
||||
refTime: time.Date(2025, 3, 30, 0, 55, 0, 0, time.UTC), // 01:55 local time
|
||||
spec: "10 2 * * *", // The clock jumps at 2 o'clock to 3 o'clock.
|
||||
timeZone: "Europe/Berlin",
|
||||
want: "2025-03-31T00:10:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "executes a first time before DST jump backwards",
|
||||
refTime: time.Date(2025, 10, 26, 0, 5, 0, 0, time.UTC), // 02:05 local time
|
||||
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
|
||||
timeZone: "Europe/Berlin",
|
||||
want: "2025-10-26T00:10:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "executes a second time after DST jump backwards",
|
||||
refTime: time.Date(2025, 10, 26, 1, 5, 0, 0, time.UTC), // 02:05 local time
|
||||
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
|
||||
timeZone: "Europe/Berlin",
|
||||
want: "2025-10-26T01:10:00Z",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &ActionScheduleSpec{
|
||||
Spec: tt.spec,
|
||||
TimeZone: optional.FromNonDefault(tt.timeZone),
|
||||
Spec: tt.spec,
|
||||
}
|
||||
got, err := s.Parse()
|
||||
tt.wantErr(t, err)
|
||||
|
||||
if err == nil {
|
||||
assert.Equal(t, tt.want, got.Next(tt.refTime).UTC().Format(time.RFC3339))
|
||||
assert.Equal(t, tt.want, got.Next(now).UTC().Format(time.RFC3339))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/models/user"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/webhook"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScheduleCreateScheduleTask(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 2})
|
||||
repo62 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 62, Name: "test_workflows", OwnerID: user2.ID})
|
||||
|
||||
content := `
|
||||
on:
|
||||
push:
|
||||
schedule:
|
||||
- cron: "2 13 * * *"
|
||||
- cron: "03 13 * * *"
|
||||
timezone: Europe/Paris
|
||||
jobs:
|
||||
test:
|
||||
runs-on: debian
|
||||
steps:
|
||||
- run: |
|
||||
echo "OK"
|
||||
`
|
||||
|
||||
referenceTime := time.Date(2026, 3, 27, 17, 41, 21, 0, time.UTC)
|
||||
|
||||
specWithoutTZ, err := NewActionScheduleSpec("2 13 * * *", optional.None[string](), referenceTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
specWithTZ, err := NewActionScheduleSpec("3 13 * * *", optional.Some("Europe/Paris"), referenceTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
schedule := &ActionSchedule{
|
||||
Title: ".forgejo/workflows/test.yaml",
|
||||
Specs: []*ActionScheduleSpec{specWithoutTZ, specWithTZ},
|
||||
RepoID: repo62.ID,
|
||||
OwnerID: user2.ID,
|
||||
WorkflowID: "test.yaml",
|
||||
WorkflowDirectory: ".forgejo/workflows",
|
||||
TriggerUserID: -2,
|
||||
Ref: "main",
|
||||
CommitSHA: "6af834a5bc97c1a337eb3a21d26903c5cdceca0c",
|
||||
Event: webhook.HookEventPush,
|
||||
EventPayload: "{\"action\":\"schedule\"}",
|
||||
Content: []byte(content),
|
||||
}
|
||||
|
||||
err = CreateScheduleTask(t.Context(), []*ActionSchedule{schedule})
|
||||
require.NoError(t, err)
|
||||
|
||||
schedules, err := db.Find[ActionSchedule](t.Context(), FindScheduleOptions{OwnerID: user2.ID, RepoID: repo62.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, schedules, 1)
|
||||
|
||||
assert.NotZero(t, schedules[0].ID)
|
||||
assert.Equal(t, ".forgejo/workflows/test.yaml", schedules[0].Title)
|
||||
assert.Equal(t, "test.yaml", schedules[0].WorkflowID)
|
||||
assert.Equal(t, ".forgejo/workflows", schedules[0].WorkflowDirectory)
|
||||
assert.Equal(t, int64(-2), schedules[0].TriggerUserID)
|
||||
assert.Equal(t, "main", schedules[0].Ref)
|
||||
assert.Equal(t, "6af834a5bc97c1a337eb3a21d26903c5cdceca0c", schedules[0].CommitSHA)
|
||||
assert.Equal(t, webhook.HookEventPush, schedules[0].Event)
|
||||
assert.JSONEq(t, "{\"action\":\"schedule\"}", schedules[0].EventPayload)
|
||||
assert.Equal(t, []byte(content), schedules[0].Content)
|
||||
|
||||
specs, total, err := FindSpecs(t.Context(), FindSpecOptions{RepoID: repo62.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(2), total)
|
||||
|
||||
assert.NotZero(t, specs[0].ID)
|
||||
assert.Equal(t, schedules[0].ID, specs[0].ScheduleID)
|
||||
assert.Equal(t, timeutil.TimeStamp(1774699380), specs[0].Next)
|
||||
assert.Equal(t, "3 13 * * *", specs[0].Spec)
|
||||
assert.Equal(t, optional.Some("Europe/Paris"), specs[0].TimeZone)
|
||||
assert.Zero(t, specs[0].Prev)
|
||||
|
||||
assert.NotZero(t, specs[1].ID)
|
||||
assert.Equal(t, schedules[0].ID, specs[1].ScheduleID)
|
||||
assert.Equal(t, timeutil.TimeStamp(1774702920), specs[1].Next)
|
||||
assert.Equal(t, "2 13 * * *", specs[1].Spec)
|
||||
assert.Equal(t, optional.None[string](), specs[1].TimeZone)
|
||||
assert.Zero(t, specs[1].Prev)
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"forgejo.org/modules/translation"
|
||||
|
||||
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
|
||||
|
|
@ -109,7 +107,12 @@ func (s Status) IsBlocked() bool {
|
|||
|
||||
// In returns whether s is one of the given statuses
|
||||
func (s Status) In(statuses ...Status) bool {
|
||||
return slices.Contains(statuses, s)
|
||||
for _, v := range statuses {
|
||||
if s == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s Status) AsResult() runnerv1.Result {
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ func GetAvailableJobsForRunner(e db.Engine, runner *ActionRunner) ([]*ActionRunJ
|
|||
return jobs, nil
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey, handle *string) (*ActionTask, bool, error) {
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey *string) (*ActionTask, bool, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
@ -364,9 +364,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey,
|
|||
// TODO: a more efficient way to filter labels
|
||||
var job *ActionRunJob
|
||||
log.Trace("runner labels: %v", runner.AgentLabels)
|
||||
for _, j := range jobs {
|
||||
if j.IsRequestedByRunner(handle) && j.ItRunsOn(runner.AgentLabels) {
|
||||
job = j
|
||||
for _, v := range jobs {
|
||||
if v.ItRunsOn(runner.AgentLabels) {
|
||||
job = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -378,6 +378,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey,
|
|||
}
|
||||
|
||||
now := timeutil.TimeStampNow()
|
||||
job.Attempt++
|
||||
job.Started = now
|
||||
job.Status = StatusRunning
|
||||
|
||||
|
|
@ -451,7 +452,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey,
|
|||
}
|
||||
|
||||
// Placeholder tasks are created when the status/content of an [ActionRunJob] is resolved by Forgejo without dispatch to
|
||||
// a runner, specifically in the case of a workflow call's outer job.
|
||||
// a runner, specifically in the case of a workflow call's outer job. It is the responsibility of the caller to
|
||||
// increment the job's Attempt field before invoking this method, and to update that field in the database, so that
|
||||
// reruns can function for placeholder tasks and provide updated outputs.
|
||||
func CreatePlaceholderTask(ctx context.Context, job *ActionRunJob, outputs map[string]string) (*ActionTask, error) {
|
||||
actionTask := &ActionTask{
|
||||
JobID: job.ID,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
|
@ -65,13 +64,13 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
|
|||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.CommitSHA != "" {
|
||||
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
|
||||
}
|
||||
if len(opts.Status) > 0 {
|
||||
if opts.Status != nil {
|
||||
cond = cond.And(builder.In("status", opts.Status))
|
||||
}
|
||||
if opts.UpdatedBefore > 0 {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,12 @@ func (at ActionType) String() string {
|
|||
}
|
||||
|
||||
func (at ActionType) InActions(actions ...string) bool {
|
||||
return slices.Contains(actions, at.String())
|
||||
for _, action := range actions {
|
||||
if action == at.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Action represents user operation type and other information to
|
||||
|
|
@ -812,36 +817,3 @@ func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
|
|||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (a *Action) IsActionPrivate(ctx context.Context) (bool, error) {
|
||||
if a.IsPrivate {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
a.loadRepo(ctx)
|
||||
if a.Repo == nil {
|
||||
return true, repo_model.ErrRepoNotExist{}
|
||||
}
|
||||
|
||||
repo := a.Repo
|
||||
err := repo.LoadOwner(ctx)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if repo.IsPrivate || repo.Owner.KeepActivityPrivate || repo.Owner.Visibility != structs.VisibleTypePublic {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
a.LoadActUser(ctx)
|
||||
if a.ActUser == nil {
|
||||
return true, user_model.ErrUserNotExist{}
|
||||
}
|
||||
|
||||
user := a.ActUser
|
||||
if user.KeepActivityPrivate || user.Visibility != structs.VisibleTypePublic {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,28 +371,3 @@ func TestGetIssueInfos(t *testing.T) {
|
|||
assert.Equal(t, test.field3, issueInfos[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrivate(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/activities/fixtures/TestIsPrivate")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
tt := []struct {
|
||||
activityID int64
|
||||
private bool
|
||||
}{
|
||||
{1, true}, // private repo
|
||||
{3, false}, // public activities, public repo
|
||||
{11, true}, // private activities
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
ctx := t.Context()
|
||||
action, err := activities_model.GetActivityByID(ctx, test.activityID)
|
||||
require.NoError(t, err)
|
||||
|
||||
private, err := action.IsActionPrivate(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.private, private, "action ID: %d", test.activityID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package activities
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ErrActivityPrivate struct {
|
||||
id int64
|
||||
}
|
||||
|
||||
func (err ErrActivityPrivate) Error() string {
|
||||
return fmt.Sprintf("Activity with id %d is private", err.id)
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeed
|
|||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
|
||||
count, err := sess.Desc("`federated_user_activity`.created").FindAndCount(&actions)
|
||||
count, err := sess.FindAndCount(&actions)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
- id: 11
|
||||
user_id: 44
|
||||
op_type: 1 # create repo
|
||||
act_user_id: 44 # private user activities
|
||||
repo_id: 60 # public
|
||||
is_private: false
|
||||
created_unix: 1680454039
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
- id: 44
|
||||
lower_name: user44
|
||||
name: user44
|
||||
full_name: user44
|
||||
email: user44@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: user44
|
||||
type: 0
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: true
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: true
|
||||
prohibit_login: false
|
||||
avatar: ""
|
||||
avatar_email: user44@example.com
|
||||
use_custom_avatar: true
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: true
|
||||
created_unix: 1672578380
|
||||
|
|
@ -210,9 +210,34 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
|
|||
}
|
||||
|
||||
repoIDs := nl.getPendingRepoIDs()
|
||||
repos, err := db.GetByIDs(ctx, "id", repoIDs, &repo_model.Repository{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||
left := len(repoIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", repoIDs[:limit]).
|
||||
Rows(new(repo_model.Repository))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var repo repo_model.Repository
|
||||
err = rows.Scan(&repo)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repos[repo.ID] = &repo
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
repoIDs = repoIDs[limit:]
|
||||
}
|
||||
|
||||
failed := []int{}
|
||||
|
|
@ -259,9 +284,34 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
|
|||
}
|
||||
|
||||
issueIDs := nl.getPendingIssueIDs()
|
||||
issues, err := db.GetByIDs(ctx, "id", issueIDs, &issues_model.Issue{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
|
||||
left := len(issueIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", issueIDs[:limit]).
|
||||
Rows(new(issues_model.Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var issue issues_model.Issue
|
||||
err = rows.Scan(&issue)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issues[issue.ID] = &issue
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
issueIDs = issueIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
|
|
@ -329,9 +379,34 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
|
|||
}
|
||||
|
||||
userIDs := nl.getUserIDs()
|
||||
users, err := db.GetByIDs(ctx, "id", userIDs, &user_model.User{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
left := len(userIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", userIDs[:limit]).
|
||||
Rows(new(user_model.User))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var user user_model.User
|
||||
err = rows.Scan(&user)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users[user.ID] = &user
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
userIDs = userIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
|
|
@ -355,9 +430,34 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
|||
}
|
||||
|
||||
commentIDs := nl.getPendingCommentIDs()
|
||||
comments, err := db.GetByIDs(ctx, "id", commentIDs, &issues_model.Comment{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||
left := len(commentIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", commentIDs[:limit]).
|
||||
Rows(new(issues_model.Comment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var comment issues_model.Comment
|
||||
err = rows.Scan(&comment)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments[comment.ID] = &comment
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
commentIDs = commentIDs[limit:]
|
||||
}
|
||||
|
||||
failures := []int{}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,10 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository
|
|||
return v[i].Commits > v[j].Commits
|
||||
})
|
||||
|
||||
cnt := min(count, len(v))
|
||||
cnt := count
|
||||
if cnt > len(v) {
|
||||
cnt = len(v)
|
||||
}
|
||||
|
||||
return v[:cnt], nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (opts FindGPGKeyOptions) ToConds() builder.Cond {
|
|||
cond = cond.And(builder.Eq{"primary_key_id": ""})
|
||||
}
|
||||
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.KeyID != "" {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -457,7 +458,7 @@ epiDVQ==
|
|||
func TestTryGetKeyIDFromSignature(t *testing.T) {
|
||||
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
|
||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||
IssuerKeyId: new(uint64(0x38D1A3EADDBEA9C)),
|
||||
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
|
||||
}))
|
||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||
|
|
|
|||
|
|
@ -20,32 +20,14 @@ func HandleCompositeErrorReason(handler llu.Handler, fset *token.FileSet, n *ast
|
|||
}
|
||||
|
||||
// fields are normally named
|
||||
var reason ast.Expr
|
||||
verified := false
|
||||
for _, i := range n.Elts {
|
||||
if kve, ok := i.(*ast.KeyValueExpr); ok {
|
||||
ident, ok = kve.Key.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch ident.Name {
|
||||
case "Reason":
|
||||
reason = kve.Value
|
||||
case "Verified":
|
||||
if valueIdent, ok := kve.Value.(*ast.Ident); ok {
|
||||
switch valueIdent.Name {
|
||||
case "true":
|
||||
verified = true
|
||||
case "false":
|
||||
verified = false
|
||||
}
|
||||
}
|
||||
if ok && ident.Name == "Reason" {
|
||||
handler.HandleGoTrArgument(fset, kve.Value, "")
|
||||
}
|
||||
} else {
|
||||
handler.OnWarning(fset, i.Pos(), "unable to parse ObjectVerification field assignment")
|
||||
}
|
||||
}
|
||||
if !verified && reason != nil {
|
||||
handler.HandleGoTrArgument(fset, reason, "")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ type FindPublicKeyOptions struct {
|
|||
|
||||
func (opts FindPublicKeyOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.OwnerID != 0 {
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.Fingerprint != "" {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
- integ_id: 1
|
||||
repo_id: 1
|
||||
created_unix: 1772158384
|
||||
- integ_id: 1
|
||||
repo_id: 2
|
||||
created_unix: 1772158384
|
||||
- integ_id: 1
|
||||
repo_id: 3
|
||||
created_unix: 1772158384
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
- id: 1
|
||||
user_id: 2
|
||||
scope: all
|
||||
resource_all_repos: false
|
||||
issuer: https://example.org/
|
||||
audience: https://forgejo.example.org/-/user/integration/abcdef123
|
||||
claim_rules: "{}"
|
||||
created_unix: 1777153359
|
||||
updated_unix: 1777153359
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
- token_id: 3
|
||||
repo_id: 1
|
||||
created_unix: 1772158384
|
||||
- token_id: 3
|
||||
repo_id: 2
|
||||
created_unix: 1772158384
|
||||
- token_id: 3
|
||||
repo_id: 3
|
||||
created_unix: 1772158384
|
||||
|
||||
- token_id: 2
|
||||
repo_id: 1
|
||||
created_unix: 1772158384
|
||||
# (no repo 2 for token 2)
|
||||
- token_id: 2
|
||||
repo_id: 3
|
||||
created_unix: 1772158384
|
||||
|
|
@ -224,23 +224,15 @@ func (opts ListAccessTokensOptions) ToOrders() string {
|
|||
|
||||
// DeleteAccessTokenByID deletes access token by given ID.
|
||||
func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := db.DeleteBeans(ctx,
|
||||
&AccessTokenResourceRepo{TokenID: id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("DeleteBeans: %w", err)
|
||||
}
|
||||
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
|
||||
UID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if cnt != 1 {
|
||||
return ErrAccessTokenNotExist{}
|
||||
}
|
||||
return nil
|
||||
cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{
|
||||
UID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if cnt != 1 {
|
||||
return ErrAccessTokenNotExist{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegenerateAccessTokenByID regenerates access token by given ID.
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@ func init() {
|
|||
db.RegisterModel(new(AccessTokenResourceRepo))
|
||||
}
|
||||
|
||||
func (atr *AccessTokenResourceRepo) GetTargetRepoID() int64 {
|
||||
return atr.RepoID
|
||||
}
|
||||
|
||||
func GetRepositoriesAccessibleWithToken(ctx context.Context, accessTokenID int64) ([]*AccessTokenResourceRepo, error) {
|
||||
var resources []*AccessTokenResourceRepo
|
||||
err := db.GetEngine(ctx).
|
||||
|
|
@ -38,35 +34,3 @@ func GetRepositoriesAccessibleWithToken(ctx context.Context, accessTokenID int64
|
|||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func GetRepositoriesAccessibleWithTokens(ctx context.Context, accessTokens []*AccessToken) (map[int64][]*AccessTokenResourceRepo, error) {
|
||||
accessTokenIDs := make([]int64, len(accessTokens))
|
||||
for i, at := range accessTokens {
|
||||
accessTokenIDs[i] = at.ID
|
||||
}
|
||||
|
||||
var resources []*AccessTokenResourceRepo
|
||||
err := db.GetEngine(ctx).
|
||||
In("token_id", accessTokenIDs).
|
||||
Find(&resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retval := make(map[int64][]*AccessTokenResourceRepo)
|
||||
for _, resource := range resources {
|
||||
retval[resource.TokenID] = append(retval[resource.TokenID], resource)
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
func InsertAccessTokenResourceRepos(ctx context.Context, accessTokenID int64, resources []*AccessTokenResourceRepo) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for _, resourceRepo := range resources {
|
||||
resourceRepo.TokenID = accessTokenID
|
||||
if err := db.Insert(ctx, resourceRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -18,13 +19,13 @@ func TestGetRepositoriesAccessibleWithToken(t *testing.T) {
|
|||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("No Resources", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithToken(t.Context(), 999)
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithToken(db.DefaultContext, 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resources)
|
||||
})
|
||||
|
||||
t.Run("Has Resources", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithToken(t.Context(), 3)
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithToken(db.DefaultContext, 3)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resources, 3)
|
||||
|
||||
|
|
@ -38,86 +39,3 @@ func TestGetRepositoriesAccessibleWithToken(t *testing.T) {
|
|||
assert.Contains(t, repoIDs, int64(3))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRepositoriesAccessibleWithTokens(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/auth/TestGetRepositoriesAccessibleWithTokens")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
token1 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 1})
|
||||
token2 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
token3 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 3})
|
||||
|
||||
t.Run("No Tokens", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithTokens(t.Context(), []*auth_model.AccessToken{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resources)
|
||||
})
|
||||
|
||||
t.Run("Multiple Access Tokens", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithTokens(t.Context(), []*auth_model.AccessToken{token1, token2, token3})
|
||||
require.NoError(t, err)
|
||||
|
||||
repos1, ok := resources[token1.ID]
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, repos1)
|
||||
|
||||
repos2, ok := resources[token2.ID]
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, repos2, 2)
|
||||
|
||||
repos3, ok := resources[token3.ID]
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, repos3, 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertAccessTokenResourceRepos(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
token1 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 1})
|
||||
token2 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2})
|
||||
token3 := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 3})
|
||||
|
||||
t.Run("blank insert", func(t *testing.T) {
|
||||
err := auth_model.InsertAccessTokenResourceRepos(t.Context(), token1.ID, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("multiple insert", func(t *testing.T) {
|
||||
resRepo1 := &auth_model.AccessTokenResourceRepo{
|
||||
TokenID: token2.ID,
|
||||
RepoID: 1,
|
||||
}
|
||||
resRepo3 := &auth_model.AccessTokenResourceRepo{
|
||||
TokenID: token2.ID,
|
||||
RepoID: 3,
|
||||
}
|
||||
err := auth_model.InsertAccessTokenResourceRepos(t.Context(), token2.ID,
|
||||
[]*auth_model.AccessTokenResourceRepo{resRepo1, resRepo3})
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &auth_model.AccessTokenResourceRepo{TokenID: token2.ID}, 2)
|
||||
})
|
||||
|
||||
t.Run("in tx", func(t *testing.T) {
|
||||
// Pre-condition: count is 0.
|
||||
unittest.AssertCount(t, &auth_model.AccessTokenResourceRepo{TokenID: token3.ID}, 0)
|
||||
|
||||
// Verify that InsertAccessTokenResourceRepos performs inserts in a TX by having a second one with an invalid
|
||||
// RepoID, causing a foreign key violation
|
||||
resRepo1 := &auth_model.AccessTokenResourceRepo{
|
||||
TokenID: token3.ID,
|
||||
RepoID: 1,
|
||||
}
|
||||
resRepo3 := &auth_model.AccessTokenResourceRepo{
|
||||
TokenID: token3.ID,
|
||||
RepoID: 30000, // invalid
|
||||
}
|
||||
err := auth_model.InsertAccessTokenResourceRepos(t.Context(), token3.ID,
|
||||
[]*auth_model.AccessTokenResourceRepo{resRepo1, resRepo3})
|
||||
require.ErrorContains(t, err, "foreign key")
|
||||
|
||||
// Count remains 0; the first record was not inserted.
|
||||
unittest.AssertCount(t, &auth_model.AccessTokenResourceRepo{TokenID: token3.ID}, 0)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ package auth
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/perm"
|
||||
|
|
@ -205,7 +204,12 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
|
|||
|
||||
// ContainsCategory checks if a list of categories contains a specific category
|
||||
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
|
||||
return slices.Contains(categories, category)
|
||||
for _, c := range categories {
|
||||
if c == category {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetScopeLevelFromAccessMode converts permission access mode to scope level
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// An Authorized Integration allow users to define external systems which can generate JSON Web Tokens (JWTs) that
|
||||
// Forgejo will trust in order to perform API access on behalf of a user defined by the UserID field.
|
||||
//
|
||||
// When a JWT is received by Forgejo, the issuer (iss) and audience (aud) claims are used to lookup an authorized
|
||||
// integration with an exact match. Together these fields serve as a unique key for the authorized issuer. Duplicates
|
||||
// cannot be permitted because we would not know which user to authenticate the JWT as.
|
||||
type AuthorizedIntegration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
||||
UserID int64 `xorm:"NOT NULL REFERENCES(user, id)"`
|
||||
Scope AccessTokenScope `xorm:"NOT NULL"`
|
||||
ResourceAllRepos bool `xorm:"NOT NULL"` // flag for whether AuthorizedIntegrationResourceRepo instances will limit the resources this access token can access (false) or won't limit them (true).
|
||||
|
||||
Name string // short name for lists of authorized integrations
|
||||
Description string `xorm:"LONGTEXT"` // long description, optional to document relevant details of the integration
|
||||
|
||||
// Exact-match `iss` claim of the JWT
|
||||
Issuer string `xorm:"NOT NULL UNIQUE(s)"`
|
||||
// Exact-match `aud` claim of the JWT
|
||||
Audience string `xorm:"NOT NULL UNIQUE(s)"`
|
||||
ClaimRules *ClaimRules `xorm:"NOT NULL JSON"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"NOT NULL created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"NOT NULL updated"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(AuthorizedIntegration))
|
||||
}
|
||||
|
||||
// An [AuthorizedIntegration] can validate the claims in a JWT against a set of rules defined by this structure.
|
||||
//
|
||||
// JWTs can contain any number of claims, which are represented as a JSON object. A small number of common claims are
|
||||
// described in RFC7519 (sec 4.1) which defines JWTs, but most claims are entirely arbitrarily defined by the JWT
|
||||
// issuer.
|
||||
//
|
||||
// For example, eg. a claim may be {"sub": "repo:coolguy/forgejo-runner-testrepo:pull_request"} indicating that an OIDC
|
||||
// token was received from an Actions execution in a specific repo on a specific event.
|
||||
//
|
||||
// Validating the claims from a JWT issuer is a critical part of creating a secure [AuthorizedIssuer]. For example,
|
||||
// assume that we receive a JWT from a public hosting platform like Codeberg. We will validate that it is a claim
|
||||
// created by the correct Issuer, Codeberg -- but anyone can do that through Forgejo Actions. We will validate that it
|
||||
// has the correct audience -- but that's an *input* to Forgejo Actions, so anyone can create a claim on Codeberg with
|
||||
// an arbitrary audience. The rest of the claims contain the critical information about who ran a Forgejo Action, on
|
||||
// which repository, and in response to which events, and those must be validated to ensure that an authorized issuer is
|
||||
// correctly authorized.
|
||||
//
|
||||
// Following that an example, a minimum claim rule that would be required for securely using Forgejo Actions would be
|
||||
// something like:
|
||||
//
|
||||
// {
|
||||
// "rules": [{
|
||||
// "claim": "sub",
|
||||
// "comparison": "eq",
|
||||
// "value": "repo:forgejo/website:pull_request"
|
||||
// }]
|
||||
// }
|
||||
//
|
||||
// This defines a single rule which says that the `sub` claim must be exactly equal to
|
||||
// "repo:forgejo/website:pull_request". Forgejo Actions would generate this subject when an Action is running on the
|
||||
// repo forgejo/website in response to the pull_request event.
|
||||
//
|
||||
// Some JWT claims are JSON objects. The [ClaimNested] comparison operator can be used to define rules that inspect the
|
||||
// object within a claim. For example, AWS STS generates a claim "https://sts.amazonaws.com/": {...} with values inside
|
||||
// an object, like "aws_account". A nested claim can inspect those values:
|
||||
//
|
||||
// {
|
||||
// "rules":[{
|
||||
// "claim": "https://sts.amazonaws.com/",
|
||||
// "compare": "nest",
|
||||
// "nested": {"rules":[
|
||||
// {"claim": "aws_account", "compare": "eq", "value": "1234567890"},
|
||||
// {"claim": "lambda_source_function_arn", "compare": "eq", "value": "arn:aws:lambda:ca-central-1:1234567890:function:forgejo-oidc-accepting-test"}
|
||||
// ]}
|
||||
// }
|
||||
//
|
||||
// ]}
|
||||
//
|
||||
// This defines a rule that looks into the "https://sts..." claim and verifies the "aws_account" and
|
||||
// "lambda_source_function_arn" keys match specific known values.
|
||||
type ClaimRules struct {
|
||||
Rules []ClaimRule `json:"rules"`
|
||||
}
|
||||
|
||||
// Defines a single rule that will check the value of one JWT claim.
|
||||
type ClaimRule struct {
|
||||
// The target claim, eg. "sub"
|
||||
Claim string `json:"claim"`
|
||||
// Comparison rule to use on this claim
|
||||
Comparison ClaimComparison `json:"compare"`
|
||||
|
||||
// For Comparison of ClaimEqual or ClaimGlob, the specific value or glob to match against
|
||||
Value string `json:"value,omitempty"`
|
||||
|
||||
// For ClaimNested, the rules to apply to the nested object
|
||||
Nested *ClaimRules `json:"nested,omitempty"`
|
||||
}
|
||||
|
||||
type ClaimComparison string
|
||||
|
||||
const (
|
||||
ClaimEqual ClaimComparison = "eq" // exactly equal claim
|
||||
ClaimGlob ClaimComparison = "glob" // glob match complete claim string
|
||||
ClaimNested ClaimComparison = "nest" // recurse into a claim that is an map[string]any with it's own data fields
|
||||
)
|
||||
|
||||
func GetAuthorizedIntegration(ctx context.Context, issuer, audience string) (*AuthorizedIntegration, error) {
|
||||
var ai AuthorizedIntegration
|
||||
found, err := db.GetEngine(ctx).Where("issuer = ? AND audience = ?", issuer, audience).Get(&ai)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, util.ErrNotExist
|
||||
}
|
||||
return &ai, nil
|
||||
}
|
||||
|
||||
func InsertAuthorizedIntegration(ctx context.Context, ai *AuthorizedIntegration) error {
|
||||
if ai.Audience != "" {
|
||||
return errors.New("audience cannot be provided, and must be generated by NewAuthorizedIntegration")
|
||||
} else if err := ai.generateAudience(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := db.GetEngine(ctx).Insert(ai)
|
||||
return err
|
||||
}
|
||||
|
||||
// Bump the UpdatedUnix field of this authorized integration to now, tracking when it was last used for authentication.
|
||||
// To reduce database write workload, this is only tracked by one-minute intervals -- the UPDATE statement conditionally
|
||||
// avoids writes.
|
||||
func (ai *AuthorizedIntegration) UpdateLastUsed(ctx context.Context) error {
|
||||
newTime := timeutil.TimeStampNow()
|
||||
cnt, err := db.GetEngine(ctx).
|
||||
Table(&AuthorizedIntegration{}).
|
||||
Where(builder.Eq{"id": ai.ID}).
|
||||
Where(builder.Lt{"updated_unix": newTime.AddDuration(-1 * time.Minute)}).
|
||||
NoAutoTime().
|
||||
Update(map[string]any{"updated_unix": newTime})
|
||||
if cnt == 1 {
|
||||
ai.UpdatedUnix = newTime
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Generates the `aud` claim that the remote JWT generator must use to match this authorized integration. The `aud`
|
||||
// claim is an arbitrary value in a JWT claim, but Forgejo is faced with a few hard and soft requirements:
|
||||
//
|
||||
// - Hard requirement: each authorized integration must have a unique `aud`, as it is used to find the DB record that
|
||||
// authenticates a request.
|
||||
// - If authentication is failing, being able to inspect the `aud` claim can be useful to identify the intent.
|
||||
// - Inspection should have a stable meaning -- eg. if it included the username, and the user was renamed, the `aud`
|
||||
// value which can't be changed would continue to reference the old username causing confusion when inspecting it.
|
||||
// - Forgejo & GitHub Actions uses a URL $ACTIONS_ID_TOKEN_REQUEST_URL&audience=... to generate a JWT for the running
|
||||
// action, so it should only consist of safe characters for URL encoding.
|
||||
// - It should be relatively short, as it's encoded into the JWT and increases its size.
|
||||
//
|
||||
// Meeting these requirements decently well is a combination of the owner's ID, a guid, and a "u:" prefix that makes the
|
||||
// fact that it's an `aud` claim value a little bit identifiable.
|
||||
func (ai *AuthorizedIntegration) generateAudience() error {
|
||||
if ai.UserID == 0 {
|
||||
return errors.New("UserID must be initialized")
|
||||
}
|
||||
ai.Audience = fmt.Sprintf("u:%d:%s", ai.UserID, gouuid.New().String())
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/timeutil"
|
||||
)
|
||||
|
||||
// Represents a many-to-many join table which indicates specific repositories (RepoID) that can be accessed by an
|
||||
// authorized integration (IntegID). An authorized integrations's ResourceAllRepos field must be false for records in
|
||||
// this table to become active.
|
||||
//
|
||||
// Model name is shortend (from AuthorizedIntegrationResourceRepo) to accomodate recreate-tables + MySQL, where the
|
||||
// "tmp_recreate_" + foreign key index name would exceed the max identifier length.
|
||||
type AuthorizedIntegResourceRepo struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IntegID int64 `xorm:"NOT NULL REFERENCES(authorized_integration, id)"` // field name shortened (AuthorizationIntegrationID) for max identifier length
|
||||
RepoID int64 `xorm:"NOT NULL REFERENCES(repository, id)"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(AuthorizedIntegResourceRepo))
|
||||
}
|
||||
|
||||
func (air *AuthorizedIntegResourceRepo) GetTargetRepoID() int64 {
|
||||
return air.RepoID
|
||||
}
|
||||
|
||||
func GetRepositoriesAccessibleWithIntegration(ctx context.Context, aiID int64) ([]*AuthorizedIntegResourceRepo, error) {
|
||||
var resources []*AuthorizedIntegResourceRepo
|
||||
err := db.GetEngine(ctx).
|
||||
Where("integ_id = ?", aiID).
|
||||
Find(&resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func InsertAuthorizedIntegrationResourceRepos(ctx context.Context, aiID int64, resources []*AuthorizedIntegResourceRepo) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for _, resourceRepo := range resources {
|
||||
resourceRepo.IntegID = aiID
|
||||
if err := db.Insert(ctx, resourceRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRepositoriesAccessibleWithIntegration(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/auth/TestGetRepositoriesAccessibleWithIntegration")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("No Resources", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithIntegration(t.Context(), 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resources)
|
||||
})
|
||||
|
||||
t.Run("Has Resources", func(t *testing.T) {
|
||||
resources, err := auth_model.GetRepositoriesAccessibleWithIntegration(t.Context(), 1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resources, 3)
|
||||
|
||||
// Verify all expected repo IDs are present
|
||||
repoIDs := make([]int64, len(resources))
|
||||
for i, res := range resources {
|
||||
repoIDs[i] = res.RepoID
|
||||
}
|
||||
assert.Contains(t, repoIDs, int64(1))
|
||||
assert.Contains(t, repoIDs, int64(2))
|
||||
assert.Contains(t, repoIDs, int64(3))
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertAuthorizedIntegration(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ai1 := makeAuthorizedIntegration(t)
|
||||
ai2 := makeAuthorizedIntegration(t)
|
||||
ai3 := makeAuthorizedIntegration(t)
|
||||
|
||||
t.Run("blank insert", func(t *testing.T) {
|
||||
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai1.ID, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("multiple insert", func(t *testing.T) {
|
||||
resRepo1 := &auth_model.AuthorizedIntegResourceRepo{
|
||||
IntegID: ai2.ID,
|
||||
RepoID: 1,
|
||||
}
|
||||
resRepo3 := &auth_model.AuthorizedIntegResourceRepo{
|
||||
IntegID: ai2.ID,
|
||||
RepoID: 3,
|
||||
}
|
||||
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai2.ID,
|
||||
[]*auth_model.AuthorizedIntegResourceRepo{resRepo1, resRepo3})
|
||||
require.NoError(t, err)
|
||||
|
||||
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai2.ID}, 2)
|
||||
})
|
||||
|
||||
t.Run("in tx", func(t *testing.T) {
|
||||
// Pre-condition: count is 0.
|
||||
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai3.ID}, 0)
|
||||
|
||||
// Verify that InsertAuthorizedIntegrationResourceRepos performs inserts in a TX by having a second one with an invalid
|
||||
// RepoID, causing a foreign key violation
|
||||
resRepo1 := &auth_model.AuthorizedIntegResourceRepo{
|
||||
IntegID: ai3.ID,
|
||||
RepoID: 1,
|
||||
}
|
||||
resRepo3 := &auth_model.AuthorizedIntegResourceRepo{
|
||||
IntegID: ai3.ID,
|
||||
RepoID: 30000, // invalid
|
||||
}
|
||||
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai3.ID,
|
||||
[]*auth_model.AuthorizedIntegResourceRepo{resRepo1, resRepo3})
|
||||
require.ErrorContains(t, err, "foreign key")
|
||||
|
||||
// Count remains 0; the first record was not inserted.
|
||||
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai3.ID}, 0)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func makeAuthorizedIntegration(t *testing.T) *auth_model.AuthorizedIntegration {
|
||||
t.Helper()
|
||||
ai := &auth_model.AuthorizedIntegration{
|
||||
UserID: 2,
|
||||
Scope: auth_model.AccessTokenScopeAll,
|
||||
ResourceAllRepos: true,
|
||||
Issuer: "https://example.org/",
|
||||
ClaimRules: &auth_model.ClaimRules{},
|
||||
}
|
||||
require.NoError(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai))
|
||||
return ai
|
||||
}
|
||||
|
||||
func TestGetAuthorizedIntegration(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
ai := makeAuthorizedIntegration(t)
|
||||
|
||||
get, err := auth_model.GetAuthorizedIntegration(t.Context(), "abc", "123")
|
||||
require.ErrorIs(t, err, util.ErrNotExist)
|
||||
assert.Nil(t, get)
|
||||
|
||||
get, err = auth_model.GetAuthorizedIntegration(t.Context(), ai.Issuer, ai.Audience)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, get)
|
||||
assert.Equal(t, ai.ID, get.ID)
|
||||
}
|
||||
|
||||
func TestAuthorizedIntegrationUpdateLastUsed(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ai := makeAuthorizedIntegration(t)
|
||||
ai.UpdatedUnix = 0
|
||||
cnt, err := db.GetEngine(t.Context()).ID(ai.ID).Cols("updated_unix").NoAutoTime().Update(ai)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 1, cnt)
|
||||
|
||||
timeutil.MockSet(time.Unix(1777130023, 0))
|
||||
defer timeutil.MockUnset()
|
||||
|
||||
assert.EqualValues(t, 0, ai.UpdatedUnix)
|
||||
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||
assert.EqualValues(t, 1777130023, ai.UpdatedUnix) // object field updated
|
||||
assert.EqualValues(t, 1777130023, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix)
|
||||
|
||||
// nearly immediate redo should have same timestamp due to the 1 minute deduplication:
|
||||
timeutil.MockSet(time.Unix(1777130025, 0))
|
||||
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||
assert.EqualValues(t, 1777130023, ai.UpdatedUnix) // object field not updated
|
||||
assert.EqualValues(t, 1777130023, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix) // database field not updated
|
||||
|
||||
// but if it's a little while later..
|
||||
timeutil.MockSet(time.Unix(1777131139, 0))
|
||||
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||
assert.EqualValues(t, 1777131139, ai.UpdatedUnix) // object field updated
|
||||
assert.EqualValues(t, 1777131139, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix) // database field updated
|
||||
}
|
||||
|
||||
func TestNewAuthorizedIntegration(t *testing.T) {
|
||||
ai := &auth_model.AuthorizedIntegration{
|
||||
UserID: 2,
|
||||
Scope: auth_model.AccessTokenScopeAll,
|
||||
ResourceAllRepos: true,
|
||||
Issuer: "https://example.org/",
|
||||
ClaimRules: &auth_model.ClaimRules{},
|
||||
}
|
||||
require.NoError(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai))
|
||||
assert.Contains(t, ai.Audience, "u:2:")
|
||||
|
||||
ai = &auth_model.AuthorizedIntegration{
|
||||
UserID: 2,
|
||||
Scope: auth_model.AccessTokenScopeAll,
|
||||
ResourceAllRepos: true,
|
||||
Issuer: "https://example.org/",
|
||||
Audience: "I made my own audience",
|
||||
ClaimRules: &auth_model.ClaimRules{},
|
||||
}
|
||||
require.ErrorContains(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai), "audience cannot be provided")
|
||||
|
||||
ai = &auth_model.AuthorizedIntegration{
|
||||
// Forgot to set UserID
|
||||
Scope: auth_model.AccessTokenScopeAll,
|
||||
ResourceAllRepos: true,
|
||||
Issuer: "https://example.org/",
|
||||
ClaimRules: &auth_model.ClaimRules{},
|
||||
}
|
||||
require.ErrorContains(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai), "UserID must be initialized")
|
||||
}
|
||||
|
|
@ -505,7 +505,7 @@ func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
|
|||
|
||||
// ScopeContains returns true if the grant scope contains the specified scope
|
||||
func (grant *OAuth2Grant) ScopeContains(scope string) bool {
|
||||
for currentScope := range strings.SplitSeq(grant.Scope, " ") {
|
||||
for _, currentScope := range strings.Split(grant.Scope, " ") {
|
||||
if scope == currentScope {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ var registeredConfigs = map[Type]func() Config{}
|
|||
|
||||
// RegisterTypeConfig register a config for a provided type
|
||||
func RegisterTypeConfig(typ Type, exemplar Config) {
|
||||
if reflect.TypeOf(exemplar).Kind() == reflect.Pointer {
|
||||
if reflect.TypeOf(exemplar).Kind() == reflect.Ptr {
|
||||
// Pointer:
|
||||
registeredConfigs[typ] = func() Config {
|
||||
return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ package db
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
|
|
@ -271,91 +268,6 @@ func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err e
|
|||
return &bean, true, nil
|
||||
}
|
||||
|
||||
// Retrieves multiple objects with database queries similar to an xorm `.In(idField, idList)`. idField must be a unique
|
||||
// field on the database table, as a map[id]obj is returned and the usage of a non-unique field would result in objects
|
||||
// being overwritten in the map.
|
||||
//
|
||||
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
|
||||
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
|
||||
// limitations or query length limitations in the database engine.
|
||||
func GetByIDs[Bean any, Id comparable](ctx context.Context, idField string, idList []Id, bean *Bean) (map[Id]*Bean, error) {
|
||||
retval := make(map[Id]*Bean, len(idList))
|
||||
if len(idList) == 0 {
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
|
||||
}
|
||||
|
||||
var structFieldName string
|
||||
for _, c := range table.Columns() {
|
||||
if c.Name == idField {
|
||||
structFieldName = c.FieldName
|
||||
break
|
||||
}
|
||||
}
|
||||
if structFieldName == "" {
|
||||
return nil, fmt.Errorf("unable to identify struct field for id field %s", idField)
|
||||
}
|
||||
|
||||
for idChunk := range slices.Chunk(idList, DefaultMaxInSize) {
|
||||
beans := make([]*Bean, 0, len(idChunk))
|
||||
if err := GetEngine(ctx).In(idField, idChunk).Find(&beans); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, bean := range beans {
|
||||
retval[extractFieldValue(bean, structFieldName).(Id)] = bean
|
||||
}
|
||||
}
|
||||
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
// Retrieves multiple objects with database queries similar to an xorm `.In(field, valueList)`. Similar to GetByIDs,
|
||||
// except that a map[Id][]*Bean is returned as the field value is not assumed to be a unique value -- if there are
|
||||
// multiple rows in the table for each value, all of them are returned.
|
||||
//
|
||||
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
|
||||
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
|
||||
// limitations or query length limitations in the database engine.
|
||||
func GetByFieldIn[Bean any, Id comparable](ctx context.Context, field string, valueList []Id, bean *Bean) (map[Id][]*Bean, error) {
|
||||
retval := make(map[Id][]*Bean, len(valueList))
|
||||
if len(valueList) == 0 {
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
table, err := TableInfo(bean)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
|
||||
}
|
||||
|
||||
var structFieldName string
|
||||
for _, c := range table.Columns() {
|
||||
if c.Name == field {
|
||||
structFieldName = c.FieldName
|
||||
break
|
||||
}
|
||||
}
|
||||
if structFieldName == "" {
|
||||
return nil, fmt.Errorf("unable to identify struct field for field %s", field)
|
||||
}
|
||||
|
||||
for idChunk := range slices.Chunk(valueList, DefaultMaxInSize) {
|
||||
beans := make([]*Bean, 0, len(idChunk))
|
||||
if err := GetEngine(ctx).In(field, idChunk).Find(&beans); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, bean := range beans {
|
||||
fieldValue := extractFieldValue(bean, structFieldName).(Id)
|
||||
retval[fieldValue] = append(retval[fieldValue], bean)
|
||||
}
|
||||
}
|
||||
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
|
||||
if !cond.IsValid() {
|
||||
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
|
||||
|
|
@ -504,68 +416,3 @@ func inTransaction(ctx context.Context) (*xorm.Session, bool) {
|
|||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
type RetryConfig struct {
|
||||
ErrorIs []error
|
||||
AttemptCount int
|
||||
}
|
||||
|
||||
var ErrNestedRetryTxFailure = errors.New("(nested)")
|
||||
|
||||
type nestedRetryTxState int
|
||||
|
||||
var nestedRetryTx nestedRetryTxState
|
||||
|
||||
// Execute the given function in a transaction. RetryConfig will retry the function on an error, if it matches the
|
||||
// ErrorIs parameter, up to the total of AttemptCount number of tries. RetryTx cannot be invoked when already within a
|
||||
// transaction and will return an error immediately.
|
||||
//
|
||||
// ErrNestedRetryTxFailure is an error type that will occur when RetryTx is nested within each other, and indicates that
|
||||
// an inner RetryTx encountered an error that matched its error list.
|
||||
func RetryTx(ctx context.Context, config RetryConfig, f func(ctx context.Context) error) error {
|
||||
matchError := func(err error) bool {
|
||||
for _, possibleError := range config.ErrorIs {
|
||||
if errors.Is(err, possibleError) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Accept `ErrNestedRetryTxFailure` as error to retry on, means that a nested
|
||||
// RetryTx indicated to retry the whole transaction.
|
||||
config.ErrorIs = append(config.ErrorIs, ErrNestedRetryTxFailure)
|
||||
|
||||
withinRetryTx, present := ctx.Value(nestedRetryTx).(bool)
|
||||
if present && withinRetryTx {
|
||||
// If a caller already started `RetryTx`, then we assume we don't have to actually perform retries here -- we
|
||||
// can attempt the requested function once, and if an error is returned that matches the configured error list,
|
||||
// we'll return that error + ErrNestedRetryTxFailure wrapping.
|
||||
err := f(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if matchError(err) {
|
||||
return fmt.Errorf("nested RetryTx; internal Tx failed with error that won't be retried: %w %w", err, ErrNestedRetryTxFailure)
|
||||
}
|
||||
return err
|
||||
} else if InTransaction(ctx) {
|
||||
return errors.New("unsupported operation: attempted to use RetryTx while already within a transaction")
|
||||
} else if config.AttemptCount == 0 {
|
||||
return errors.New("unsupported operation: attempted to use RetryTx with 0 attempts")
|
||||
}
|
||||
|
||||
innerCtx := context.WithValue(ctx, nestedRetryTx, true)
|
||||
var lastError error
|
||||
for range config.AttemptCount {
|
||||
err := WithTx(innerCtx, f)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if !matchError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
lastError = err
|
||||
}
|
||||
|
||||
return fmt.Errorf("retry tx failed after %d attempts; last error: %w", config.AttemptCount, lastError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,103 +220,3 @@ func TestAfterTx(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryTx(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{AttemptCount: 1}, func(ctx context.Context) error {
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("fail constantly", func(t *testing.T) {
|
||||
attemptCount := 0
|
||||
testError := errors.New("hello")
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
ErrorIs: []error{testError},
|
||||
}, func(ctx context.Context) error {
|
||||
attemptCount++
|
||||
return testError
|
||||
})
|
||||
require.ErrorIs(t, err, testError)
|
||||
require.ErrorContains(t, err, "2 attempts")
|
||||
assert.Equal(t, 2, attemptCount)
|
||||
})
|
||||
|
||||
t.Run("fail w/ non retriable error", func(t *testing.T) {
|
||||
attemptCount := 0
|
||||
testError := errors.New("hello")
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
ErrorIs: []error{},
|
||||
}, func(ctx context.Context) error {
|
||||
attemptCount++
|
||||
return testError
|
||||
})
|
||||
require.ErrorIs(t, err, testError)
|
||||
assert.Equal(t, 1, attemptCount)
|
||||
})
|
||||
|
||||
t.Run("succeed on retry", func(t *testing.T) {
|
||||
attemptCount := 0
|
||||
testError := errors.New("hello")
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
ErrorIs: []error{testError},
|
||||
}, func(ctx context.Context) error {
|
||||
attemptCount++
|
||||
if attemptCount == 1 {
|
||||
return testError
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, attemptCount)
|
||||
})
|
||||
|
||||
t.Run("nested", func(t *testing.T) {
|
||||
attemptCount := 0
|
||||
testError := errors.New("hello")
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
}, func(ctx context.Context) error {
|
||||
attemptCount++
|
||||
return db.RetryTx(ctx, db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
ErrorIs: []error{testError},
|
||||
}, func(ctx context.Context) error {
|
||||
if attemptCount == 2 {
|
||||
return nil
|
||||
}
|
||||
return testError
|
||||
})
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, attemptCount)
|
||||
})
|
||||
|
||||
t.Run("inner RetryTx decides on error", func(t *testing.T) {
|
||||
attemptCount := 0
|
||||
testError := errors.New("hello")
|
||||
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
ErrorIs: []error{},
|
||||
}, func(ctx context.Context) error {
|
||||
attemptCount++
|
||||
return db.RetryTx(ctx, db.RetryConfig{
|
||||
AttemptCount: 2,
|
||||
}, func(ctx context.Context) error {
|
||||
if attemptCount == 2 {
|
||||
return nil
|
||||
}
|
||||
return testError
|
||||
})
|
||||
})
|
||||
|
||||
require.ErrorIs(t, err, testError)
|
||||
assert.Equal(t, 1, attemptCount)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx contex
|
|||
|
||||
func extractFieldValue(bean any, fieldName string) any {
|
||||
v := reflect.ValueOf(bean)
|
||||
if v.Kind() == reflect.Pointer {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
field := v.FieldByName(fieldName)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue