mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
829b49c1ae | ||
|
|
58031cdc97 | ||
|
|
1eee73e9bd | ||
|
|
dc30660291 | ||
|
|
6bc0e8cd57 | ||
|
|
cfcbdf41e2 | ||
|
|
6195cda58f | ||
|
|
5251cd572f | ||
|
|
ce8912c8ff | ||
|
|
0e2a188d8e | ||
|
|
95d6b572fe | ||
|
|
c4e9d96162 | ||
|
|
96cb6cd16f | ||
|
|
02b18a0c8e | ||
|
|
1ffe46d2a4 | ||
|
|
26f537dea6 | ||
|
|
74e7e813c3 | ||
|
|
3f0aa7a01e | ||
|
|
7e1e32b42e | ||
|
|
63669003eb | ||
|
|
f5fd936cbb | ||
|
|
d87bbd3dd4 | ||
|
|
7a03ec3a4c | ||
|
|
0132cb82c2 | ||
|
|
33aaf0cfa3 | ||
|
|
6064554b8d | ||
|
|
d29364a4f6 | ||
|
|
998a06627e | ||
|
|
250d18d5f6 | ||
|
|
c26daf578f | ||
|
|
5371e788fe | ||
|
|
0001e58e6b | ||
|
|
79c4237cb2 | ||
|
|
20f73b2230 | ||
|
|
175bc89bf9 | ||
|
|
793e77b073 | ||
|
|
5018ae1b29 | ||
|
|
c60b13b2dd | ||
|
|
27bd022b34 | ||
|
|
4ebd5e319b | ||
|
|
d6945fb0b5 | ||
|
|
69a2bfcfbd | ||
|
|
15a0ea83ca | ||
|
|
30cc41ff97 | ||
|
|
7f672160de | ||
|
|
e27f69bc88 | ||
|
|
a67171af37 | ||
|
|
c7bd37e901 | ||
|
|
0a4078558e | ||
|
|
e66e773a6f | ||
|
|
08c123299a | ||
|
|
219b0e7c17 | ||
|
|
02fd18970b | ||
|
|
efee6d7ce4 | ||
|
|
0ca36eeb3c | ||
|
|
3ee1924069 | ||
|
|
99ac787400 | ||
|
|
1856fef7a8 | ||
|
|
7440f1e452 | ||
|
|
638c75a4d3 | ||
|
|
575754cbeb | ||
|
|
f24f3e1eda | ||
|
|
1f615a406f | ||
|
|
9c77a87908 | ||
|
|
4c3df3ab3c | ||
|
|
565957503c | ||
|
|
e48591ba64 | ||
|
|
f2a08e0261 | ||
|
|
532fb8f4f4 | ||
|
|
25416e9be7 | ||
|
|
1c4fff096f | ||
|
|
d16daed041 | ||
|
|
2d58c8c3df | ||
|
|
1e28bb1bd7 | ||
|
|
f0e8c3c3d0 | ||
|
|
7c58b73243 | ||
|
|
6546382f4e | ||
|
|
0930bd71fe | ||
|
|
15b8a5805a |
No files matched your search
@@ -63,6 +63,7 @@ cpu.out
|
|||||||
/public/assets/fonts
|
/public/assets/fonts
|
||||||
/public/assets/img/avatar
|
/public/assets/img/avatar
|
||||||
/vendor
|
/vendor
|
||||||
|
/VERSION
|
||||||
/.air
|
/.air
|
||||||
/.go-licenses
|
/.go-licenses
|
||||||
/Dockerfile
|
/Dockerfile
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
/public/assets/img/svg/*.svg linguist-generated
|
/public/assets/img/svg/*.svg linguist-generated
|
||||||
/templates/swagger/*.generated.json linguist-generated
|
/templates/swagger/*.generated.json linguist-generated
|
||||||
/options/fileicon/** linguist-generated
|
/options/fileicon/** linguist-generated
|
||||||
/services/migrations/_mock_data/** linguist-generated
|
|
||||||
/tests/integration/_mock_data/** linguist-generated
|
|
||||||
/vendor/** -text -eol linguist-vendored
|
/vendor/** -text -eol linguist-vendored
|
||||||
Dockerfile.* linguist-language=Dockerfile
|
Dockerfile.* linguist-language=Dockerfile
|
||||||
Makefile.* linguist-language=Makefile
|
Makefile.* linguist-language=Makefile
|
||||||
@@ -6,7 +6,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Follow the [release management guide](https://github.com/go-gitea/gitea/blob/main/docs/release-management.md).
|
Follow the [release management guide](https://github.com/go-gitea/gitea/blob/main/docs/release-management.md).
|
||||||
Replace the examples below and mark inapplicable tasks as such.
|
Set the issue title and milestone to the version being released. Replace the examples below and mark inapplicable tasks as such.
|
||||||
CI signs the tag, generates release notes, and publishes binaries and containers. Track verification here; no manual changelog PR or release upload is needed.
|
CI signs the tag, generates release notes, and publishes binaries and containers. Track verification here; no manual changelog PR or release upload is needed.
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||||
with:
|
with:
|
||||||
node-version: 26
|
node-version: 26
|
||||||
- if: ${{ inputs.cache == 'true' }}
|
- if: ${{ inputs.cache == 'true' }}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
||||||
GOTEST_FLAGS: -race -timeout=40m
|
GOTEST_FLAGS: -race -timeout=40m
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
TEST_LDAP: 1
|
TEST_LDAP: 1
|
||||||
TEST_SHARD: ${{ inputs.shard }}
|
TEST_SHARD: ${{ inputs.shard }}
|
||||||
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
|
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
|
||||||
@@ -4,7 +4,7 @@ description: Set up uv and python and restore the uv cache
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.14
|
python-version: 3.14
|
||||||
save-cache: ${{ github.workflow == 'cache-seeder' }}
|
save-cache: ${{ github.workflow == 'cache-seeder' }}
|
||||||
@@ -37,15 +37,22 @@ jobs:
|
|||||||
- uses: ./.github/actions/go-setup
|
- uses: ./.github/actions/go-setup
|
||||||
- run: make deps-backend deps-tools
|
- run: make deps-backend deps-tools
|
||||||
- run: TAGS="bindata" make backend
|
- run: TAGS="bindata" make backend
|
||||||
|
- run: TAGS="bindata gogit" make backend
|
||||||
- name: warm test compile cache (bindata)
|
- name: warm test compile cache (bindata)
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
GOTEST_FLAGS: -race -list=^$$ -count=1
|
GOTEST_FLAGS: -race -list=^$$ -count=1
|
||||||
run: make test-backend
|
run: make test-backend
|
||||||
|
- name: warm test compile cache (bindata gogit)
|
||||||
|
env:
|
||||||
|
TAGS: bindata gogit
|
||||||
|
GOTEST_FLAGS: -race -list=^$$ -count=1
|
||||||
|
run: make test-backend
|
||||||
- name: warm integration compile cache
|
- name: warm integration compile cache
|
||||||
run: |
|
run: |
|
||||||
TAGS="bindata" make test-integration-compile
|
TAGS="bindata" make test-integration-compile
|
||||||
TAGS="bindata" GOTEST_FLAGS="-race" make test-integration-compile
|
TAGS="bindata gogit" make test-integration-compile
|
||||||
|
TAGS="bindata gogit" GOTEST_FLAGS="-race" make test-integration-compile
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ concurrency:
|
|||||||
group: cron-renovate
|
group: cron-renovate
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RENOVATE_VERSION: 44.155.8 # renovate: datasource=npm depName=renovate
|
RENOVATE_VERSION: 44.109.1 # renovate: datasource=npm depName=renovate
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- uses: renovatebot/github-action@f0f41e5f2e706165f6814b1d0f0d0e319de7d1d9 # v46.3.8
|
- uses: renovatebot/github-action@9fea9f0fbf80401026d11d03911d62b1f70fef1f # v46.3.3
|
||||||
with:
|
with:
|
||||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
renovate-version: ${{ env.RENOVATE_VERSION }}
|
||||||
configurationFile: renovate.json5
|
configurationFile: renovate.json5
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0
|
- uses: crowdin/github-action@df474cdfb9f41d6ae777118749477c2cdf7cacc8 # v3.2.0
|
||||||
with:
|
with:
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1
|
- uses: go-gitea/giteabot@f48c6a15e0d384f037aea19cc05ff5e9551096b9 # v1.1.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITEABOT_TOKEN }}
|
github_token: ${{ secrets.GITEABOT_TOKEN }}
|
||||||
gitea_fork: giteabot/gitea
|
gitea_fork: giteabot/gitea
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ on:
|
|||||||
checks:
|
checks:
|
||||||
description: Comma-separated list of non-backport checks to run
|
description: Comma-separated list of non-backport checks to run
|
||||||
required: false
|
required: false
|
||||||
default: backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions
|
default: labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
statuses: write
|
statuses: write
|
||||||
steps:
|
steps:
|
||||||
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1
|
- uses: go-gitea/giteabot@8033358e118c0bd97f5e9902dc5d6633eadc6aeb # v1.2.1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITEABOT_TOKEN }}
|
github_token: ${{ secrets.GITEABOT_TOKEN }}
|
||||||
checks: ${{ github.event.inputs.checks || 'backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
|
checks: ${{ github.event.inputs.checks || 'labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
|
||||||
@@ -27,7 +27,6 @@ jobs:
|
|||||||
- run: TAGS="bindata" make generate-go lint-backend # lint-go can lint with "bindata" tags
|
- run: TAGS="bindata" make generate-go lint-backend # lint-go can lint with "bindata" tags
|
||||||
|
|
||||||
lint-on-demand:
|
lint-on-demand:
|
||||||
if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true' || needs.files-changed.outputs.shell == 'true'
|
|
||||||
needs: files-changed
|
needs: files-changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -39,6 +38,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: "false"
|
cache: "false"
|
||||||
|
|
||||||
|
- run: make lint-spell
|
||||||
|
|
||||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true'
|
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||||
uses: ./.github/actions/python-setup
|
uses: ./.github/actions/python-setup
|
||||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
||||||
@@ -92,13 +93,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
- name: build-backend-windows
|
- name: build-backend-windows
|
||||||
run: go build -ldflags '-s -w' -o gitea-windows
|
run: go build -ldflags '-s -w' -o gitea-windows
|
||||||
env:
|
env:
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
- name: build-backend-386
|
- name: build-backend-386
|
||||||
run: go build -ldflags '-s -w' -o gitea-linux-386
|
run: go build -ldflags '-s -w' -o gitea-linux-386
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
POSTGRES_DB: test
|
POSTGRES_DB: test
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
options: >-
|
|
||||||
--mount type=tmpfs,destination=/var/lib/postgresql/data
|
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
ldap:
|
ldap:
|
||||||
@@ -63,8 +61,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
POSTGRES_DB: test
|
POSTGRES_DB: test
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
options: >-
|
|
||||||
--mount type=tmpfs,destination=/var/lib/postgresql/data
|
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
ldap:
|
ldap:
|
||||||
@@ -99,17 +95,17 @@ jobs:
|
|||||||
- run: make deps-backend
|
- run: make deps-backend
|
||||||
- run: make backend
|
- run: make backend
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
- run: GITEA_TEST_DATABASE=sqlite make test-migration
|
- run: GITEA_TEST_DATABASE=sqlite make test-migration
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
||||||
GOTEST_FLAGS: -timeout=40m
|
GOTEST_FLAGS: -timeout=40m
|
||||||
TAGS: bindata
|
TAGS: bindata gogit
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
if: needs.files-changed.outputs.backend == 'true'
|
if: needs.files-changed.outputs.backend == 'true'
|
||||||
@@ -117,7 +113,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:8.19.23@sha256:8b1751877c984f3e4f59bafb15fc890e01969a744255fd5f6860c060b62cff36
|
image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
|
||||||
env:
|
env:
|
||||||
discovery.type: single-node
|
discovery.type: single-node
|
||||||
xpack.security.enabled: false
|
xpack.security.enabled: false
|
||||||
@@ -167,6 +163,13 @@ jobs:
|
|||||||
GOTEST_FLAGS: -race -timeout=20m
|
GOTEST_FLAGS: -race -timeout=20m
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
|
- name: unit-tests-gogit
|
||||||
|
run: make test-backend
|
||||||
|
env:
|
||||||
|
GOTEST_FLAGS: -race -timeout=20m
|
||||||
|
TAGS: bindata gogit
|
||||||
|
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||||
|
GITEA_TEST_CI_SKIP_EXTERNAL: true
|
||||||
- run: make test-check
|
- run: make test-check
|
||||||
|
|
||||||
test-mysql:
|
test-mysql:
|
||||||
@@ -185,7 +188,7 @@ jobs:
|
|||||||
options: >-
|
options: >-
|
||||||
--mount type=tmpfs,destination=/bitnami/mysql/data
|
--mount type=tmpfs,destination=/bitnami/mysql/data
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:8.19.23@sha256:8b1751877c984f3e4f59bafb15fc890e01969a744255fd5f6860c060b62cff36
|
image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
|
||||||
env:
|
env:
|
||||||
discovery.type: single-node
|
discovery.type: single-node
|
||||||
xpack.security.enabled: false
|
xpack.security.enabled: false
|
||||||
@@ -223,7 +226,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mssql:
|
mssql:
|
||||||
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:07d7dedecdbfcf6776035dc5eefb5bfd9aa1804b2f6b99f7dedb397d8a599069
|
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:ef0b8db33970ecd01bed49c3a84a1d083c435a9891718df619298b67b352e74a
|
||||||
env:
|
env:
|
||||||
ACCEPT_EULA: Y
|
ACCEPT_EULA: Y
|
||||||
MSSQL_PID: Standard
|
MSSQL_PID: Standard
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.base.sha }}
|
ref: ${{ github.event.pull_request.base.sha }}
|
||||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||||
with:
|
with:
|
||||||
node-version: 26
|
node-version: 26
|
||||||
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
|
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
tag:
|
tag:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment:
|
environment: release-signing
|
||||||
name: release-signing
|
|
||||||
deployment: false
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
- Never assume, verify before claiming
|
- Never assume, verify before claiming
|
||||||
- List development targets with `make help`
|
- List development targets with `make help`
|
||||||
- Before working on code, read the `docs` guides for every area touched: `guidelines-backend.md` for Go, `guidelines-frontend.md` for TS, CSS and Vue, both for templates, `testing.md` for tests, `development.md` for config options and migrations, `guidelines-refactoring.md` for refactors
|
- Read relevant developer documentation in the `docs` folder
|
||||||
- PR descriptions: minimal, only what and why, no task or file listings. Include screenshots for UI changes, before and after when modifying existing UI. Aim for less than 1000 characters
|
- PR descriptions: minimal, only what and why, no task or file listings. Include screenshots for UI changes, before and after when modifying existing UI. Aim for less than 1000 characters
|
||||||
- Reference issues and PRs by full URL, not by number
|
- Reference issues and PRs by full URL, not by number
|
||||||
- Use Conventional Commits for commit messages and PR titles, plus Gitea's `enhance` type for user-facing enhancements
|
- Use Conventional Commits for commit messages and PR titles, plus Gitea's `enhance` type for user-facing enhancements
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
|
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
|
||||||
- In TS, use `!` instead of `?.`/`??` when a value always exists
|
- In TS, use `!` instead of `?.`/`??` when a value always exists
|
||||||
- In Go, prefer to use modern language features wherever possible
|
- In Go, prefer to use modern language features wherever possible
|
||||||
- In Go, function-name prefixes in errors like `fmt.Errorf("Foo: %w", err)` must always name the function they are in
|
|
||||||
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
|
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
|
||||||
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
|
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
|
||||||
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
|
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits
|
|||||||
type(scope)!: subject
|
type(scope)!: subject
|
||||||
```
|
```
|
||||||
|
|
||||||
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). Do not mark `build`, `chore`, `ci`, `docs`, `style`, or `test` as breaking.
|
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`).
|
||||||
|
|
||||||
Use one of these types:
|
Use one of these types:
|
||||||
|
|
||||||
|
|||||||
@@ -65,4 +65,3 @@ Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
|
|||||||
TheFox <thefox0x7@gmail.com> (@TheFox0x7)
|
TheFox <thefox0x7@gmail.com> (@TheFox0x7)
|
||||||
Nicolas <bircni@icloud.com> (@bircni)
|
Nicolas <bircni@icloud.com> (@bircni)
|
||||||
Minjie Fang <wingsallen@gmail.com> (@wingsallen)
|
Minjie Fang <wingsallen@gmail.com> (@wingsallen)
|
||||||
Shudhanshu Singh <sudhanshuwriterblc@gmail.com> (@SudhanshuMatrix)
|
|
||||||
@@ -8,8 +8,9 @@ AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go
|
|||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
|
||||||
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.9.0 # renovate: datasource=go
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.8.0 # renovate: datasource=go
|
||||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
|
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
|
||||||
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
|
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
|
||||||
|
|
||||||
@@ -74,7 +75,8 @@ STORED_VERSION_FILE := VERSION
|
|||||||
GITHUB_REF_TYPE ?= branch
|
GITHUB_REF_TYPE ?= branch
|
||||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
# VERSION: the name for the build and filenames, e.g.: "1.2.3" for tag "v1.2.3", "main-nightly" for branch "main"
|
# VERSION: the branch name for the build and filenames, e.g.: "feature/foo-bar", "main"
|
||||||
|
# branch name "release/v1.27.2" is stripped to "1.27.2".
|
||||||
# GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
|
# GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
|
||||||
ifeq ($(GITHUB_REF_TYPE),tag)
|
ifeq ($(GITHUB_REF_TYPE),tag)
|
||||||
# convert tag "v1.2.3" to "1.2.3"
|
# convert tag "v1.2.3" to "1.2.3"
|
||||||
@@ -99,6 +101,11 @@ else
|
|||||||
$(error unsupported ref type $(GITHUB_REF_TYPE))
|
$(error unsupported ref type $(GITHUB_REF_TYPE))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# if version == "main" then add "-nightly" to the version for nightly builds: "main-nightly"
|
||||||
|
ifeq ($(VERSION),main)
|
||||||
|
VERSION := main-nightly
|
||||||
|
endif
|
||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
|
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
|
||||||
|
|
||||||
@@ -125,9 +132,11 @@ GO_LICENSE_FILE := assets/go-licenses.json
|
|||||||
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
|
||||||
|
|
||||||
GO_DIRS := build cmd modelmigration models modules routers services tests tools
|
GO_DIRS := build cmd modelmigration models modules routers services tests tools
|
||||||
|
WEB_DIRS := web_src/js web_src/css
|
||||||
|
|
||||||
ESLINT_FILES := web_src/js tools *.ts tests/e2e
|
ESLINT_FILES := web_src/js tools *.ts tests/e2e
|
||||||
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
|
||||||
|
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.json .github $(wildcard *.go *.md *.yml *.yaml *.toml)
|
||||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
|
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
|
||||||
|
|
||||||
GO_SOURCES := $(wildcard *.go)
|
GO_SOURCES := $(wildcard *.go)
|
||||||
@@ -264,10 +273,10 @@ checks-frontend: lockfile-check svg-check ## check frontend files
|
|||||||
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
|
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-frontend lint-backend lint-templates lint-swagger lint-md lint-actions lint-json lint-yaml lint-shell ## lint everything
|
lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml lint-shell ## lint everything
|
||||||
|
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix: lint-frontend-fix lint-backend-fix ## lint everything and fix issues
|
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
.PHONY: lint-frontend
|
||||||
lint-frontend: lint-js lint-css ## lint frontend files
|
lint-frontend: lint-js lint-css ## lint frontend files
|
||||||
@@ -311,6 +320,14 @@ lint-md: node_modules ## lint markdown files
|
|||||||
lint-md-fix: node_modules ## lint markdown files and fix issues
|
lint-md-fix: node_modules ## lint markdown files and fix issues
|
||||||
pnpm exec markdownlint --fix *.md
|
pnpm exec markdownlint --fix *.md
|
||||||
|
|
||||||
|
.PHONY: lint-spell
|
||||||
|
lint-spell: ## lint spelling
|
||||||
|
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
|
||||||
|
|
||||||
|
.PHONY: lint-spell-fix
|
||||||
|
lint-spell-fix: ## lint spelling and fix issues
|
||||||
|
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w
|
||||||
|
|
||||||
.PHONY: lint-go
|
.PHONY: lint-go
|
||||||
lint-go: ## lint go files
|
lint-go: ## lint go files
|
||||||
GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go
|
GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go
|
||||||
@@ -539,7 +556,7 @@ release-compress: | $(DIST_DIRS)
|
|||||||
|
|
||||||
.PHONY: release-sources
|
.PHONY: release-sources
|
||||||
release-sources: | $(DIST_DIRS)
|
release-sources: | $(DIST_DIRS)
|
||||||
echo $(GITEA_VERSION) > $(STORED_VERSION_FILE)
|
echo $(VERSION) > $(STORED_VERSION_FILE)
|
||||||
# bsdtar needs a ^ to prevent matching subdirectories
|
# bsdtar needs a ^ to prevent matching subdirectories
|
||||||
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
|
||||||
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
|
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
|
||||||
@@ -561,14 +578,15 @@ deps-backend: ## install backend dependencies
|
|||||||
$(GO) mod download
|
$(GO) mod download
|
||||||
|
|
||||||
.PHONY: deps-tools
|
.PHONY: deps-tools
|
||||||
deps-tools: ## fetch and compile tool dependencies
|
deps-tools: ## install tool dependencies
|
||||||
$(GO) run -exec true $(AIR_PACKAGE) & \
|
$(GO) install $(AIR_PACKAGE) & \
|
||||||
$(GO) run -exec true $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
|
||||||
$(GO) run -exec true $(GOLANGCI_LINT_PACKAGE) & \
|
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
|
||||||
$(GO) run -exec true $(GXZ_PACKAGE) & \
|
$(GO) install $(GXZ_PACKAGE) & \
|
||||||
$(GO) run -exec true $(SWAGGER_PACKAGE) & \
|
$(GO) install $(MISSPELL_PACKAGE) & \
|
||||||
$(GO) run -exec true $(GOVULNCHECK_PACKAGE) & \
|
$(GO) install $(SWAGGER_PACKAGE) & \
|
||||||
$(GO) run -exec true $(ACTIONLINT_PACKAGE) & \
|
$(GO) install $(GOVULNCHECK_PACKAGE) & \
|
||||||
|
$(GO) install $(ACTIONLINT_PACKAGE) & \
|
||||||
wait
|
wait
|
||||||
|
|
||||||
node_modules: pnpm-lock.yaml
|
node_modules: pnpm-lock.yaml
|
||||||
|
|||||||
Generated
+72
-2
@@ -5,8 +5,8 @@
|
|||||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "connectrpc.com/connect/v2",
|
"name": "connectrpc.com/connect",
|
||||||
"path": "connectrpc.com/connect/v2/LICENSE",
|
"path": "connectrpc.com/connect/LICENSE",
|
||||||
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
|
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -14,6 +14,26 @@
|
|||||||
"path": "filippo.io/edwards25519/LICENSE",
|
"path": "filippo.io/edwards25519/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "gitea.com/go-chi/binding",
|
||||||
|
"path": "gitea.com/go-chi/binding/LICENSE",
|
||||||
|
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gitea.com/go-chi/cache",
|
||||||
|
"path": "gitea.com/go-chi/cache/LICENSE",
|
||||||
|
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gitea.com/go-chi/captcha",
|
||||||
|
"path": "gitea.com/go-chi/captcha/LICENSE",
|
||||||
|
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gitea.com/go-chi/session",
|
||||||
|
"path": "gitea.com/go-chi/session/LICENSE",
|
||||||
|
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "gitea.com/lunny/dingtalk_webhook",
|
"name": "gitea.com/lunny/dingtalk_webhook",
|
||||||
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
|
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
|
||||||
@@ -54,6 +74,11 @@
|
|||||||
"path": "github.com/Azure/go-ntlmssp/LICENSE",
|
"path": "github.com/Azure/go-ntlmssp/LICENSE",
|
||||||
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Microsoft\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Microsoft\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/DataDog/zstd",
|
||||||
|
"path": "github.com/DataDog/zstd/LICENSE",
|
||||||
|
"licenseText": "Simplified BSD License\n\nCopyright (c) 2016, Datadog \u003cinfo@datadoghq.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the copyright holder nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/Necoro/html2text",
|
"name": "github.com/Necoro/html2text",
|
||||||
"path": "github.com/Necoro/html2text/LICENSE",
|
"path": "github.com/Necoro/html2text/LICENSE",
|
||||||
@@ -284,6 +309,11 @@
|
|||||||
"path": "github.com/charmbracelet/git-lfs-transfer/LICENSE",
|
"path": "github.com/charmbracelet/git-lfs-transfer/LICENSE",
|
||||||
"licenseText": "MIT License\n\nCopyright (c) 2022-2023 Charmbracelet, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
"licenseText": "MIT License\n\nCopyright (c) 2022-2023 Charmbracelet, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/chi-middleware/proxy",
|
||||||
|
"path": "github.com/chi-middleware/proxy/LICENSE",
|
||||||
|
"licenseText": "Copyright (c) 2020 Lauris BH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/clipperhouse/displaywidth",
|
"name": "github.com/clipperhouse/displaywidth",
|
||||||
"path": "github.com/clipperhouse/displaywidth/LICENSE",
|
"path": "github.com/clipperhouse/displaywidth/LICENSE",
|
||||||
@@ -304,6 +334,21 @@
|
|||||||
"path": "github.com/coder/websocket/LICENSE.txt",
|
"path": "github.com/coder/websocket/LICENSE.txt",
|
||||||
"licenseText": "Copyright (c) 2025 Coder\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
|
"licenseText": "Copyright (c) 2025 Coder\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/couchbase/go-couchbase",
|
||||||
|
"path": "github.com/couchbase/go-couchbase/LICENSE",
|
||||||
|
"licenseText": "Copyright (c) 2013 Couchbase, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/couchbase/gomemcached",
|
||||||
|
"path": "github.com/couchbase/gomemcached/LICENSE",
|
||||||
|
"licenseText": "Copyright (c) 2013 Dustin Sallings\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/couchbase/goutils",
|
||||||
|
"path": "github.com/couchbase/goutils/LICENSE.txt",
|
||||||
|
"licenseText": "Source code in this repository is licensed under various licenses. The\nBusiness Source License 1.1 (BSL) is one such license. Each file indicates in\na section at the beginning of the file the name of the license that applies to\nit. All licenses used in this repository can be found in the top-level\nlicenses directory.\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/cpuguy83/go-md2man/v2",
|
"name": "github.com/cpuguy83/go-md2man/v2",
|
||||||
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
|
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
|
||||||
@@ -484,6 +529,11 @@
|
|||||||
"path": "github.com/golang/snappy/LICENSE",
|
"path": "github.com/golang/snappy/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/google/flatbuffers",
|
||||||
|
"path": "github.com/google/flatbuffers/LICENSE",
|
||||||
|
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/google/go-github/v92",
|
"name": "github.com/google/go-github/v92",
|
||||||
"path": "github.com/google/go-github/v92/LICENSE",
|
"path": "github.com/google/go-github/v92/LICENSE",
|
||||||
@@ -874,6 +924,11 @@
|
|||||||
"path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE",
|
"path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE",
|
||||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/sassoftware/go-rpmutils",
|
||||||
|
"path": "github.com/sassoftware/go-rpmutils/LICENSE",
|
||||||
|
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/sergi/go-diff",
|
"name": "github.com/sergi/go-diff",
|
||||||
"path": "github.com/sergi/go-diff/LICENSE",
|
"path": "github.com/sergi/go-diff/LICENSE",
|
||||||
@@ -884,6 +939,11 @@
|
|||||||
"path": "github.com/shopspring/decimal/LICENSE",
|
"path": "github.com/shopspring/decimal/LICENSE",
|
||||||
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Spring, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n- Based on https://github.com/oguzbilgic/fpd, which has the following license:\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2013 Oguz Bilgic\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\n"
|
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Spring, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n- Based on https://github.com/oguzbilgic/fpd, which has the following license:\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2013 Oguz Bilgic\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/sirupsen/logrus",
|
||||||
|
"path": "github.com/sirupsen/logrus/LICENSE",
|
||||||
|
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Simon Eskildsen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/sorairolake/lzip-go",
|
"name": "github.com/sorairolake/lzip-go",
|
||||||
"path": "github.com/sorairolake/lzip-go/LICENSE-APACHE",
|
"path": "github.com/sorairolake/lzip-go/LICENSE-APACHE",
|
||||||
@@ -934,6 +994,11 @@
|
|||||||
"path": "github.com/ulikunitz/xz/LICENSE",
|
"path": "github.com/ulikunitz/xz/LICENSE",
|
||||||
"licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
"licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/unknwon/com",
|
||||||
|
"path": "github.com/unknwon/com/LICENSE",
|
||||||
|
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/urfave/cli-docs/v3",
|
"name": "github.com/urfave/cli-docs/v3",
|
||||||
"path": "github.com/urfave/cli-docs/v3/LICENSE",
|
"path": "github.com/urfave/cli-docs/v3/LICENSE",
|
||||||
@@ -959,6 +1024,11 @@
|
|||||||
"path": "github.com/x448/float16/LICENSE",
|
"path": "github.com/x448/float16/LICENSE",
|
||||||
"licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
|
"licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "github.com/xi2/xz",
|
||||||
|
"path": "github.com/xi2/xz/LICENSE",
|
||||||
|
"licenseText": "Licensing of github.com/xi2/xz\n==============================\n\n This Go package is a modified version of\n\n XZ Embedded \u003chttp://tukaani.org/xz/embedded.html\u003e\n\n The contents of the testdata directory are modified versions of\n the test files from\n\n XZ Utils \u003chttp://tukaani.org/xz/\u003e\n\n All the files in this package have been written by Michael Cross,\n Lasse Collin and/or Igor PavLov. All these files have been put\n into the public domain. You can do whatever you want with these\n files.\n\n This software is provided \"as is\", without any warranty.\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "github.com/yohcop/openid-go",
|
"name": "github.com/yohcop/openid-go",
|
||||||
"path": "github.com/yohcop/openid-go/LICENSE",
|
"path": "github.com/yohcop/openid-go/LICENSE",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
acounts,accounts
|
||||||
|
canidate,candidate
|
||||||
|
comfirm,confirm
|
||||||
|
converage,coverage
|
||||||
|
currrently,currently
|
||||||
|
delimeter,delimiter
|
||||||
|
differrent,different
|
||||||
|
exclusing,excluding
|
||||||
|
finshed,finished
|
||||||
|
formated,formatted
|
||||||
|
inderect,indirect
|
||||||
|
insuficient,insufficient
|
||||||
|
likly,likely
|
||||||
|
mergable,mergeable
|
||||||
|
overrided,overridden
|
||||||
|
priortized,prioritized
|
||||||
|
registeration,registration
|
||||||
|
reuqest,request
|
||||||
|
reviwer,reviewer
|
||||||
|
superceded,superseded
|
||||||
|
underlaying,underlying
|
||||||
|
@@ -23,8 +23,8 @@ import (
|
|||||||
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||||
|
|
||||||
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
||||||
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
// Gitea-specific post-processing: server URL, file-schema fixups, URI
|
||||||
// deprecated flags, and shared-enum extraction.
|
// formats, deprecated flags, and shared-enum extraction.
|
||||||
//
|
//
|
||||||
// astEnumMap is a value-set-key → Go-type-name(s) map (built by
|
// astEnumMap is a value-set-key → Go-type-name(s) map (built by
|
||||||
// ScanSwaggerEnumTypes). When a value set is shared by multiple Go types,
|
// ScanSwaggerEnumTypes). When a value set is shared by multiple Go types,
|
||||||
@@ -42,6 +42,7 @@ func Convert(swaggerJSON []byte, astEnumMap map[string][]string) (*openapi3.T, e
|
|||||||
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
|
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oas3.Servers = openapi3.Servers{{URL: swagger2.BasePath}}
|
||||||
fixFileSchemas(oas3)
|
fixFileSchemas(oas3)
|
||||||
addURIFormats(oas3)
|
addURIFormats(oas3)
|
||||||
addDeprecatedFlags(oas3)
|
addDeprecatedFlags(oas3)
|
||||||
|
|||||||
@@ -42,19 +42,27 @@ func TestDeriveEnumName_miss(t *testing.T) {
|
|||||||
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
||||||
doc := &openapi3.T{
|
doc := &openapi3.T{
|
||||||
Components: &openapi3.Components{
|
Components: &openapi3.Components{
|
||||||
Schemas: openapi3.Schemas{},
|
Schemas: openapi3.Schemas{
|
||||||
},
|
"A": {Value: &openapi3.Schema{
|
||||||
}
|
Type: &openapi3.Types{"object"},
|
||||||
for _, name := range []string{"A", "B"} {
|
Properties: openapi3.Schemas{
|
||||||
doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
|
"color": {Value: &openapi3.Schema{
|
||||||
Type: &openapi3.Types{"object"},
|
Type: &openapi3.Types{"string"},
|
||||||
Properties: openapi3.Schemas{
|
Enum: []any{"red", "green", "blue"},
|
||||||
"color": {Value: &openapi3.Schema{
|
}},
|
||||||
Type: &openapi3.Types{"string"},
|
},
|
||||||
Enum: []any{"red", "green", "blue"},
|
}},
|
||||||
|
"B": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green", "blue"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
}
|
}
|
||||||
astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
|
astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
|
||||||
if err := extractSharedEnums(doc, astMap); err != nil {
|
if err := extractSharedEnums(doc, astMap); err != nil {
|
||||||
@@ -182,19 +190,27 @@ func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) {
|
|||||||
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||||
doc := &openapi3.T{
|
doc := &openapi3.T{
|
||||||
Components: &openapi3.Components{
|
Components: &openapi3.Components{
|
||||||
Schemas: openapi3.Schemas{},
|
Schemas: openapi3.Schemas{
|
||||||
},
|
"A": {Value: &openapi3.Schema{
|
||||||
}
|
Type: &openapi3.Types{"object"},
|
||||||
for _, name := range []string{"A", "B"} {
|
Properties: openapi3.Schemas{
|
||||||
doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
|
"color": {Value: &openapi3.Schema{
|
||||||
Type: &openapi3.Types{"object"},
|
Type: &openapi3.Types{"string"},
|
||||||
Properties: openapi3.Schemas{
|
Enum: []any{"red", "green"},
|
||||||
"color": {Value: &openapi3.Schema{
|
}},
|
||||||
Type: &openapi3.Types{"string"},
|
},
|
||||||
Enum: []any{"red", "green"},
|
}},
|
||||||
|
"B": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"object"},
|
||||||
|
Properties: openapi3.Schemas{
|
||||||
|
"color": {Value: &openapi3.Schema{
|
||||||
|
Type: &openapi3.Types{"string"},
|
||||||
|
Enum: []any{"red", "green"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
}
|
}
|
||||||
if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
|
if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
|
||||||
t.Fatal("expected miss error")
|
t.Fatal("expected miss error")
|
||||||
|
|||||||
+141
-110
@@ -33,17 +33,9 @@ func TestEnumKey_handlesNonStringValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanSwaggerEnumTypes(t *testing.T) {
|
func TestScanSwaggerEnumTypes_basic(t *testing.T) {
|
||||||
scan := func(t *testing.T, filename, src string) (map[string][]string, error) {
|
dir := t.TempDir()
|
||||||
dir := t.TempDir()
|
src := `package fixture
|
||||||
if err := os.WriteFile(filepath.Join(dir, filename), []byte(src), 0o644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return ScanSwaggerEnumTypes([]string{dir})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("basic", func(t *testing.T) {
|
|
||||||
got, err := scan(t, "color.go", `package fixture
|
|
||||||
|
|
||||||
// Color is a primary color.
|
// Color is a primary color.
|
||||||
// swagger:enum Color
|
// swagger:enum Color
|
||||||
@@ -54,18 +46,24 @@ const (
|
|||||||
ColorGreen Color = "green"
|
ColorGreen Color = "green"
|
||||||
ColorBlue Color = "blue"
|
ColorBlue Color = "blue"
|
||||||
)
|
)
|
||||||
`)
|
`
|
||||||
if err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
|
||||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
wantKey := EnumKey([]any{"red", "green", "blue"})
|
|
||||||
if single(got, wantKey) != "Color" {
|
|
||||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("orphanAnnotation", func(t *testing.T) {
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
_, err := scan(t, "typo.go", `package fixture
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
wantKey := EnumKey([]any{"red", "green", "blue"})
|
||||||
|
if single(got, wantKey) != "Color" {
|
||||||
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
// swagger:enum Sttype
|
// swagger:enum Sttype
|
||||||
type StateType string
|
type StateType string
|
||||||
@@ -73,17 +71,23 @@ type StateType string
|
|||||||
const (
|
const (
|
||||||
StateOpen StateType = "open"
|
StateOpen StateType = "open"
|
||||||
)
|
)
|
||||||
`)
|
`
|
||||||
if err == nil {
|
if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
|
||||||
t.Fatal("expected error for annotation referencing a non-matching type name")
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "Sttype") {
|
|
||||||
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("collision", func(t *testing.T) {
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
got, err := scan(t, "dup.go", `package fixture
|
if err == nil {
|
||||||
|
t.Fatal("expected error for annotation referencing a non-matching type name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "Sttype") {
|
||||||
|
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_collision(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
// swagger:enum Alpha
|
// swagger:enum Alpha
|
||||||
type Alpha string
|
type Alpha string
|
||||||
@@ -98,90 +102,52 @@ const (
|
|||||||
BetaX Beta = "x"
|
BetaX Beta = "x"
|
||||||
BetaY Beta = "y"
|
BetaY Beta = "y"
|
||||||
)
|
)
|
||||||
`)
|
`
|
||||||
if err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
|
||||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
key := EnumKey([]any{"x", "y"})
|
|
||||||
names := got[key]
|
|
||||||
if !slices.Equal(names, []string{"Alpha", "Beta"}) {
|
|
||||||
t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("parseFailure", func(t *testing.T) {
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
_, err := scan(t, "bad.go", "package fixture\nfunc Foo() {")
|
if err != nil {
|
||||||
if err == nil {
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
t.Fatal("expected parse error, got nil")
|
}
|
||||||
}
|
key := EnumKey([]any{"x", "y"})
|
||||||
})
|
names := got[key]
|
||||||
|
if !slices.Equal(names, []string{"Alpha", "Beta"}) {
|
||||||
|
t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("annotationWithoutConsts", func(t *testing.T) {
|
func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
|
||||||
_, err := scan(t, "lonely.go", `package fixture
|
dir := t.TempDir()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected parse error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
// swagger:enum Lonely
|
// swagger:enum Lonely
|
||||||
type Lonely string
|
type Lonely string
|
||||||
`)
|
`
|
||||||
if err == nil {
|
if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
|
||||||
t.Fatal("expected error for annotation without consts")
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "Lonely") {
|
|
||||||
t.Fatalf("error %q should mention Lonely", err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("constsBeforeType", func(t *testing.T) {
|
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
got, err := scan(t, "shade.go", `package fixture
|
if err == nil {
|
||||||
|
t.Fatal("expected error for annotation without consts")
|
||||||
const (
|
}
|
||||||
ShadeDark Shade = "dark"
|
if !strings.Contains(err.Error(), "Lonely") {
|
||||||
ShadeLight Shade = "light"
|
t.Fatalf("error %q should mention Lonely", err.Error())
|
||||||
)
|
}
|
||||||
|
|
||||||
// swagger:enum Shade
|
|
||||||
type Shade string
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
|
||||||
}
|
|
||||||
wantKey := EnumKey([]any{"dark", "light"})
|
|
||||||
if single(got, wantKey) != "Shade" {
|
|
||||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("groupedTypeDecl", func(t *testing.T) {
|
|
||||||
got, err := scan(t, "grouped.go", `package fixture
|
|
||||||
|
|
||||||
type (
|
|
||||||
// swagger:enum Color
|
|
||||||
Color string
|
|
||||||
// swagger:enum Shade
|
|
||||||
Shade string
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ColorRed Color = "red"
|
|
||||||
ColorBlue Color = "blue"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ShadeDark Shade = "dark"
|
|
||||||
ShadeLight Shade = "light"
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
|
||||||
}
|
|
||||||
colorKey := EnumKey([]any{"red", "blue"})
|
|
||||||
shadeKey := EnumKey([]any{"dark", "light"})
|
|
||||||
if single(got, colorKey) != "Color" {
|
|
||||||
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
|
||||||
}
|
|
||||||
if single(got, shadeKey) != "Shade" {
|
|
||||||
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
|
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
|
||||||
@@ -216,3 +182,68 @@ type Hue string
|
|||||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
const (
|
||||||
|
ShadeDark Shade = "dark"
|
||||||
|
ShadeLight Shade = "light"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swagger:enum Shade
|
||||||
|
type Shade string
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
wantKey := EnumKey([]any{"dark", "light"})
|
||||||
|
if single(got, wantKey) != "Shade" {
|
||||||
|
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
src := `package fixture
|
||||||
|
|
||||||
|
type (
|
||||||
|
// swagger:enum Color
|
||||||
|
Color string
|
||||||
|
// swagger:enum Shade
|
||||||
|
Shade string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorRed Color = "red"
|
||||||
|
ColorBlue Color = "blue"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ShadeDark Shade = "dark"
|
||||||
|
ShadeLight Shade = "light"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||||
|
}
|
||||||
|
colorKey := EnumKey([]any{"red", "blue"})
|
||||||
|
shadeKey := EnumKey([]any{"dark", "light"})
|
||||||
|
if single(got, colorKey) != "Color" {
|
||||||
|
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
||||||
|
}
|
||||||
|
if single(got, shadeKey) != "Shade" {
|
||||||
|
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
||||||
|
}
|
||||||
|
}
|
||||||
+401
-70
@@ -489,20 +489,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
type testCase struct {
|
cases := []struct {
|
||||||
args []string
|
args []string
|
||||||
id int64
|
id int64
|
||||||
existingAuthSource *auth.Source
|
existingAuthSource *auth.Source
|
||||||
authSource *auth.Source
|
authSource *auth.Source
|
||||||
errMsg string
|
errMsg string
|
||||||
}
|
}{
|
||||||
withConfig := func(config *ldap.Source, args ...string) testCase {
|
|
||||||
return testCase{
|
|
||||||
args: append([]string{"ldap-test", "--id", "1"}, args...),
|
|
||||||
authSource: &auth.Source{Type: auth.LDAP, Cfg: config},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cases := []testCase{
|
|
||||||
// case 0
|
// case 0
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -581,7 +574,16 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// case 1
|
// case 1
|
||||||
withConfig(&ldap.Source{}),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 2
|
// case 2
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -616,44 +618,215 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// case 4
|
// case 4
|
||||||
withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(1)}, "--security-protocol", "LDAPS"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--security-protocol", "LDAPS",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
SecurityProtocol: ldap.SecurityProtocol(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 5
|
// case 5
|
||||||
withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--skip-tls-verify",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
SkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 6
|
// case 6
|
||||||
withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--host", "ldap-server",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Host: "ldap-server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 7
|
// case 7
|
||||||
withConfig(&ldap.Source{Port: 389}, "--port", "389"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--port", "389",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Port: 389,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 8
|
// case 8
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
|
args: []string{
|
||||||
"--user-search-base", "ou=Users,dc=domain,dc=org",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--user-search-base", "ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
UserBase: "ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 9
|
// case 9
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)"},
|
args: []string{
|
||||||
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 10
|
// case 10
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
|
args: []string{
|
||||||
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 11
|
// case 11
|
||||||
withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--username-attribute", "uid",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeUsername: "uid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 12
|
// case 12
|
||||||
withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--firstname-attribute", "givenName",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeName: "givenName",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 13
|
// case 13
|
||||||
withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--surname-attribute", "sn",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeSurname: "sn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 14
|
// case 14
|
||||||
withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--email-attribute", "mail",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeMail: "mail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 15
|
// case 15
|
||||||
withConfig(&ldap.Source{AttributesInBind: true}, "--attributes-in-bind"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--attributes-in-bind",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributesInBind: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 16
|
// case 16
|
||||||
withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--public-ssh-key-attribute", "publickey",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeSSHPublicKey: "publickey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 17
|
// case 17
|
||||||
withConfig(&ldap.Source{BindDN: "cn=readonly,dc=domain,dc=org"}, "--bind-dn", "cn=readonly,dc=domain,dc=org"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--bind-dn", "cn=readonly,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
BindDN: "cn=readonly,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 18
|
// case 18
|
||||||
withConfig(&ldap.Source{BindPassword: "secret"}, "--bind-password", "secret"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--bind-password", "secret",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
BindPassword: "secret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 19
|
// case 19
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -668,7 +841,19 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// case 20
|
// case 20
|
||||||
withConfig(&ldap.Source{SearchPageSize: 12}, "--page-size", "12"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--page-size", "12",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.LDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
SearchPageSize: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 21
|
// case 21
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -779,20 +964,13 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||||
|
|
||||||
// Test cases
|
// Test cases
|
||||||
type testCase struct {
|
cases := []struct {
|
||||||
args []string
|
args []string
|
||||||
id int64
|
id int64
|
||||||
existingAuthSource *auth.Source
|
existingAuthSource *auth.Source
|
||||||
authSource *auth.Source
|
authSource *auth.Source
|
||||||
errMsg string
|
errMsg string
|
||||||
}
|
}{
|
||||||
withConfig := func(config *ldap.Source, args ...string) testCase {
|
|
||||||
return testCase{
|
|
||||||
args: append([]string{"ldap-test", "--id", "1"}, args...),
|
|
||||||
authSource: &auth.Source{Type: auth.DLDAP, Cfg: config},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cases := []testCase{
|
|
||||||
// case 0
|
// case 0
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -842,7 +1020,16 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// case 1
|
// case 1
|
||||||
withConfig(&ldap.Source{}),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 2
|
// case 2
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
@@ -877,43 +1064,187 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// case 4
|
// case 4
|
||||||
withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(2)}, "--security-protocol", "starttls"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--security-protocol", "starttls",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
SecurityProtocol: ldap.SecurityProtocol(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 5
|
// case 5
|
||||||
withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--skip-tls-verify",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
SkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 6
|
// case 6
|
||||||
withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--host", "ldap-server",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Host: "ldap-server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 7
|
// case 7
|
||||||
withConfig(&ldap.Source{Port: 987}, "--port", "987"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--port", "987",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Port: 987,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 8
|
// case 8
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
|
args: []string{
|
||||||
"--user-search-base", "ou=Users,dc=domain,dc=org",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--user-search-base", "ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
UserBase: "ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 9
|
// case 9
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{Filter: "(&(objectClass=posixAccount)(cn=%s))"},
|
args: []string{
|
||||||
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
Filter: "(&(objectClass=posixAccount)(cn=%s))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 10
|
// case 10
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
|
args: []string{
|
||||||
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 11
|
// case 11
|
||||||
withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--username-attribute", "uid",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeUsername: "uid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 12
|
// case 12
|
||||||
withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--firstname-attribute", "givenName",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeName: "givenName",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 13
|
// case 13
|
||||||
withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--surname-attribute", "sn",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeSurname: "sn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 14
|
// case 14
|
||||||
withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--email-attribute", "mail",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeMail: "mail",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 15
|
// case 15
|
||||||
withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
|
{
|
||||||
|
args: []string{
|
||||||
|
"ldap-test",
|
||||||
|
"--id", "1",
|
||||||
|
"--public-ssh-key-attribute", "publickey",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
AttributeSSHPublicKey: "publickey",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 16
|
// case 16
|
||||||
withConfig(
|
{
|
||||||
&ldap.Source{UserDN: "cn=%s,ou=Users,dc=domain,dc=org"},
|
args: []string{
|
||||||
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
|
"ldap-test",
|
||||||
),
|
"--id", "1",
|
||||||
|
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
authSource: &auth.Source{
|
||||||
|
Type: auth.DLDAP,
|
||||||
|
Cfg: &ldap.Source{
|
||||||
|
UserDN: "cn=%s,ou=Users,dc=domain,dc=org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
// case 17
|
// case 17
|
||||||
{
|
{
|
||||||
args: []string{
|
args: []string{
|
||||||
|
|||||||
+105
-49
@@ -16,11 +16,10 @@ import (
|
|||||||
|
|
||||||
func TestAddOauth(t *testing.T) {
|
func TestAddOauth(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
config *oauth2.Source
|
source *auth_model.Source
|
||||||
twoFactorPolicy string
|
errMsg string
|
||||||
errMsg string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid config",
|
name: "valid config",
|
||||||
@@ -30,11 +29,17 @@ func TestAddOauth(t *testing.T) {
|
|||||||
"--key", "some_key",
|
"--key", "some_key",
|
||||||
"--secret", "some_secret",
|
"--secret", "some_secret",
|
||||||
},
|
},
|
||||||
config: &oauth2.Source{
|
source: &auth_model.Source{
|
||||||
Scopes: []string{},
|
Type: auth_model.OAuth2,
|
||||||
Provider: "github",
|
Name: "test",
|
||||||
ClientID: "some_key",
|
IsActive: true,
|
||||||
ClientSecret: "some_secret",
|
Cfg: &oauth2.Source{
|
||||||
|
Scopes: []string{},
|
||||||
|
Provider: "github",
|
||||||
|
ClientID: "some_key",
|
||||||
|
ClientSecret: "some_secret",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,12 +51,18 @@ func TestAddOauth(t *testing.T) {
|
|||||||
"--secret", "some_secret",
|
"--secret", "some_secret",
|
||||||
"--auto-discover-url", "https://example.com",
|
"--auto-discover-url", "https://example.com",
|
||||||
},
|
},
|
||||||
config: &oauth2.Source{
|
source: &auth_model.Source{
|
||||||
Scopes: []string{},
|
Type: auth_model.OAuth2,
|
||||||
Provider: "openidConnect",
|
Name: "test",
|
||||||
ClientID: "some_key",
|
IsActive: true,
|
||||||
ClientSecret: "some_secret",
|
Cfg: &oauth2.Source{
|
||||||
OpenIDConnectAutoDiscoveryURL: "https://example.com",
|
Scopes: []string{},
|
||||||
|
Provider: "openidConnect",
|
||||||
|
ClientID: "some_key",
|
||||||
|
ClientSecret: "some_secret",
|
||||||
|
OpenIDConnectAutoDiscoveryURL: "https://example.com",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,30 +91,35 @@ func TestAddOauth(t *testing.T) {
|
|||||||
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
|
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
|
||||||
"--full-name-claim-name", "attr_full_name",
|
"--full-name-claim-name", "attr_full_name",
|
||||||
},
|
},
|
||||||
config: &oauth2.Source{
|
source: &auth_model.Source{
|
||||||
Provider: "gitlab",
|
Type: auth_model.OAuth2,
|
||||||
ClientID: "some_key",
|
Name: "test",
|
||||||
ClientSecret: "some_secret",
|
IsActive: true,
|
||||||
CustomURLMapping: &oauth2.CustomURLMapping{
|
Cfg: &oauth2.Source{
|
||||||
TokenURL: "https://example.com/token",
|
Provider: "gitlab",
|
||||||
AuthURL: "https://example.com/auth",
|
ClientID: "some_key",
|
||||||
ProfileURL: "https://example.com/profile",
|
ClientSecret: "some_secret",
|
||||||
EmailURL: "https://example.com/email",
|
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||||
Tenant: "some_tenant",
|
TokenURL: "https://example.com/token",
|
||||||
|
AuthURL: "https://example.com/auth",
|
||||||
|
ProfileURL: "https://example.com/profile",
|
||||||
|
EmailURL: "https://example.com/email",
|
||||||
|
Tenant: "some_tenant",
|
||||||
|
},
|
||||||
|
IconURL: "https://example.com/icon",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
RequiredClaimName: "claim_name",
|
||||||
|
RequiredClaimValue: "claim_value",
|
||||||
|
GroupClaimName: "group_name",
|
||||||
|
AdminGroup: "admin",
|
||||||
|
RestrictedGroup: "restricted",
|
||||||
|
GroupTeamMap: `{"group1": [1,2]}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
|
SSHPublicKeyClaimName: "attr_ssh_pub_key",
|
||||||
|
FullNameClaimName: "attr_full_name",
|
||||||
},
|
},
|
||||||
IconURL: "https://example.com/icon",
|
TwoFactorPolicy: "skip",
|
||||||
Scopes: []string{"scope1", "scope2"},
|
|
||||||
RequiredClaimName: "claim_name",
|
|
||||||
RequiredClaimValue: "claim_value",
|
|
||||||
GroupClaimName: "group_name",
|
|
||||||
AdminGroup: "admin",
|
|
||||||
RestrictedGroup: "restricted",
|
|
||||||
GroupTeamMap: `{"group1": [1,2]}`,
|
|
||||||
GroupTeamMapRemoval: true,
|
|
||||||
SSHPublicKeyClaimName: "attr_ssh_pub_key",
|
|
||||||
FullNameClaimName: "attr_full_name",
|
|
||||||
},
|
},
|
||||||
twoFactorPolicy: "skip",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,13 +150,7 @@ func TestAddOauth(t *testing.T) {
|
|||||||
assert.EqualError(t, err, tc.errMsg)
|
assert.EqualError(t, err, tc.errMsg)
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, &auth_model.Source{
|
assert.Equal(t, tc.source, createdSource)
|
||||||
Type: auth_model.OAuth2,
|
|
||||||
Name: "test",
|
|
||||||
IsActive: true,
|
|
||||||
Cfg: tc.config,
|
|
||||||
TwoFactorPolicy: tc.twoFactorPolicy,
|
|
||||||
}, createdSource)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -148,10 +158,12 @@ func TestAddOauth(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateOauth(t *testing.T) {
|
func TestUpdateOauth(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
authSource *auth_model.Source
|
id int64
|
||||||
errMsg string
|
existingAuthSource *auth_model.Source
|
||||||
|
authSource *auth_model.Source
|
||||||
|
errMsg string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing id",
|
name: "missing id",
|
||||||
@@ -162,6 +174,19 @@ func TestUpdateOauth(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid config",
|
name: "valid config",
|
||||||
|
id: 1,
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "github",
|
||||||
|
ClientID: "old_key",
|
||||||
|
ClientSecret: "old_secret",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
args: []string{
|
args: []string{
|
||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--name", "test",
|
"--name", "test",
|
||||||
@@ -185,6 +210,37 @@ func TestUpdateOauth(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid config with options",
|
name: "valid config with options",
|
||||||
|
id: 1,
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.OAuth2,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &oauth2.Source{
|
||||||
|
Provider: "gitlab",
|
||||||
|
ClientID: "old_key",
|
||||||
|
ClientSecret: "old_secret",
|
||||||
|
CustomURLMapping: &oauth2.CustomURLMapping{
|
||||||
|
TokenURL: "https://old.example.com/token",
|
||||||
|
AuthURL: "https://old.example.com/auth",
|
||||||
|
ProfileURL: "https://old.example.com/profile",
|
||||||
|
EmailURL: "https://old.example.com/email",
|
||||||
|
Tenant: "old_tenant",
|
||||||
|
},
|
||||||
|
IconURL: "https://old.example.com/icon",
|
||||||
|
Scopes: []string{"old_scope1", "old_scope2"},
|
||||||
|
RequiredClaimName: "old_claim_name",
|
||||||
|
RequiredClaimValue: "old_claim_value",
|
||||||
|
GroupClaimName: "old_group_name",
|
||||||
|
AdminGroup: "old_admin",
|
||||||
|
RestrictedGroup: "old_restricted",
|
||||||
|
GroupTeamMap: `{"old_group1": [1,2]}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
|
SSHPublicKeyClaimName: "old_ssh_pub_key",
|
||||||
|
FullNameClaimName: "old_full_name",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
args: []string{
|
args: []string{
|
||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--name", "test",
|
"--name", "test",
|
||||||
|
|||||||
@@ -132,10 +132,11 @@ func TestAddSMTP(t *testing.T) {
|
|||||||
|
|
||||||
func TestUpdateSMTP(t *testing.T) {
|
func TestUpdateSMTP(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
authSource *auth_model.Source
|
existingAuthSource *auth_model.Source
|
||||||
errMsg string
|
authSource *auth_model.Source
|
||||||
|
errMsg string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing id",
|
name: "missing id",
|
||||||
@@ -148,6 +149,17 @@ func TestUpdateSMTP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid config",
|
name: "valid config",
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "old host",
|
||||||
|
Port: 26,
|
||||||
|
},
|
||||||
|
},
|
||||||
args: []string{
|
args: []string{
|
||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--name", "test",
|
"--name", "test",
|
||||||
@@ -168,6 +180,20 @@ func TestUpdateSMTP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid config with options",
|
name: "valid config with options",
|
||||||
|
existingAuthSource: &auth_model.Source{
|
||||||
|
ID: 1,
|
||||||
|
Type: auth_model.SMTP,
|
||||||
|
Name: "old name",
|
||||||
|
IsActive: true,
|
||||||
|
Cfg: &smtp.Source{
|
||||||
|
Auth: "PLAIN",
|
||||||
|
Host: "old host",
|
||||||
|
Port: 26,
|
||||||
|
HeloHostname: "old.example.com",
|
||||||
|
AllowedDomains: "old.example.com",
|
||||||
|
},
|
||||||
|
TwoFactorPolicy: "",
|
||||||
|
},
|
||||||
args: []string{
|
args: []string{
|
||||||
"--id", "1",
|
"--id", "1",
|
||||||
"--name", "test",
|
"--name", "test",
|
||||||
|
|||||||
@@ -24,29 +24,42 @@ func TestAdminUserDelete(t *testing.T) {
|
|||||||
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
|
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
setupTestUser := func(t *testing.T) {
|
||||||
name string
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
args []string
|
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
}{
|
require.NoError(t, err)
|
||||||
{"delete user by id", []string{"delete-test", "--id", ""}},
|
|
||||||
{"delete user by username", []string{"delete-test", "--username", "testuser"}},
|
|
||||||
{"delete user by email", []string{"delete-test", "--email", "testuser@gitea.local"}},
|
|
||||||
{"delete user by all 3 attributes", []string{"delete", "--id", "", "--username", "testuser", "--email", "testuser@gitea.local"}},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
|
||||||
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if tc.args[1] == "--id" {
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
|
||||||
tc.args[2] = strconv.FormatInt(user.ID, 10)
|
|
||||||
}
|
|
||||||
err = microcmdUserDelete().Run(ctx, tc.args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("delete user by id", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by username", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by email", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
|
t.Run("delete user by all 3 attributes", func(t *testing.T) {
|
||||||
|
setupTestUser(t)
|
||||||
|
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdminUserDeleteFailure(t *testing.T) {
|
func TestAdminUserDeleteFailure(t *testing.T) {
|
||||||
|
|||||||
@@ -18,28 +18,61 @@ func TestMustChangePassword(t *testing.T) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
|
||||||
}()
|
}()
|
||||||
for _, name := range []string{"testuser", "testuserexclude"} {
|
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
|
||||||
require.NoError(t, microcmdUserCreate().Run(t.Context(), []string{"create", "--username", name, "--email", name + "@gitea.local", "--random-password"}))
|
require.NoError(t, err)
|
||||||
}
|
err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
|
||||||
for _, tc := range []struct {
|
require.NoError(t, err)
|
||||||
args []string
|
// Reset password change flag
|
||||||
assertUser, assertExcludedUser assert.BoolAssertionFunc
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
|
||||||
}{
|
require.NoError(t, err)
|
||||||
// Reset password change flag
|
|
||||||
{[]string{"change-test", "--all", "--unset"}, assert.False, assert.False},
|
testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
// Make all users change password
|
assert.False(t, testUser.MustChangePassword)
|
||||||
{[]string{"change-test", "--all"}, assert.True, assert.True},
|
testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
// Reset password change flag but exclude all tested users
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
{[]string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}, assert.True, assert.True},
|
|
||||||
// Reset password change flag by listing multiple users
|
// Make all users change password
|
||||||
{[]string{"change-test", "--unset", "testuser", "testuserexclude"}, assert.False, assert.False},
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
|
||||||
// Exclude a user from all user
|
require.NoError(t, err)
|
||||||
{[]string{"change-test", "--all", "--exclude", "testuserexclude"}, assert.True, assert.False},
|
|
||||||
// Unset a flag for single user
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
{[]string{"change-test", "--unset", "testuser"}, assert.False, assert.False},
|
assert.True(t, testUser.MustChangePassword)
|
||||||
} {
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
require.NoError(t, microcmdUserMustChangePassword().Run(t.Context(), tc.args))
|
assert.True(t, testUserExclude.MustChangePassword)
|
||||||
tc.assertUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}).MustChangePassword)
|
|
||||||
tc.assertExcludedUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}).MustChangePassword)
|
// Reset password change flag but exclude all tested users
|
||||||
}
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.True(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Reset password change flag by listing multiple users
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Exclude a user from all user
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.True(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
|
|
||||||
|
// Unset a flag for single user
|
||||||
|
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
|
||||||
|
assert.False(t, testUser.MustChangePassword)
|
||||||
|
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
|
||||||
|
assert.False(t, testUserExclude.MustChangePassword)
|
||||||
}
|
}
|
||||||
+79
-51
@@ -76,82 +76,110 @@ func TestCliCmd(t *testing.T) {
|
|||||||
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
cli.SubcommandHelpTemplate = "(subcommand help template)"
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
env map[string]string
|
env map[string]string
|
||||||
cmds []string
|
cmd string
|
||||||
exp string
|
exp string
|
||||||
}{
|
}{
|
||||||
// help commands
|
// help commands
|
||||||
{
|
{
|
||||||
cmds: []string{"./gitea -h", "./gitea help"},
|
cmd: "./gitea -h",
|
||||||
exp: "DEFAULT CONFIGURATION:",
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cmds: []string{
|
cmd: "./gitea help",
|
||||||
"./gitea -c /dev/null -h",
|
exp: "DEFAULT CONFIGURATION:",
|
||||||
"./gitea -c /dev/null help",
|
},
|
||||||
"./gitea help -c /dev/null",
|
|
||||||
"./gitea -c /dev/null test-cmd -h",
|
{
|
||||||
"./gitea test-cmd -c /dev/null -h",
|
cmd: "./gitea -c /dev/null -h",
|
||||||
"./gitea test-cmd -h -c /dev/null",
|
exp: "ConfigFile: /dev/null",
|
||||||
"./gitea -c /dev/null test-cmd help",
|
},
|
||||||
"./gitea test-cmd -c /dev/null help",
|
|
||||||
"./gitea test-cmd help -c /dev/null",
|
{
|
||||||
},
|
cmd: "./gitea -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea help -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null -h",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -h -c /dev/null",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd: "./gitea -c /dev/null test-cmd help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd -c /dev/null help",
|
||||||
|
exp: "ConfigFile: /dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd: "./gitea test-cmd help -c /dev/null",
|
||||||
exp: "ConfigFile: /dev/null",
|
exp: "ConfigFile: /dev/null",
|
||||||
},
|
},
|
||||||
|
|
||||||
// parse paths
|
// parse paths
|
||||||
{
|
{
|
||||||
cmds: []string{"./gitea test-cmd"},
|
cmd: "./gitea test-cmd",
|
||||||
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cmds: []string{"./gitea -c /tmp/app.ini test-cmd"},
|
cmd: "./gitea -c /tmp/app.ini test-cmd",
|
||||||
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cmds: []string{"./gitea test-cmd -c /tmp/app.ini"},
|
cmd: "./gitea test-cmd -c /tmp/app.ini",
|
||||||
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
cmds: []string{"./gitea test-cmd"},
|
cmd: "./gitea test-cmd",
|
||||||
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
|
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
cmds: []string{"./gitea test-cmd --work-path /tmp/other"},
|
cmd: "./gitea test-cmd --work-path /tmp/other",
|
||||||
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
|
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||||
cmds: []string{"./gitea test-cmd --config /tmp/app-other.ini"},
|
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
|
||||||
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
|
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
for _, command := range c.cmds {
|
t.Run(c.cmd, func(t *testing.T) {
|
||||||
t.Run(command, func(t *testing.T) {
|
app := newTestApp(cli.Command{
|
||||||
app := newTestApp(cli.Command{
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||||
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
return nil
|
||||||
return nil
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
for k, v := range c.env {
|
|
||||||
t.Setenv(k, v)
|
|
||||||
}
|
|
||||||
args := strings.Split(command, " ") // for test only, "split" is good enough
|
|
||||||
r, err := runTestApp(app, args...)
|
|
||||||
assert.NoError(t, err, command)
|
|
||||||
assert.NotEmpty(t, c.exp, command)
|
|
||||||
if !assert.Contains(t, r.Stdout, c.exp, command) {
|
|
||||||
t.Log("Full output:\n" + r.Stdout)
|
|
||||||
t.Log("Expected:\n" + c.exp)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
for k, v := range c.env {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
|
||||||
|
r, err := runTestApp(app, args...)
|
||||||
|
assert.NoError(t, err, c.cmd)
|
||||||
|
assert.NotEmpty(t, c.exp, c.cmd)
|
||||||
|
if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
|
||||||
|
t.Log("Full output:\n" + r.Stdout)
|
||||||
|
t.Log("Expected:\n" + c.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -13,11 +13,13 @@ import (
|
|||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/dump"
|
"gitea.dev/modules/dump"
|
||||||
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/storage"
|
"gitea.dev/modules/storage"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
|
"gitea.com/go-chi/session"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -247,8 +249,12 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
|
|||||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||||
|
|
||||||
var excludes []string
|
var excludes []string
|
||||||
if setting.SessionConfig.Provider == "file" {
|
if setting.SessionConfig.OriginalProvider == "file" {
|
||||||
excludes = append(excludes, setting.SessionConfig.ProviderConfig)
|
var opts session.Options
|
||||||
|
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
excludes = append(excludes, opts.ProviderConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
|
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
|
||||||
|
|||||||
+1
-3
@@ -33,7 +33,7 @@ func newDumpRepositoryCommand() *cli.Command {
|
|||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "git_service",
|
Name: "git_service",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Git service, git, github, gitea, forgejo, gitlab. If clone_addr could be recognized, this could be ignored.",
|
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "repo_dir",
|
Name: "repo_dir",
|
||||||
@@ -117,8 +117,6 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
|
|||||||
serviceStr = "gitlab"
|
serviceStr = "gitlab"
|
||||||
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
|
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
|
||||||
serviceStr = "gitea"
|
serviceStr = "gitea"
|
||||||
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://codeberg.org/") {
|
|
||||||
serviceStr = "forgejo"
|
|
||||||
}
|
}
|
||||||
if serviceStr == "" {
|
if serviceStr == "" {
|
||||||
return errors.New("git_service missed or clone_addr cannot be recognized")
|
return errors.New("git_service missed or clone_addr cannot be recognized")
|
||||||
|
|||||||
+50
-14
@@ -169,6 +169,10 @@ func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
|
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
|
||||||
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
setup(ctx, c.Bool("debug"))
|
setup(ctx, c.Bool("debug"))
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
@@ -209,6 +213,7 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
refFullNames := make([]git.RefName, hookBatchSize)
|
refFullNames := make([]git.RefName, hookBatchSize)
|
||||||
count := 0
|
count := 0
|
||||||
total := 0
|
total := 0
|
||||||
|
lastline := 0
|
||||||
|
|
||||||
out := io.Discard
|
out := io.Discard
|
||||||
if setting.Git.VerbosePush {
|
if setting.Git.VerbosePush {
|
||||||
@@ -221,6 +226,8 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// TODO: support news feeds for wiki
|
// TODO: support news feeds for wiki
|
||||||
if isWiki {
|
if isWiki {
|
||||||
@@ -233,23 +240,37 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
}
|
}
|
||||||
|
|
||||||
total++
|
total++
|
||||||
oldCommitIDs[count] = oldCommitID
|
lastline++
|
||||||
newCommitIDs[count] = newCommitID
|
|
||||||
refFullNames[count] = refFullName
|
|
||||||
count++
|
|
||||||
fmt.Fprintf(out, "*")
|
|
||||||
|
|
||||||
if count >= hookBatchSize {
|
// If the ref is a branch or tag, check if it's protected
|
||||||
fmt.Fprintf(out, " Checking %d references\n", count)
|
// if supportProcReceive all ref should be checked because
|
||||||
|
// permission check was delayed
|
||||||
|
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
|
||||||
|
oldCommitIDs[count] = oldCommitID
|
||||||
|
newCommitIDs[count] = newCommitID
|
||||||
|
refFullNames[count] = refFullName
|
||||||
|
count++
|
||||||
|
fmt.Fprintf(out, "*")
|
||||||
|
|
||||||
hookOptions.OldCommitIDs = oldCommitIDs
|
if count >= hookBatchSize {
|
||||||
hookOptions.NewCommitIDs = newCommitIDs
|
fmt.Fprintf(out, " Checking %d references\n", count)
|
||||||
hookOptions.RefFullNames = refFullNames
|
|
||||||
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
|
hookOptions.OldCommitIDs = oldCommitIDs
|
||||||
if extra.HasError() {
|
hookOptions.NewCommitIDs = newCommitIDs
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
hookOptions.RefFullNames = refFullNames
|
||||||
|
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
|
||||||
|
if extra.HasError() {
|
||||||
|
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
|
||||||
|
}
|
||||||
|
count = 0
|
||||||
|
lastline = 0
|
||||||
}
|
}
|
||||||
count = 0
|
} else {
|
||||||
|
fmt.Fprintf(out, ".")
|
||||||
|
}
|
||||||
|
if lastline >= hookBatchSize {
|
||||||
|
fmt.Fprintf(out, "\n")
|
||||||
|
lastline = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
@@ -267,6 +288,8 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
|
} else if lastline > 0 {
|
||||||
|
fmt.Fprintf(out, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
fmt.Fprintf(out, "Checked %d references in total\n", total)
|
||||||
@@ -276,6 +299,10 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
// runHookUpdate avoid to do heavy operations on update hook because it will be
|
||||||
// invoked for every ref update which does not like pre-receive and post-receive
|
// invoked for every ref update which does not like pre-receive and post-receive
|
||||||
func runHookUpdate(_ context.Context, c *cli.Command) error {
|
func runHookUpdate(_ context.Context, c *cli.Command) error {
|
||||||
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update is empty and is kept only for backwards compatibility
|
// Update is empty and is kept only for backwards compatibility
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
return nil
|
return nil
|
||||||
@@ -296,6 +323,11 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
|||||||
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now if we're an internal don't do anything else
|
||||||
|
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
|
||||||
return fail(ctx, `Rejecting changes as Gitea environment not set.
|
return fail(ctx, `Rejecting changes as Gitea environment not set.
|
||||||
@@ -443,6 +475,10 @@ Gitea or set your environment appropriately.`, "")
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !git.DefaultFeatures().SupportProcReceive {
|
||||||
|
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
||||||
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
repoUser := os.Getenv(repo_module.EnvRepoUsername)
|
||||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||||
|
|||||||
+6
-4
@@ -195,10 +195,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(sshCmdArgs) < 2 {
|
if len(sshCmdArgs) < 2 {
|
||||||
// for AGit Flow
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
if cmd == "ssh_info" {
|
// for AGit Flow
|
||||||
cprintf(c, "%s", agit.SshInfoJson)
|
if cmd == "ssh_info" {
|
||||||
return nil
|
cprintf(c, "%s", agit.SshInfoJson)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-34
@@ -155,7 +155,7 @@
|
|||||||
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
|
||||||
;BUILTIN_SSH_SERVER_USER =
|
;BUILTIN_SSH_SERVER_USER =
|
||||||
;;
|
;;
|
||||||
;; Domain name to be exposed in clone URL, defaults to the domain part of ROOT_URL
|
;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
|
||||||
;SSH_DOMAIN =
|
;SSH_DOMAIN =
|
||||||
;;
|
;;
|
||||||
;; Port number to be exposed in clone URL.
|
;; Port number to be exposed in clone URL.
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
;; For the built-in SSH server, choose the keypair to offer as the host key
|
;; For the built-in SSH server, choose the keypair to offer as the host key
|
||||||
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
|
||||||
;; relative paths are made absolute relative to the APP_DATA_PATH
|
;; relative paths are made absolute relative to the APP_DATA_PATH
|
||||||
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gitea.ed25519, ssh/gitea.ecdsa, ssh/gogs.rsa
|
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
|
||||||
;;
|
;;
|
||||||
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
|
||||||
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
;SSH_AUTHORIZED_KEYS_BACKUP = false
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
|
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
|
||||||
;;
|
;;
|
||||||
;; Indicate whether to check minimum key size with corresponding type
|
;; Indicate whether to check minimum key size with corresponding type
|
||||||
;MINIMUM_KEY_SIZE_CHECK = true
|
;MINIMUM_KEY_SIZE_CHECK = false
|
||||||
;;
|
;;
|
||||||
;; TLS Settings: Either ACME or manual
|
;; TLS Settings: Either ACME or manual
|
||||||
;; (Other common TLS configuration are found before)
|
;; (Other common TLS configuration are found before)
|
||||||
@@ -459,7 +459,7 @@ INTERNAL_TOKEN =
|
|||||||
;LOGIN_REMEMBER_DAYS = 31
|
;LOGIN_REMEMBER_DAYS = 31
|
||||||
;;
|
;;
|
||||||
;; Name of cookie used to store authentication information.
|
;; Name of cookie used to store authentication information.
|
||||||
;COOKIE_REMEMBER_NAME = gitea_remember
|
;COOKIE_REMEMBER_NAME = gitea_incredible
|
||||||
;;
|
;;
|
||||||
;; URL or path that Gitea should redirect users to *after* performing its own logout.
|
;; URL or path that Gitea should redirect users to *after* performing its own logout.
|
||||||
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
|
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
|
||||||
@@ -471,8 +471,7 @@ INTERNAL_TOKEN =
|
|||||||
;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL
|
;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL
|
||||||
;REVERSE_PROXY_AUTHENTICATION_FULL_NAME = X-WEBAUTH-FULLNAME
|
;REVERSE_PROXY_AUTHENTICATION_FULL_NAME = X-WEBAUTH-FULLNAME
|
||||||
;;
|
;;
|
||||||
;; Interpret the X-Real-IP, X-Forwarded-For or Forwarded header, in that order, and set this as the remote IP for the request.
|
;; Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request
|
||||||
;; The client port is taken from the header value or, with X-Real-IP, from X-Real-Port.
|
|
||||||
;REVERSE_PROXY_LIMIT = 1
|
;REVERSE_PROXY_LIMIT = 1
|
||||||
;;
|
;;
|
||||||
;; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
|
;; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
|
||||||
@@ -784,6 +783,8 @@ LEVEL = Info
|
|||||||
;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
|
;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
|
||||||
;PULL_REQUEST_PUSH_MESSAGE = true
|
;PULL_REQUEST_PUSH_MESSAGE = true
|
||||||
;;
|
;;
|
||||||
|
;; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.)
|
||||||
|
;LARGE_OBJECT_THRESHOLD = 1048576
|
||||||
;; Set to true to forcibly set core.protectNTFS=false
|
;; Set to true to forcibly set core.protectNTFS=false
|
||||||
;DISABLE_CORE_PROTECT_NTFS=false
|
;DISABLE_CORE_PROTECT_NTFS=false
|
||||||
;; Disable the usage of using partial clones for git.
|
;; Disable the usage of using partial clones for git.
|
||||||
@@ -837,7 +838,7 @@ LEVEL = Info
|
|||||||
;EMAIL_DOMAIN_BLOCKLIST =
|
;EMAIL_DOMAIN_BLOCKLIST =
|
||||||
;;
|
;;
|
||||||
;; Disallow registration, only allow admins to create accounts.
|
;; Disallow registration, only allow admins to create accounts.
|
||||||
;DISABLE_REGISTRATION = true
|
;DISABLE_REGISTRATION = false
|
||||||
;;
|
;;
|
||||||
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
|
||||||
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
|
||||||
@@ -866,10 +867,9 @@ LEVEL = Info
|
|||||||
;; Allow users to sign-in with a passkey
|
;; Allow users to sign-in with a passkey
|
||||||
;ENABLE_PASSKEY_AUTHENTICATION = true
|
;ENABLE_PASSKEY_AUTHENTICATION = true
|
||||||
;;
|
;;
|
||||||
;; Reverse proxy authentication for the web interface, Git over HTTP, Git LFS and the package registry.
|
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||||
;; Gitea must only be reachable through the proxy, which must set or remove the authentication headers on every request.
|
|
||||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||||
;; Reverse proxy authentication for the REST API under /api/v1.
|
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION_API = false
|
;ENABLE_REVERSE_PROXY_AUTHENTICATION_API = false
|
||||||
;ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
;ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||||
;ENABLE_REVERSE_PROXY_EMAIL = false
|
;ENABLE_REVERSE_PROXY_EMAIL = false
|
||||||
@@ -970,11 +970,12 @@ LEVEL = Info
|
|||||||
;; Value for the domain part of the user's email address in the git log if user
|
;; Value for the domain part of the user's email address in the git log if user
|
||||||
;; has set KeepEmailPrivate to true. The user's email will be replaced with a
|
;; has set KeepEmailPrivate to true. The user's email will be replaced with a
|
||||||
;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
|
;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
|
||||||
;; value is "noreply." + the domain part of ROOT_URL
|
;; value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
|
||||||
;NO_REPLY_ADDRESS =
|
;; Note: do not use the <DOMAIN> notation below
|
||||||
|
;NO_REPLY_ADDRESS = ; noreply.<DOMAIN>
|
||||||
;;
|
;;
|
||||||
;; Show Registration button, defaults to true only if both DISABLE_REGISTRATION and ALLOW_ONLY_EXTERNAL_REGISTRATION are false
|
;; Show Registration button
|
||||||
;SHOW_REGISTRATION_BUTTON = false
|
;SHOW_REGISTRATION_BUTTON = true
|
||||||
;;
|
;;
|
||||||
;; Show milestones dashboard page - a view of all the user's milestones
|
;; Show milestones dashboard page - a view of all the user's milestones
|
||||||
;SHOW_MILESTONES_DASHBOARD_PAGE = true
|
;SHOW_MILESTONES_DASHBOARD_PAGE = true
|
||||||
@@ -1220,9 +1221,8 @@ LEVEL = Info
|
|||||||
;; In default merge messages only include approvers who are official
|
;; In default merge messages only include approvers who are official
|
||||||
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
|
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
|
||||||
;;
|
;;
|
||||||
;; Set default squash merge commit message body, valid options: PR_BODY (pull request description), COMMIT_MESSAGES (commit messages), BLANK (none)
|
;; In default squash-merge messages include the commit message of all commits comprising the pull request.
|
||||||
;; Repositories store the current value when created or when their settings are next saved, and can change it there.
|
;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
|
||||||
;DEFAULT_SQUASH_MERGE_MESSAGE = PR_BODY
|
|
||||||
;;
|
;;
|
||||||
;; Add co-authored-by and co-committed-by trailers if committer does not match author
|
;; Add co-authored-by and co-committed-by trailers if committer does not match author
|
||||||
;ADD_CO_COMMITTER_TRAILERS = true
|
;ADD_CO_COMMITTER_TRAILERS = true
|
||||||
@@ -1436,14 +1436,11 @@ LEVEL = Info
|
|||||||
;; The icon theme for folders (basic/material)
|
;; The icon theme for folders (basic/material)
|
||||||
;FOLDER_ICON_THEME = basic
|
;FOLDER_ICON_THEME = basic
|
||||||
;;
|
;;
|
||||||
;; All available reactions users can choose on issues, pull requests and comments.
|
;; All available reactions users can choose on issues/prs and comments.
|
||||||
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
||||||
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
||||||
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
|
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
|
||||||
;;
|
;;
|
||||||
;; Reactions available for releases
|
|
||||||
;;RELEASE_REACTIONS = +1, laugh, hooray, heart, rocket, eyes
|
|
||||||
;;
|
|
||||||
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
|
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
|
||||||
;REACTION_MAX_USER_NUM = 10
|
;REACTION_MAX_USER_NUM = 10
|
||||||
;;
|
;;
|
||||||
@@ -1676,13 +1673,13 @@ LEVEL = Info
|
|||||||
;;
|
;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; General queue type, currently support: level, channel, redis, dummy
|
;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy
|
||||||
;; default to level
|
;; default to persistable-channel
|
||||||
;TYPE = level
|
;TYPE = persistable-channel
|
||||||
;;
|
;;
|
||||||
;; data-dir for storing level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
|
||||||
;; Relative paths will be made absolute against "APP_DATA_PATH"
|
;; Relative paths will be made absolute against "APP_DATA_PATH"
|
||||||
;DATADIR = queues/common
|
;DATADIR = queues/
|
||||||
;;
|
;;
|
||||||
;; Default queue length before a channel queue will block
|
;; Default queue length before a channel queue will block
|
||||||
;LENGTH = 100000
|
;LENGTH = 100000
|
||||||
@@ -1690,7 +1687,7 @@ LEVEL = Info
|
|||||||
;; Batch size to send for batched queues
|
;; Batch size to send for batched queues
|
||||||
;BATCH_LENGTH = 20
|
;BATCH_LENGTH = 20
|
||||||
;;
|
;;
|
||||||
;; When `TYPE` is `level`, this provides a directory for the underlying leveldb
|
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
|
||||||
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
|
||||||
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
|
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
|
||||||
;CONN_STR =
|
;CONN_STR =
|
||||||
@@ -1758,6 +1755,7 @@ LEVEL = Info
|
|||||||
;ENABLE_OPENID_SIGNIN = false
|
;ENABLE_OPENID_SIGNIN = false
|
||||||
;;
|
;;
|
||||||
;; Whether to allow registering via OpenID
|
;; Whether to allow registering via OpenID
|
||||||
|
;; Do not include to rely on rhw DISABLE_REGISTRATION setting
|
||||||
;;ENABLE_OPENID_SIGNUP = false
|
;;ENABLE_OPENID_SIGNUP = false
|
||||||
;;
|
;;
|
||||||
;; Allowed URI patterns (POSIX regexp).
|
;; Allowed URI patterns (POSIX regexp).
|
||||||
@@ -1988,7 +1986,7 @@ LEVEL = Info
|
|||||||
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
|
||||||
;ADAPTER = memory
|
;ADAPTER = memory
|
||||||
;;
|
;;
|
||||||
;; For "memory" and "twoqueue", GC interval in seconds, default is 60
|
;; For "memory" only, GC interval in seconds, default is 60
|
||||||
;INTERVAL = 60
|
;INTERVAL = 60
|
||||||
;;
|
;;
|
||||||
;; For "redis" and "memcache", connection host address
|
;; For "redis" and "memcache", connection host address
|
||||||
@@ -2019,17 +2017,19 @@ LEVEL = Info
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Either "memory", "file", "redis" or "db", default is "file". "db" will reuse the configuration in [database]
|
;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
|
||||||
;PROVIDER = file
|
;; Default is "memory". "db" will reuse the configuration in [database]
|
||||||
|
;PROVIDER = memory
|
||||||
;;
|
;;
|
||||||
;; Provider config options
|
;; Provider config options
|
||||||
;; memory: doesn't have any config yet
|
;; memory: doesn't have any config yet
|
||||||
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
|
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
|
||||||
;; redis: default to [redis] CONN_STR
|
;; redis: default to [redis] CONN_STR
|
||||||
|
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
|
||||||
;PROVIDER_CONFIG =
|
;PROVIDER_CONFIG =
|
||||||
;;
|
;;
|
||||||
;; Session cookie name
|
;; Session cookie name
|
||||||
;COOKIE_NAME = gitea_session
|
;COOKIE_NAME = i_like_gitea
|
||||||
;;
|
;;
|
||||||
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||||
;COOKIE_SECURE =
|
;COOKIE_SECURE =
|
||||||
@@ -2055,8 +2055,7 @@ LEVEL = Info
|
|||||||
;; How Gitea deals with missing repository avatars
|
;; How Gitea deals with missing repository avatars
|
||||||
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
|
||||||
;REPOSITORY_AVATAR_FALLBACK = none
|
;REPOSITORY_AVATAR_FALLBACK = none
|
||||||
;; Image URL for the "image" fallback, used as-is, defaults to Gitea's builtin repository avatar
|
;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
|
||||||
;REPOSITORY_AVATAR_FALLBACK_IMAGE =
|
|
||||||
;;
|
;;
|
||||||
;; Max Width and Height of uploaded avatars.
|
;; Max Width and Height of uploaded avatars.
|
||||||
;; This is to limit the amount of RAM used when resizing the image.
|
;; This is to limit the amount of RAM used when resizing the image.
|
||||||
@@ -3084,7 +3083,7 @@ LEVEL = Info
|
|||||||
;LOG_RETENTION_DAYS = 365
|
;LOG_RETENTION_DAYS = 365
|
||||||
;; Days to keep artifacts. Old artifacts will be deleted after this period. 0 means keep forever.
|
;; Days to keep artifacts. Old artifacts will be deleted after this period. 0 means keep forever.
|
||||||
;; Changes only apply to newly uploaded artifacts, existing ones keep the expiry stored when they were uploaded.
|
;; Changes only apply to newly uploaded artifacts, existing ones keep the expiry stored when they were uploaded.
|
||||||
;; Workflows can request a shorter retention period, down to 1 day.
|
;; Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
|
||||||
;ARTIFACT_RETENTION_DAYS = 90
|
;ARTIFACT_RETENTION_DAYS = 90
|
||||||
;; Max artifact size in bytes that can be browsed or previewed in the web UI. Set to 0 to disable artifact previews, or -1 for no limit.
|
;; Max artifact size in bytes that can be browsed or previewed in the web UI. Set to 0 to disable artifact previews, or -1 for no limit.
|
||||||
;ARTIFACT_PREVIEW_MAX_SIZE = 10485760
|
;ARTIFACT_PREVIEW_MAX_SIZE = 10485760
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Depending on requirements, the following build tags can be included.
|
|||||||
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
|
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
|
||||||
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
|
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
|
||||||
Can be used to authenticate local users or extend authentication to methods available to PAM.
|
Can be used to authenticate local users or extend authentication to methods available to PAM.
|
||||||
|
- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands.
|
||||||
|
|
||||||
To include all assets, use the `bindata` tag:
|
To include all assets, use the `bindata` tag:
|
||||||
|
|
||||||
@@ -32,6 +33,13 @@ To include all assets, use the `bindata` tag:
|
|||||||
TAGS="bindata" make build
|
TAGS="bindata" make build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it.
|
||||||
|
You can build a Windows binary by:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GOOS=windows TAGS="bindata gogit" make build
|
||||||
|
```
|
||||||
|
|
||||||
## Changing default paths
|
## Changing default paths
|
||||||
|
|
||||||
Gitea will search for a number of things from the _`CustomPath`_.
|
Gitea will search for a number of things from the _`CustomPath`_.
|
||||||
|
|||||||
+1
-4
@@ -105,13 +105,10 @@ When adding configuration options it is not enough to add them to the
|
|||||||
[`custom/conf/app.example.ini`](../custom/conf/app.example.ini), and document them in
|
[`custom/conf/app.example.ini`](../custom/conf/app.example.ini), and document them in
|
||||||
the [configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet),
|
the [configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet),
|
||||||
which lives in the [documentation repository](https://gitea.com/gitea/docs).
|
which lives in the [documentation repository](https://gitea.com/gitea/docs).
|
||||||
Every `app.example.ini` change needs a companion PR there that copies the ini text
|
|
||||||
verbatim into every affected version's cheat sheet, translated where feasible, with a
|
|
||||||
`WIP:` title prefix until the Gitea PR merges.
|
|
||||||
|
|
||||||
## Database migrations
|
## Database migrations
|
||||||
|
|
||||||
Avoid migrations unless the alternative is a workaround. If you make breaking changes to a database-persisted struct under `models/`, add a
|
If you make breaking changes to a database-persisted struct under `models/`, add a
|
||||||
new migration in `modelmigration/`. See [testing.md](testing.md#migration-tests)
|
new migration in `modelmigration/`. See [testing.md](testing.md#migration-tests)
|
||||||
for running the migration tests.
|
for running the migration tests.
|
||||||
|
|
||||||
|
|||||||
@@ -74,19 +74,6 @@ conditional `UPDATE` or a lock.
|
|||||||
plain `Sync`.
|
plain `Sync`.
|
||||||
- When inserting rows with preset IDs, MSSQL requires `SET IDENTITY_INSERT` to be
|
- When inserting rows with preset IDs, MSSQL requires `SET IDENTITY_INSERT` to be
|
||||||
enabled and PostgreSQL requires the sequence to be updated afterwards.
|
enabled and PostgreSQL requires the sequence to be updated afterwards.
|
||||||
- Struct conditions skip zero-valued fields, so `Get(&Reaction{IssueID: 1})` also
|
|
||||||
matches comment reactions. Use `Where` or `builder` when a zero value matters.
|
|
||||||
- Struct updates skip zero-valued fields unless selected by `Cols` or `AllCols()`.
|
|
||||||
`Cols` silently ignores unknown names. `updated` columns are always written unless
|
|
||||||
`NoAutoTime()` is used.
|
|
||||||
- `xorm:"deleted"` columns count both `0` and `NULL` as not deleted, so hand-written
|
|
||||||
conditions must accept both.
|
|
||||||
- On every start, `SyncAllTables` drops indexes absent from the model, so declare
|
|
||||||
migration-created indexes there, including in backports. It only warns about
|
|
||||||
nullability and default changes, so apply them through migrations.
|
|
||||||
- `ForUpdate` only works on MySQL and PostgreSQL. MSSQL rejects TEXT columns in
|
|
||||||
struct conditions and inserts of more than 1000 rows.
|
|
||||||
- Paginated queries need a unique tiebreaker like `id` for a stable order.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ Run a single integration test with the `#` selector:
|
|||||||
make test-integration#TestName
|
make test-integration#TestName
|
||||||
```
|
```
|
||||||
|
|
||||||
Each run gets its own work directory under `tests/integration/` and random ports, so SQLite runs
|
|
||||||
can execute in parallel. Runs against other databases still share the database and object storage.
|
|
||||||
The work directory is removed after a passing run and kept after a failure.
|
|
||||||
|
|
||||||
If you hit errors such as a mismatched database version or SSH push failures, try a
|
If you hit errors such as a mismatched database version or SSH push failures, try a
|
||||||
clean rebuild first:
|
clean rebuild first:
|
||||||
|
|
||||||
|
|||||||
+29
-55
@@ -17,7 +17,6 @@ import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts
|
|||||||
|
|
||||||
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
const jsExts = ['js', 'mjs', 'cjs'] as const;
|
||||||
const tsExts = ['ts', 'mts', 'cts'] as const;
|
const tsExts = ['ts', 'mts', 'cts'] as const;
|
||||||
const vueExts = ['vue'] as const;
|
|
||||||
|
|
||||||
const restrictedGlobals = [
|
const restrictedGlobals = [
|
||||||
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
|
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
|
||||||
@@ -37,7 +36,7 @@ export default defineConfig([
|
|||||||
'public/assets/js',
|
'public/assets/js',
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
files: [`**/*.{${[...jsExts, ...tsExts, ...vueExts].join(',')}}`],
|
files: [`**/*.{${[...jsExts, ...tsExts].join(',')}}`],
|
||||||
ignores: ['dist/*'],
|
ignores: ['dist/*'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
@@ -49,9 +48,7 @@ export default defineConfig([
|
|||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
impliedStrict: true,
|
impliedStrict: true,
|
||||||
},
|
},
|
||||||
parser: typescriptParser,
|
|
||||||
project: true,
|
project: true,
|
||||||
extraFileExtensions: vueExts.map((ext) => `.${ext}`),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
linterOptions: {
|
linterOptions: {
|
||||||
@@ -69,11 +66,8 @@ export default defineConfig([
|
|||||||
wc,
|
wc,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
'import-x/extensions': [...jsExts, ...tsExts, ...vueExts].map((ext) => `.${ext}`),
|
'import-x/extensions': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
|
||||||
'import-x/parsers': {
|
'import-x/parsers': {'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`)},
|
||||||
'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
|
|
||||||
'vue-eslint-parser': vueExts.map((ext) => `.${ext}`),
|
|
||||||
},
|
|
||||||
'import-x/resolver': {'typescript': true},
|
'import-x/resolver': {'typescript': true},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
@@ -239,7 +233,6 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/no-unsafe-assignment': [0],
|
'@typescript-eslint/no-unsafe-assignment': [0],
|
||||||
'@typescript-eslint/no-unsafe-call': [0],
|
'@typescript-eslint/no-unsafe-call': [0],
|
||||||
'@typescript-eslint/no-unsafe-declaration-merging': [2],
|
'@typescript-eslint/no-unsafe-declaration-merging': [2],
|
||||||
'@typescript-eslint/no-unsafe-enum-assignment': [0],
|
|
||||||
'@typescript-eslint/no-unsafe-enum-comparison': [0],
|
'@typescript-eslint/no-unsafe-enum-comparison': [0],
|
||||||
'@typescript-eslint/no-unsafe-function-type': [2],
|
'@typescript-eslint/no-unsafe-function-type': [2],
|
||||||
'@typescript-eslint/no-unsafe-member-access': [0],
|
'@typescript-eslint/no-unsafe-member-access': [0],
|
||||||
@@ -311,7 +304,6 @@ export default defineConfig([
|
|||||||
'func-names': [0],
|
'func-names': [0],
|
||||||
'func-style': [0],
|
'func-style': [0],
|
||||||
'getter-return': [2],
|
'getter-return': [2],
|
||||||
'gitea/unescaped-html-literal': [2],
|
|
||||||
'grouped-accessor-pairs': [2],
|
'grouped-accessor-pairs': [2],
|
||||||
'guard-for-in': [0],
|
'guard-for-in': [0],
|
||||||
'id-denylist': [0],
|
'id-denylist': [0],
|
||||||
@@ -712,7 +704,6 @@ export default defineConfig([
|
|||||||
'unicorn/better-dom-traversing': [2],
|
'unicorn/better-dom-traversing': [2],
|
||||||
'unicorn/catch-error-name': [0],
|
'unicorn/catch-error-name': [0],
|
||||||
'unicorn/class-reference-in-static-methods': [2],
|
'unicorn/class-reference-in-static-methods': [2],
|
||||||
'unicorn/comma-spacing': [0], // only applies to json language
|
|
||||||
'unicorn/comment-content': [0],
|
'unicorn/comment-content': [0],
|
||||||
'unicorn/consistent-arrow-return-style': [0],
|
'unicorn/consistent-arrow-return-style': [0],
|
||||||
'unicorn/consistent-assert': [0],
|
'unicorn/consistent-assert': [0],
|
||||||
@@ -743,10 +734,8 @@ export default defineConfig([
|
|||||||
'unicorn/filename-case': [0],
|
'unicorn/filename-case': [0],
|
||||||
'unicorn/id-match': [2],
|
'unicorn/id-match': [2],
|
||||||
'unicorn/import-style': [0],
|
'unicorn/import-style': [0],
|
||||||
'unicorn/indent': [0], // only applies to json and css languages
|
|
||||||
'unicorn/isolated-functions': [2, {functions: []}],
|
'unicorn/isolated-functions': [2, {functions: []}],
|
||||||
'unicorn/iteration-fallback-style': [2, 'fallback'],
|
'unicorn/iteration-fallback-style': [2, 'fallback'],
|
||||||
'unicorn/key-name-casing': [0], // only applies to json, yaml and toml languages
|
|
||||||
'unicorn/logical-assignment-operators': [0],
|
'unicorn/logical-assignment-operators': [0],
|
||||||
'unicorn/max-nested-calls': [0],
|
'unicorn/max-nested-calls': [0],
|
||||||
'unicorn/name-replacements': [0],
|
'unicorn/name-replacements': [0],
|
||||||
@@ -779,20 +768,21 @@ export default defineConfig([
|
|||||||
'unicorn/no-chained-comparison': [2],
|
'unicorn/no-chained-comparison': [2],
|
||||||
'unicorn/no-collection-bracket-access': [2],
|
'unicorn/no-collection-bracket-access': [2],
|
||||||
'unicorn/no-computed-property-existence-check': [0],
|
'unicorn/no-computed-property-existence-check': [0],
|
||||||
'unicorn/no-conflicting-constraints': [2],
|
|
||||||
'unicorn/no-confusing-array-splice': [2],
|
'unicorn/no-confusing-array-splice': [2],
|
||||||
'unicorn/no-confusing-array-with': [2],
|
'unicorn/no-confusing-array-with': [2],
|
||||||
'unicorn/no-console-spaces': [0],
|
'unicorn/no-console-spaces': [0],
|
||||||
'unicorn/no-constant-zero-expression': [2],
|
'unicorn/no-constant-zero-expression': [2],
|
||||||
'unicorn/no-declarations-before-early-exit': [0],
|
'unicorn/no-declarations-before-early-exit': [0],
|
||||||
|
'unicorn/no-deprecated-css-features': [0],
|
||||||
'unicorn/no-document-cookie': [2],
|
'unicorn/no-document-cookie': [2],
|
||||||
'unicorn/no-double-comparison': [2],
|
'unicorn/no-double-comparison': [2],
|
||||||
|
'unicorn/no-duplicate-css-selectors': [0],
|
||||||
|
'unicorn/no-duplicate-font-family-names': [0],
|
||||||
'unicorn/no-duplicate-if-branches': [2],
|
'unicorn/no-duplicate-if-branches': [2],
|
||||||
'unicorn/no-duplicate-logical-operands': [2],
|
'unicorn/no-duplicate-logical-operands': [2],
|
||||||
'unicorn/no-duplicate-loops': [0],
|
'unicorn/no-duplicate-loops': [0],
|
||||||
'unicorn/no-duplicate-set-values': [2],
|
'unicorn/no-duplicate-set-values': [2],
|
||||||
'unicorn/no-empty-file': [2],
|
'unicorn/no-empty-file': [2],
|
||||||
'unicorn/no-empty-link-text': [0], // only applies to markdown language
|
|
||||||
'unicorn/no-error-property-assignment': [2],
|
'unicorn/no-error-property-assignment': [2],
|
||||||
'unicorn/no-exports-in-scripts': [2],
|
'unicorn/no-exports-in-scripts': [2],
|
||||||
'unicorn/no-for-each': [2],
|
'unicorn/no-for-each': [2],
|
||||||
@@ -800,34 +790,21 @@ export default defineConfig([
|
|||||||
'unicorn/no-global-object-property-assignment': [0],
|
'unicorn/no-global-object-property-assignment': [0],
|
||||||
'unicorn/no-immediate-mutation': [0],
|
'unicorn/no-immediate-mutation': [0],
|
||||||
'unicorn/no-impossible-length-comparison': [2],
|
'unicorn/no-impossible-length-comparison': [2],
|
||||||
'unicorn/no-incomplete-accessor-override': [2],
|
|
||||||
'unicorn/no-incorrect-query-selector': [2],
|
'unicorn/no-incorrect-query-selector': [2],
|
||||||
'unicorn/no-incorrect-template-string-interpolation': [0],
|
'unicorn/no-incorrect-template-string-interpolation': [0],
|
||||||
'unicorn/no-ineffective-csp-directives': [2],
|
|
||||||
'unicorn/no-instanceof-builtins': [2],
|
'unicorn/no-instanceof-builtins': [2],
|
||||||
'unicorn/no-invalid-argument-count': [2],
|
'unicorn/no-invalid-argument-count': [2],
|
||||||
'unicorn/no-invalid-boolean-attribute-value': [2],
|
|
||||||
'unicorn/no-invalid-character-comparison': [2],
|
'unicorn/no-invalid-character-comparison': [2],
|
||||||
'unicorn/no-invalid-dom-token': [2],
|
|
||||||
'unicorn/no-invalid-fetch-options': [2],
|
'unicorn/no-invalid-fetch-options': [2],
|
||||||
'unicorn/no-invalid-file-input-accept': [2],
|
'unicorn/no-invalid-file-input-accept': [2],
|
||||||
'unicorn/no-invalid-integrity': [2],
|
'unicorn/no-invalid-media-features': [0],
|
||||||
'unicorn/no-invalid-intl-options': [2],
|
|
||||||
'unicorn/no-invalid-property-descriptor': [2],
|
|
||||||
'unicorn/no-invalid-remove-event-listener': [2],
|
'unicorn/no-invalid-remove-event-listener': [2],
|
||||||
'unicorn/no-invalid-response-options': [2],
|
|
||||||
'unicorn/no-invalid-style-set-property': [2],
|
|
||||||
'unicorn/no-invalid-temporal-arithmetic': [2],
|
|
||||||
'unicorn/no-invalid-url-protocol-comparison': [2],
|
|
||||||
'unicorn/no-invalid-well-known-symbol-methods': [2],
|
'unicorn/no-invalid-well-known-symbol-methods': [2],
|
||||||
'unicorn/no-javascript-url': [0], // only applies to markdown language
|
|
||||||
'unicorn/no-keyword-prefix': [0],
|
'unicorn/no-keyword-prefix': [0],
|
||||||
'unicorn/no-late-current-target-access': [2],
|
'unicorn/no-late-current-target-access': [2],
|
||||||
'unicorn/no-late-event-control': [2],
|
'unicorn/no-late-event-control': [2],
|
||||||
'unicorn/no-leading-empty-lines': [0], // handled by @stylistic/no-multiple-empty-lines
|
|
||||||
'unicorn/no-lonely-if': [2],
|
'unicorn/no-lonely-if': [2],
|
||||||
'unicorn/no-loop-iterable-mutation': [2],
|
'unicorn/no-loop-iterable-mutation': [2],
|
||||||
'unicorn/no-loss-of-precision': [0], // only applies to json, toml and css languages
|
|
||||||
'unicorn/no-magic-array-flat-depth': [0],
|
'unicorn/no-magic-array-flat-depth': [0],
|
||||||
'unicorn/no-manually-wrapped-comments': [0], // too opinionated
|
'unicorn/no-manually-wrapped-comments': [0], // too opinionated
|
||||||
'unicorn/no-mismatched-map-key': [2],
|
'unicorn/no-mismatched-map-key': [2],
|
||||||
@@ -840,6 +817,7 @@ export default defineConfig([
|
|||||||
'unicorn/no-negated-condition': [0],
|
'unicorn/no-negated-condition': [0],
|
||||||
'unicorn/no-negation-in-equality-check': [2],
|
'unicorn/no-negation-in-equality-check': [2],
|
||||||
'unicorn/no-nested-ternary': [0],
|
'unicorn/no-nested-ternary': [0],
|
||||||
|
'unicorn/no-nesting-with-mixed-specificity': [0],
|
||||||
'unicorn/no-new-array': [0],
|
'unicorn/no-new-array': [0],
|
||||||
'unicorn/no-new-buffer': [2],
|
'unicorn/no-new-buffer': [2],
|
||||||
'unicorn/no-non-function-verb-prefix': [0],
|
'unicorn/no-non-function-verb-prefix': [0],
|
||||||
@@ -848,10 +826,9 @@ export default defineConfig([
|
|||||||
'unicorn/no-object-as-default-parameter': [0],
|
'unicorn/no-object-as-default-parameter': [0],
|
||||||
'unicorn/no-object-methods-with-collections': [2],
|
'unicorn/no-object-methods-with-collections': [2],
|
||||||
'unicorn/no-optional-chaining-on-undeclared-variable': [2],
|
'unicorn/no-optional-chaining-on-undeclared-variable': [2],
|
||||||
'unicorn/no-prevent-default-in-passive-listener': [2],
|
|
||||||
'unicorn/no-process-exit': [0],
|
'unicorn/no-process-exit': [0],
|
||||||
'unicorn/no-redundant-comparison': [2],
|
'unicorn/no-redundant-comparison': [2],
|
||||||
'unicorn/no-repeated-call-wrappers': [0],
|
'unicorn/no-redundant-nested-style-rules': [0],
|
||||||
'unicorn/no-return-array-push': [2],
|
'unicorn/no-return-array-push': [2],
|
||||||
'unicorn/no-selector-as-dom-name': [2],
|
'unicorn/no-selector-as-dom-name': [2],
|
||||||
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
|
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
|
||||||
@@ -867,16 +844,16 @@ export default defineConfig([
|
|||||||
'unicorn/no-typeof-undefined': [2],
|
'unicorn/no-typeof-undefined': [2],
|
||||||
'unicorn/no-uncalled-method': [2],
|
'unicorn/no-uncalled-method': [2],
|
||||||
'unicorn/no-undeclared-class-members': [2],
|
'unicorn/no-undeclared-class-members': [2],
|
||||||
|
'unicorn/no-unknown-css-annotations': [0],
|
||||||
|
'unicorn/no-unknown-pseudo-selectors': [0],
|
||||||
'unicorn/no-unnecessary-array-flat-depth': [2],
|
'unicorn/no-unnecessary-array-flat-depth': [2],
|
||||||
'unicorn/no-unnecessary-array-flat-map': [2],
|
'unicorn/no-unnecessary-array-flat-map': [2],
|
||||||
'unicorn/no-unnecessary-array-splice-count': [2],
|
'unicorn/no-unnecessary-array-splice-count': [2],
|
||||||
'unicorn/no-unnecessary-await': [2],
|
'unicorn/no-unnecessary-await': [2],
|
||||||
'unicorn/no-unnecessary-boolean-comparison': [2],
|
'unicorn/no-unnecessary-boolean-comparison': [2],
|
||||||
'unicorn/no-unnecessary-default-arguments': [0],
|
|
||||||
'unicorn/no-unnecessary-fetch-options': [0],
|
'unicorn/no-unnecessary-fetch-options': [0],
|
||||||
'unicorn/no-unnecessary-global-this': [0],
|
'unicorn/no-unnecessary-global-this': [0],
|
||||||
'unicorn/no-unnecessary-nested-ternary': [2],
|
'unicorn/no-unnecessary-nested-ternary': [2],
|
||||||
'unicorn/no-unnecessary-parameters': [0],
|
|
||||||
'unicorn/no-unnecessary-polyfills': [2],
|
'unicorn/no-unnecessary-polyfills': [2],
|
||||||
'unicorn/no-unnecessary-slice-end': [2],
|
'unicorn/no-unnecessary-slice-end': [2],
|
||||||
'unicorn/no-unnecessary-splice': [2],
|
'unicorn/no-unnecessary-splice': [2],
|
||||||
@@ -888,15 +865,14 @@ export default defineConfig([
|
|||||||
'unicorn/no-unreadable-object-destructuring': [0],
|
'unicorn/no-unreadable-object-destructuring': [0],
|
||||||
'unicorn/no-unsafe-buffer-conversion': [2],
|
'unicorn/no-unsafe-buffer-conversion': [2],
|
||||||
'unicorn/no-unsafe-dom-html': [0],
|
'unicorn/no-unsafe-dom-html': [0],
|
||||||
'unicorn/no-unsafe-json-serialization': [2],
|
|
||||||
'unicorn/no-unsafe-promise-all-settled-values': [2],
|
'unicorn/no-unsafe-promise-all-settled-values': [2],
|
||||||
'unicorn/no-unsafe-property-key': [0],
|
'unicorn/no-unsafe-property-key': [0],
|
||||||
'unicorn/no-unsafe-sqlite-interpolation': [2],
|
'unicorn/no-unsafe-sqlite-interpolation': [2],
|
||||||
'unicorn/no-unsafe-string-replacement': [2],
|
'unicorn/no-unsafe-string-replacement': [2],
|
||||||
|
'unicorn/no-unscoped-css-nesting-selector': [0],
|
||||||
'unicorn/no-unused-builtin-method-return': [2],
|
'unicorn/no-unused-builtin-method-return': [2],
|
||||||
'unicorn/no-unused-iterator-helper': [2],
|
'unicorn/no-unused-iterator-helper': [2],
|
||||||
'unicorn/no-unused-properties': [2],
|
'unicorn/no-unused-properties': [2],
|
||||||
'unicorn/no-url-in-search-params': [2],
|
|
||||||
'unicorn/no-useless-boolean-cast': [2],
|
'unicorn/no-useless-boolean-cast': [2],
|
||||||
'unicorn/no-useless-coercion': [2],
|
'unicorn/no-useless-coercion': [2],
|
||||||
'unicorn/no-useless-collection-argument': [2],
|
'unicorn/no-useless-collection-argument': [2],
|
||||||
@@ -933,12 +909,12 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
|
||||||
'unicorn/prefer-array-flat': [2],
|
'unicorn/prefer-array-flat': [2],
|
||||||
'unicorn/prefer-array-flat-map': [2],
|
'unicorn/prefer-array-flat-map': [2],
|
||||||
'unicorn/prefer-array-from-async': [0], // Array.fromAsync requires ES2026
|
'unicorn/prefer-array-from-async': [2],
|
||||||
'unicorn/prefer-array-from-map': [2],
|
'unicorn/prefer-array-from-map': [2],
|
||||||
'unicorn/prefer-array-from-range': [2],
|
'unicorn/prefer-array-from-range': [2],
|
||||||
'unicorn/prefer-array-index-of': [2],
|
'unicorn/prefer-array-index-of': [2],
|
||||||
'unicorn/prefer-array-iterable-methods': [2],
|
'unicorn/prefer-array-iterable-methods': [2],
|
||||||
'unicorn/prefer-array-last-methods': [0], // Array#findLast/findLastIndex requires ES2023
|
'unicorn/prefer-array-last-methods': [2],
|
||||||
'unicorn/prefer-array-slice': [2],
|
'unicorn/prefer-array-slice': [2],
|
||||||
'unicorn/prefer-array-some': [2],
|
'unicorn/prefer-array-some': [2],
|
||||||
'unicorn/prefer-at': [0],
|
'unicorn/prefer-at': [0],
|
||||||
@@ -955,7 +931,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-date-now': [2],
|
'unicorn/prefer-date-now': [2],
|
||||||
'unicorn/prefer-default-parameters': [0],
|
'unicorn/prefer-default-parameters': [0],
|
||||||
'unicorn/prefer-direct-iteration': [2],
|
'unicorn/prefer-direct-iteration': [2],
|
||||||
'unicorn/prefer-dispose': [0], // `using` requires ES2027
|
'unicorn/prefer-dispose': [2],
|
||||||
'unicorn/prefer-dom-node-append': [2],
|
'unicorn/prefer-dom-node-append': [2],
|
||||||
'unicorn/prefer-dom-node-html-methods': [0],
|
'unicorn/prefer-dom-node-html-methods': [0],
|
||||||
'unicorn/prefer-dom-node-remove': [2],
|
'unicorn/prefer-dom-node-remove': [2],
|
||||||
@@ -964,14 +940,14 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-early-return': [0],
|
'unicorn/prefer-early-return': [0],
|
||||||
'unicorn/prefer-else-if': [2],
|
'unicorn/prefer-else-if': [2],
|
||||||
'unicorn/prefer-error-is-error': [0],
|
'unicorn/prefer-error-is-error': [0],
|
||||||
'unicorn/prefer-escaped-irregular-whitespace': [2],
|
|
||||||
'unicorn/prefer-event-target': [2],
|
'unicorn/prefer-event-target': [2],
|
||||||
|
'unicorn/prefer-explicit-viewport-units': [0], // only applies to css language
|
||||||
'unicorn/prefer-export-from': [0],
|
'unicorn/prefer-export-from': [0],
|
||||||
'unicorn/prefer-flat-math-min-max': [2],
|
'unicorn/prefer-flat-math-min-max': [2],
|
||||||
'unicorn/prefer-get-or-insert-computed': [0], // Map#getOrInsertComputed requires ES2026
|
'unicorn/prefer-get-or-insert-computed': [2],
|
||||||
'unicorn/prefer-global-number-constants': [2],
|
'unicorn/prefer-global-number-constants': [2],
|
||||||
'unicorn/prefer-global-this': [0],
|
'unicorn/prefer-global-this': [0],
|
||||||
'unicorn/prefer-group-by': [0], // Object.groupBy/Map.groupBy requires ES2024
|
'unicorn/prefer-group-by': [2],
|
||||||
'unicorn/prefer-has-check': [2],
|
'unicorn/prefer-has-check': [2],
|
||||||
'unicorn/prefer-hoisting-branch-code': [2],
|
'unicorn/prefer-hoisting-branch-code': [2],
|
||||||
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
|
||||||
@@ -982,12 +958,11 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-iterable-in-constructor': [2],
|
'unicorn/prefer-iterable-in-constructor': [2],
|
||||||
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
'unicorn/prefer-iterator-concat': [0], // too opinionated
|
||||||
'unicorn/prefer-iterator-helpers': [0],
|
'unicorn/prefer-iterator-helpers': [0],
|
||||||
'unicorn/prefer-iterator-to-array': [0], // Iterator#toArray requires ES2025
|
'unicorn/prefer-iterator-to-array': [2],
|
||||||
'unicorn/prefer-iterator-to-array-at-end': [2],
|
'unicorn/prefer-iterator-to-array-at-end': [2],
|
||||||
'unicorn/prefer-iterator-zip': [0],
|
'unicorn/prefer-iterator-zip': [0],
|
||||||
'unicorn/prefer-json-import': [0],
|
'unicorn/prefer-json-import': [0],
|
||||||
'unicorn/prefer-keyboard-event-key': [2],
|
'unicorn/prefer-keyboard-event-key': [2],
|
||||||
'unicorn/prefer-literal-ascii': [2],
|
|
||||||
'unicorn/prefer-location-assign': [2],
|
'unicorn/prefer-location-assign': [2],
|
||||||
'unicorn/prefer-logical-operator-over-ternary': [0],
|
'unicorn/prefer-logical-operator-over-ternary': [0],
|
||||||
'unicorn/prefer-map-from-entries': [0],
|
'unicorn/prefer-map-from-entries': [0],
|
||||||
@@ -995,6 +970,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-math-constants': [2],
|
'unicorn/prefer-math-constants': [2],
|
||||||
'unicorn/prefer-math-min-max': [2],
|
'unicorn/prefer-math-min-max': [2],
|
||||||
'unicorn/prefer-math-trunc': [2],
|
'unicorn/prefer-math-trunc': [2],
|
||||||
|
'unicorn/prefer-media-feature-range-syntax': [0],
|
||||||
'unicorn/prefer-minimal-ternary': [0],
|
'unicorn/prefer-minimal-ternary': [0],
|
||||||
'unicorn/prefer-modern-dom-apis': [0],
|
'unicorn/prefer-modern-dom-apis': [0],
|
||||||
'unicorn/prefer-modern-math-apis': [2],
|
'unicorn/prefer-modern-math-apis': [2],
|
||||||
@@ -1013,9 +989,8 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-optional-catch-binding': [2],
|
'unicorn/prefer-optional-catch-binding': [2],
|
||||||
'unicorn/prefer-path2d': [2],
|
'unicorn/prefer-path2d': [2],
|
||||||
'unicorn/prefer-private-class-fields': [0],
|
'unicorn/prefer-private-class-fields': [0],
|
||||||
'unicorn/prefer-promise-static-methods': [2],
|
'unicorn/prefer-promise-try': [2],
|
||||||
'unicorn/prefer-promise-try': [0], // Promise.try requires ES2025
|
'unicorn/prefer-promise-with-resolvers': [2],
|
||||||
'unicorn/prefer-promise-with-resolvers': [0], // Promise.withResolvers requires ES2024
|
|
||||||
'unicorn/prefer-prototype-methods': [2],
|
'unicorn/prefer-prototype-methods': [2],
|
||||||
'unicorn/prefer-query-selector': [2],
|
'unicorn/prefer-query-selector': [2],
|
||||||
'unicorn/prefer-queue-microtask': [2],
|
'unicorn/prefer-queue-microtask': [2],
|
||||||
@@ -1028,7 +1003,6 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-set-methods': [0],
|
'unicorn/prefer-set-methods': [0],
|
||||||
'unicorn/prefer-set-size': [2],
|
'unicorn/prefer-set-size': [2],
|
||||||
'unicorn/prefer-short-arrow-method': [2],
|
'unicorn/prefer-short-arrow-method': [2],
|
||||||
'unicorn/prefer-short-escape-sequences': [2],
|
|
||||||
'unicorn/prefer-simple-condition-first': [0],
|
'unicorn/prefer-simple-condition-first': [0],
|
||||||
'unicorn/prefer-simple-sort-comparator': [2],
|
'unicorn/prefer-simple-sort-comparator': [2],
|
||||||
'unicorn/prefer-simplified-conditions': [2],
|
'unicorn/prefer-simplified-conditions': [2],
|
||||||
@@ -1050,7 +1024,7 @@ export default defineConfig([
|
|||||||
'unicorn/prefer-structured-clone': [2],
|
'unicorn/prefer-structured-clone': [2],
|
||||||
'unicorn/prefer-switch': [0],
|
'unicorn/prefer-switch': [0],
|
||||||
'unicorn/prefer-temporal': [0],
|
'unicorn/prefer-temporal': [0],
|
||||||
'unicorn/prefer-temporal-conversion': [0], // Temporal requires ES2027
|
'unicorn/prefer-temporal-conversion': [2],
|
||||||
'unicorn/prefer-ternary': [0],
|
'unicorn/prefer-ternary': [0],
|
||||||
'unicorn/prefer-then-catch': [2],
|
'unicorn/prefer-then-catch': [2],
|
||||||
'unicorn/prefer-toggle-attribute': [2],
|
'unicorn/prefer-toggle-attribute': [2],
|
||||||
@@ -1076,7 +1050,6 @@ export default defineConfig([
|
|||||||
'unicorn/require-passive-events': [2],
|
'unicorn/require-passive-events': [2],
|
||||||
'unicorn/require-post-message-target-origin': [0],
|
'unicorn/require-post-message-target-origin': [0],
|
||||||
'unicorn/require-proxy-trap-boolean-return': [2],
|
'unicorn/require-proxy-trap-boolean-return': [2],
|
||||||
'unicorn/require-text-decoder-streaming': [2],
|
|
||||||
'unicorn/single-line-block-comment-style': [0],
|
'unicorn/single-line-block-comment-style': [0],
|
||||||
'unicorn/string-content': [0],
|
'unicorn/string-content': [0],
|
||||||
'unicorn/switch-case-braces': [0],
|
'unicorn/switch-case-braces': [0],
|
||||||
@@ -1123,13 +1096,17 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: vueExts.map((ext) => `**/*.${ext}`),
|
files: ['**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
},
|
||||||
|
},
|
||||||
extends: [
|
extends: [
|
||||||
vue.configs['flat/recommended'],
|
vue.configs['flat/recommended'],
|
||||||
vueScopedCss.configs.recommended,
|
vueScopedCss.configs.recommended,
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-redundant-type-constituents': [0], // types imported from .vue files resolve to any via typescript-eslint's *.vue shim
|
|
||||||
'vue/attributes-order': [0],
|
'vue/attributes-order': [0],
|
||||||
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
|
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
|
||||||
'vue/max-attributes-per-line': [0],
|
'vue/max-attributes-per-line': [0],
|
||||||
@@ -1242,9 +1219,6 @@ export default defineConfig([
|
|||||||
{
|
{
|
||||||
files: ['*', 'tools/**/*', 'tests/**/*'],
|
files: ['*', 'tools/**/*', 'tests/**/*'],
|
||||||
languageOptions: {globals: globals.nodeBuiltin},
|
languageOptions: {globals: globals.nodeBuiltin},
|
||||||
rules: {
|
|
||||||
'gitea/unescaped-html-literal': [0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['web_src/**/*'],
|
files: ['web_src/**/*'],
|
||||||
|
|||||||
@@ -2,43 +2,50 @@ module gitea.dev
|
|||||||
|
|
||||||
go 1.27
|
go 1.27
|
||||||
|
|
||||||
toolchain go1.27.3
|
toolchain go1.27.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
connectrpc.com/connect/v2 v2.0.1
|
connectrpc.com/connect v1.21.0
|
||||||
|
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981
|
||||||
|
gitea.com/go-chi/cache v0.2.1
|
||||||
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||||
|
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||||
gitea.dev/actionslib v1.5.0
|
gitea.dev/actionslib v1.2.1
|
||||||
gitea.dev/sdk v1.3.0
|
gitea.dev/sdk v1.2.0
|
||||||
github.com/42wim/httpsig v1.3.0
|
github.com/42wim/httpsig v1.2.4
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||||
github.com/Azure/go-ntlmssp v0.1.1
|
github.com/Azure/go-ntlmssp v0.1.1
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
|
||||||
github.com/ProtonMail/go-crypto v1.5.2
|
github.com/ProtonMail/go-crypto v1.5.0
|
||||||
github.com/PuerkitoBio/goquery v1.13.0
|
github.com/PuerkitoBio/goquery v1.13.0
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0
|
github.com/alecthomas/chroma/v2 v2.27.0
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||||
github.com/blevesearch/bleve/v2 v2.6.1
|
github.com/blevesearch/bleve/v2 v2.6.1
|
||||||
github.com/bohde/codel v0.2.0
|
github.com/bohde/codel v0.2.0
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c
|
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.17.1
|
github.com/buildkite/terminal-to-html/v3 v3.17.1
|
||||||
github.com/caddyserver/certmagic v0.25.6
|
github.com/caddyserver/certmagic v0.25.4
|
||||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
|
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
|
||||||
|
github.com/chi-middleware/proxy v1.1.1
|
||||||
github.com/coder/websocket v1.8.15
|
github.com/coder/websocket v1.8.15
|
||||||
github.com/dlclark/regexp2/v2 v2.8.4
|
github.com/dlclark/regexp2/v2 v2.8.0
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
||||||
github.com/dustin/go-humanize v1.1.0
|
github.com/dustin/go-humanize v1.1.0
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
|
||||||
github.com/emersion/go-imap v1.2.1
|
github.com/emersion/go-imap v1.2.1
|
||||||
|
github.com/emirpasic/gods v1.18.1
|
||||||
github.com/felixge/fgprof v0.9.5
|
github.com/felixge/fgprof v0.9.5
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/getkin/kin-openapi v0.150.0
|
github.com/getkin/kin-openapi v0.149.0
|
||||||
github.com/go-chi/chi/v5 v5.4.0
|
github.com/go-chi/chi/v5 v5.3.2
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-co-op/gocron/v2 v2.22.0
|
github.com/go-co-op/gocron/v2 v2.22.0
|
||||||
github.com/go-enry/go-enry/v2 v2.9.6
|
github.com/go-enry/go-enry/v2 v2.9.6
|
||||||
github.com/go-ldap/ldap/v3 v3.4.15
|
github.com/go-git/go-billy/v5 v5.9.1
|
||||||
|
github.com/go-git/go-git/v5 v5.19.2
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.14
|
||||||
github.com/go-redsync/redsync/v4 v4.18.0
|
github.com/go-redsync/redsync/v4 v4.18.0
|
||||||
github.com/go-sql-driver/mysql v1.10.1
|
github.com/go-sql-driver/mysql v1.10.1
|
||||||
github.com/go-webauthn/webauthn v0.18.2
|
github.com/go-webauthn/webauthn v0.18.2
|
||||||
@@ -52,10 +59,10 @@ require (
|
|||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/hashicorp/go-version v1.9.0
|
github.com/hashicorp/go-version v1.9.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/huandu/xstrings v1.6.2
|
github.com/huandu/xstrings v1.6.1
|
||||||
github.com/jhillyerd/enmime/v2 v2.5.0
|
github.com/jhillyerd/enmime/v2 v2.5.0
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/klauspost/compress v1.20.3
|
github.com/klauspost/compress v1.20.0
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
github.com/markbates/goth v1.82.0
|
github.com/markbates/goth v1.82.0
|
||||||
github.com/mattn/go-isatty v0.0.24
|
github.com/mattn/go-isatty v0.0.24
|
||||||
@@ -64,41 +71,42 @@ require (
|
|||||||
github.com/mholt/archives v0.1.5
|
github.com/mholt/archives v0.1.5
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/microsoft/go-mssqldb v1.11.2
|
github.com/microsoft/go-mssqldb v1.11.2
|
||||||
github.com/minio/minio-go/v7 v7.4.1
|
github.com/minio/minio-go/v7 v7.3.0
|
||||||
github.com/msteinert/pam/v2 v2.1.0
|
github.com/msteinert/pam/v2 v2.1.0
|
||||||
github.com/niklasfasching/go-org v1.9.1
|
github.com/niklasfasching/go-org v1.9.1
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/pquerna/otp v1.5.0
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/prometheus/client_golang v1.25.0
|
github.com/prometheus/client_golang v1.24.1
|
||||||
github.com/quasoft/websspi v1.1.2
|
github.com/quasoft/websspi v1.1.2
|
||||||
github.com/redis/go-redis/v9 v9.23.0
|
github.com/redis/go-redis/v9 v9.22.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
|
||||||
|
github.com/sassoftware/go-rpmutils v0.4.0
|
||||||
github.com/sergi/go-diff v1.4.0
|
github.com/sergi/go-diff v1.4.0
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.12.1
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
github.com/ulikunitz/xz v0.5.17
|
github.com/ulikunitz/xz v0.5.17
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0
|
github.com/urfave/cli-docs/v3 v3.1.0
|
||||||
github.com/urfave/cli/v3 v3.14.0
|
github.com/urfave/cli/v3 v3.13.0
|
||||||
github.com/wneessen/go-mail v0.8.1
|
github.com/wneessen/go-mail v0.8.1
|
||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.8.6
|
github.com/yuin/goldmark v1.8.6
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0
|
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525
|
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/image v0.47.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/mod v0.42.0
|
golang.org/x/mod v0.41.0
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.60.0
|
||||||
golang.org/x/oauth2 v0.37.0
|
golang.org/x/oauth2 v0.37.0
|
||||||
golang.org/x/sync v0.24.0
|
golang.org/x/sync v0.23.0
|
||||||
golang.org/x/sys v0.49.0
|
golang.org/x/sys v0.48.0
|
||||||
golang.org/x/text v0.43.0
|
golang.org/x/text v0.42.0
|
||||||
google.golang.org/grpc v1.84.0
|
google.golang.org/grpc v1.84.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.12
|
||||||
gopkg.in/ini.v1 v1.67.3
|
gopkg.in/ini.v1 v1.67.3
|
||||||
modernc.org/sqlite v1.60.1
|
modernc.org/sqlite v1.59.0
|
||||||
mvdan.cc/xurls/v2 v2.6.0
|
mvdan.cc/xurls/v2 v2.6.0
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
xorm.io/xorm v1.4.3
|
xorm.io/xorm v1.4.3
|
||||||
@@ -106,7 +114,10 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
|
github.com/DataDog/zstd v1.5.7 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
|
||||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
@@ -132,18 +143,23 @@ require (
|
|||||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.3 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
github.com/bodgit/sevenzip v1.6.4 // indirect
|
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.1.0 // indirect
|
github.com/boombuler/barcode v1.1.0 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.6 // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
|
||||||
|
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.3 // indirect
|
github.com/cloudflare/circl v1.6.3 // indirect
|
||||||
|
github.com/couchbase/go-couchbase v0.1.1 // indirect
|
||||||
|
github.com/couchbase/gomemcached v0.3.4 // indirect
|
||||||
|
github.com/couchbase/goutils v0.3.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||||
@@ -152,6 +168,7 @@ require (
|
|||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
||||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
||||||
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
@@ -159,7 +176,9 @@ require (
|
|||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
|
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/google/go-tpm v0.9.8 // indirect
|
github.com/google/go-tpm v0.9.8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -169,8 +188,10 @@ require (
|
|||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||||
github.com/inbucket/html2text v1.0.0 // indirect
|
github.com/inbucket/html2text v1.0.0 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
@@ -178,8 +199,8 @@ require (
|
|||||||
github.com/markbates/going v1.0.3 // indirect
|
github.com/markbates/going v1.0.3 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||||
github.com/mholt/acmez/v3 v3.1.7 // indirect
|
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||||
github.com/miekg/dns v1.1.73 // indirect
|
github.com/miekg/dns v1.1.72 // indirect
|
||||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
@@ -198,37 +219,43 @@ require (
|
|||||||
github.com/olekukonko/ll v0.1.8 // indirect
|
github.com/olekukonko/ll v0.1.8 // indirect
|
||||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/onsi/gomega v1.34.1 // indirect
|
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
|
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_model v0.6.3 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.72.0 // indirect
|
github.com/prometheus/common v0.70.1 // indirect
|
||||||
github.com/prometheus/procfs v0.22.0 // indirect
|
github.com/prometheus/procfs v0.21.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.10.2 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||||
github.com/tinylib/msgp v1.6.4 // indirect
|
github.com/tinylib/msgp v1.6.4 // indirect
|
||||||
|
github.com/unknwon/com v1.0.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||||
go.etcd.io/bbolt v1.4.3 // indirect
|
go.etcd.io/bbolt v1.4.3 // indirect
|
||||||
go.uber.org/atomic v1.12.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.28.0 // indirect
|
go.uber.org/zap v1.28.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
|
golang.org/x/tools v0.49.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
||||||
modernc.org/libc v1.77.1 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
modernc.org/libc v1.75.7 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.12.1 // indirect
|
modernc.org/memory v1.12.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,22 +2,32 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
|
|||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||||
connectrpc.com/connect/v2 v2.0.1 h1:WTAycxe87UqxSM5oxS30d1dalZ+C5xz8Ec8O6zVYkHQ=
|
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
|
||||||
connectrpc.com/connect/v2 v2.0.1/go.mod h1:N32OLrstcPUzkykFynnefFro3+xzSdOrZlK2jHmS/xo=
|
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
||||||
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
|
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981 h1:LmdlwGbzgZFZA3bK3R1q8QrNabaSz3KpnOJBLmzhN6E=
|
||||||
|
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981/go.mod h1:q1SSPpkC9A0gfNnoqqZ3My6kEHIpKN6QsTI+Zx73B/o=
|
||||||
|
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||||
|
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
|
||||||
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||||
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
||||||
|
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6 h1:YWzVGeC/8SZThrJS48ZmQYLkzssdeABxHPhbdnxPDIU=
|
||||||
|
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0=
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
||||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
gitea.dev/actionslib v1.5.0 h1:dC3v2HTWbbvyNBSdg2W3kd4WNC/p+FnZaKpl1MdzM00=
|
gitea.dev/actionslib v1.2.1 h1:GL//K/0zIZV6h1OOksv1awvFVg0rg7fug2xWcG7mkG0=
|
||||||
gitea.dev/actionslib v1.5.0/go.mod h1:C9wmOFgDBCXyRvg4A3KTX8Vszs8v9+elH4QLGVDolqU=
|
gitea.dev/actionslib v1.2.1/go.mod h1:1+gqOKGSEPn2IFHgY8S3GC5Ld+Hn8jF3OxiFHQ/fpx4=
|
||||||
gitea.dev/sdk v1.3.0 h1:4U95+Ou6WHCQiRPDEyAzvl1Q7cxg5RUTWkqmOXr5w4I=
|
gitea.dev/sdk v1.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
|
||||||
gitea.dev/sdk v1.3.0/go.mod h1:85EBqSAqUn1KlFCk40y3sTjWjBsB/KouL3slGXMA/2o=
|
gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
|
||||||
github.com/42wim/httpsig v1.3.0 h1:HK2bieeZ99X9IvOoc1ST6yrhLek0f4MqlEGES2JCP3g=
|
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||||
github.com/42wim/httpsig v1.3.0/go.mod h1:lgC3Yrvp4lxi1odEF1ge3kQBu/VM+SRcZQu5YwiR3dU=
|
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
|
||||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE=
|
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo=
|
||||||
@@ -34,10 +44,15 @@ github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDP
|
|||||||
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
|
||||||
|
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||||
|
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||||
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
|
||||||
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
|
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
|
||||||
github.com/ProtonMail/go-crypto v1.5.2 h1:cucYnvqcY7UOXVD//mSyjeaPY0SSN3v5cDkYPxumINk=
|
github.com/ProtonMail/go-crypto v1.5.0 h1:sKmuvjOgsnrtpMvZ+84MCnTJCpjxZ1qCFn076lz1yT0=
|
||||||
github.com/ProtonMail/go-crypto v1.5.2/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
|
github.com/ProtonMail/go-crypto v1.5.0/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
|
||||||
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
|
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
|
||||||
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
|
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
|
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
|
||||||
@@ -60,6 +75,10 @@ github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eT
|
|||||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
||||||
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -106,8 +125,8 @@ github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaT
|
|||||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
|
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
|
||||||
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
|
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.3 h1:1UIv1+fqH3++ViVkpnaPTB+Pi1UHycnPFAq14zgK+gI=
|
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.3/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
@@ -128,16 +147,18 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.17.1 h1:gs9WhKUwxK27YHf3hijj4tG4wuDLfCANYbRPd5ltMdI=
|
github.com/buildkite/terminal-to-html/v3 v3.17.1 h1:gs9WhKUwxK27YHf3hijj4tG4wuDLfCANYbRPd5ltMdI=
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.17.1/go.mod h1:0pv+Z75FC+NvIju2o+Cw+YHIDkueeudIoeyfVm8i8GQ=
|
github.com/buildkite/terminal-to-html/v3 v3.17.1/go.mod h1:0pv+Z75FC+NvIju2o+Cw+YHIDkueeudIoeyfVm8i8GQ=
|
||||||
github.com/caddyserver/certmagic v0.25.6 h1:vHMtFSLKTa6kuZz6w0SnRMpQ388tSkww8ye355BgAM4=
|
github.com/caddyserver/certmagic v0.25.4 h1:8eIXh0HC3MsGnNo8One+BCxMGTbe5zb/oz+2KsxBFQg=
|
||||||
github.com/caddyserver/certmagic v0.25.6/go.mod h1:xq6cRNimqW+Tv91XNc5lNHs2XYTsLhQhYiH01H4AySs=
|
github.com/caddyserver/certmagic v0.25.4/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||||
github.com/caddyserver/zerossl v0.1.6 h1:1yrhTx5DWi43wOJPQ+Y4mVl++EPmSqqT/4REW//lsas=
|
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||||
github.com/caddyserver/zerossl v0.1.6/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33 h1:YG5mXlv9SxS4rdtSxK3RuH146x1HIQzsbVJqSp/Rvfc=
|
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33 h1:YG5mXlv9SxS4rdtSxK3RuH146x1HIQzsbVJqSp/Rvfc=
|
||||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33/go.mod h1:R+SetERD4+IL7QH0WHp9MLifvITvh9gL3g8vX1j2Fcs=
|
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33/go.mod h1:R+SetERD4+IL7QH0WHp9MLifvITvh9gL3g8vX1j2Fcs=
|
||||||
|
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
|
||||||
|
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
|
||||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
@@ -152,8 +173,17 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg
|
|||||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
||||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
|
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
|
||||||
|
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
||||||
|
github.com/couchbase/gomemcached v0.3.4 h1:VGdrZUJbt5lLyI/MXnyVCZKHKYXg/vaud08lJIAeZps=
|
||||||
|
github.com/couchbase/gomemcached v0.3.4/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68=
|
||||||
|
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
|
||||||
|
github.com/couchbase/goutils v0.3.0 h1:rsv72B6BDjW9jmwlfiDUrdu3EpNvPuo5WLULHzQ0DLE=
|
||||||
|
github.com/couchbase/goutils v0.3.0/go.mod h1:7Gm+D3vXfV4HS+hQWvKfy6e6ILCptGXNqBKvQXhplhk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -164,10 +194,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dlclark/regexp2/v2 v2.8.4 h1:yfDDXTn4oxyLDC10/ieOBNDCCdIVWS9WbT6ddVXsPKI=
|
github.com/dlclark/regexp2/v2 v2.8.0 h1:CekDhPLGfm+GAJmPVFIG+5dqMIQPkyHJll7BbdneDfw=
|
||||||
github.com/dlclark/regexp2/v2 v2.8.4/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
github.com/dlclark/regexp2/v2 v2.8.0/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
@@ -175,6 +205,8 @@ github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2
|
|||||||
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
|
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE=
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE=
|
||||||
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||||
@@ -182,6 +214,8 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
|
|||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||||
@@ -192,14 +226,17 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
|
|||||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/getkin/kin-openapi v0.150.0 h1:7+O1HMsrAB4BevQDElbLAeCAWwyJC/RjQJYw4oosUUU=
|
github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA=
|
||||||
github.com/getkin/kin-openapi v0.150.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
|
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
|
||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
|
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-chi/chi/v5 v5.4.0 h1:+ufzvFTGKyin26Or5DLrlIDq4ygIDBVcj7oVNeeaYFE=
|
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.4.0/go.mod h1:d1IG7GtKPSjPyBP365PH8sAGVHk3qfB/IhjFNRH1wWM=
|
github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY=
|
||||||
|
github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I=
|
github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I=
|
||||||
@@ -208,10 +245,18 @@ github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUD
|
|||||||
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
|
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
|
||||||
|
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
|
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||||
|
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.15 h1:eRB4f4Ecn1POL1mO86+uUDlecmyWsdiJwh60bAUS4UY=
|
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.15/go.mod h1:jeD0c98EdZQCKKKKe4kTWs5aCf6y+j8KuYsYLznZSQk=
|
github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
|
||||||
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
|
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
|
||||||
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
|
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
|
||||||
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
|
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
|
||||||
@@ -252,6 +297,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
|||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -267,6 +314,9 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
|||||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
||||||
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||||
|
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||||
|
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||||
|
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
@@ -293,6 +343,8 @@ github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe h1:QAinXoAFJdGQYztXn3
|
|||||||
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
@@ -324,11 +376,13 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
|
|||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/xstrings v1.6.2 h1:+X5X6N46b40cmDw7FFJFU6Eoq0yJS8lbYigT2EFau4c=
|
github.com/huandu/xstrings v1.6.1 h1:yYdKSd6Sjv3fNZt9BDjl1FDsPNfQEaYi19L5LzK2JKs=
|
||||||
github.com/huandu/xstrings v1.6.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.6.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||||
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||||
@@ -348,11 +402,16 @@ github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7X
|
|||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
|
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||||
|
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4=
|
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||||
github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
@@ -373,8 +432,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
|||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||||
github.com/letsencrypt/pebble/v2 v2.10.1 h1:oKHx3lgN4e5Nno2LKTMrVx+b+NkDptkO9aDireiBDGE=
|
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||||
github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY=
|
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||||
@@ -394,24 +453,24 @@ github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdo
|
|||||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||||
github.com/meilisearch/meilisearch-go v0.36.3 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
|
github.com/meilisearch/meilisearch-go v0.36.3 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
|
||||||
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||||
github.com/mholt/acmez/v3 v3.1.7 h1:XTqpuUcRdRoIBuDoI+M4mCnRnPTPN4rqH3Mb723Rg4U=
|
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||||
github.com/mholt/acmez/v3 v3.1.7/go.mod h1:Lwv6P/czh/AOq+c99tAzP68/VP92s+8y+hDs4FCgxvo=
|
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||||
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||||
github.com/microsoft/go-mssqldb v1.11.2 h1:FCgeBIK8um2+X4tbun6Q71N1KsfyCDPKY41e1yGVjSE=
|
github.com/microsoft/go-mssqldb v1.11.2 h1:FCgeBIK8um2+X4tbun6Q71N1KsfyCDPKY41e1yGVjSE=
|
||||||
github.com/microsoft/go-mssqldb v1.11.2/go.mod h1:CYgwG5AMXFojbjTg+GNP5G/y6uz1BhTyZaPqQWzkGnQ=
|
github.com/microsoft/go-mssqldb v1.11.2/go.mod h1:CYgwG5AMXFojbjTg+GNP5G/y6uz1BhTyZaPqQWzkGnQ=
|
||||||
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.4.1 h1:PtU/zIOfQRWZ+QydXAZysc+xZYHdG+BBR4Z6TUvy73w=
|
github.com/minio/minio-go/v7 v7.3.0 h1:HM4pFCSQq/TK+j0/zmorSh5ddh81iDgRgU0BG0Vz/YU=
|
||||||
github.com/minio/minio-go/v7 v7.4.1/go.mod h1:nQnQi7zTfXR4lREtYXZQWqo5aw0Rqv+ZqCGrTORygRk=
|
github.com/minio/minio-go/v7 v7.3.0/go.mod h1:KUPWdecEO1LWyUz+sTGXAuf2jZHrPh5fCsRH86QbPfk=
|
||||||
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||||
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -467,6 +526,8 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
|||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||||
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||||
|
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -476,18 +537,18 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/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 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
|
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
||||||
github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
|
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
||||||
github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4=
|
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
|
||||||
github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk=
|
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||||
github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=
|
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||||
github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
|
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
||||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
||||||
github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s=
|
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
|
||||||
github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0=
|
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
|
||||||
github.com/redis/rueidis v1.0.77 h1:ZR41bgJcm7oRFb3aSDPrRhC0eonDSrPzjvvZvHIlNjM=
|
github.com/redis/rueidis v1.0.77 h1:ZR41bgJcm7oRFb3aSDPrRhC0eonDSrPzjvvZvHIlNjM=
|
||||||
github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo=
|
github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo=
|
||||||
github.com/redis/rueidis/rueidiscompat v1.0.77 h1:S1xiLQsFv8XQ46e0LelC8ihGqbDzVRocXXZLzVAeh64=
|
github.com/redis/rueidis/rueidiscompat v1.0.77 h1:S1xiLQsFv8XQ46e0LelC8ihGqbDzVRocXXZLzVAeh64=
|
||||||
@@ -496,20 +557,32 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
|
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
|
||||||
|
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
|
||||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||||
|
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
||||||
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
@@ -533,6 +606,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
@@ -545,14 +619,20 @@ github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77ro
|
|||||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
||||||
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||||
|
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||||
|
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
||||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||||
github.com/urfave/cli/v3 v3.14.0 h1:a8414NQlHJs0c/iBsulKLzlES0n/lEAskbL2LKpU4/s=
|
github.com/urfave/cli/v3 v3.13.0 h1:Dr6jqMfIyyFsRVn7Nz5mqLsMY+ZMpfh3a0aMs+umPVY=
|
||||||
github.com/urfave/cli/v3 v3.14.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4=
|
github.com/urfave/cli/v3 v3.13.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4=
|
||||||
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
|
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
|
||||||
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
||||||
@@ -572,12 +652,12 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0 h1:Q2odjMtdRLG1HEAlx8mv3A7QoLT6hW7tdttTXLX9Mw0=
|
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0 h1:CTUXvcL6OrGjYNafbws5C1lJwlXrxGJHWQGMF+hCdz0=
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.19.0/go.mod h1:UQl7jIdE6nbYNDtc4XWXeiam0V4MJx459e637POlpnQ=
|
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0/go.mod h1:k7uYxRoIeuSqyWZSJfAPZlf0L32OH0lUBImAl4gWUFc=
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
@@ -601,26 +681,41 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
|
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||||
|
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||||
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
|
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
|
||||||
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
|
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||||
|
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
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.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
|
||||||
|
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -628,47 +723,75 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||||
|
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
|
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||||
|
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||||
|
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
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.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -694,6 +817,8 @@ gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
|
|||||||
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -703,10 +828,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
|
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
|
||||||
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
|
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
|
||||||
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
|
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
@@ -715,8 +840,8 @@ modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
|
|||||||
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
|
modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
|
||||||
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
|
modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||||
@@ -725,8 +850,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
|||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw=
|
modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
|
||||||
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
|
modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@@ -518,20 +518,3 @@ func ModifyColumn(ctx context.Context, x EngineMigration, tableName string, col
|
|||||||
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
||||||
return db.Iterate(ctx, cond, f)
|
return db.Iterate(ctx, cond, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EngineTransaction(x EngineMigration, f func(Session) error) error {
|
|
||||||
sess := x.NewSession()
|
|
||||||
defer sess.Close()
|
|
||||||
if err := sess.Begin(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err := f(sess)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sess.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func IterateTableByColumn[Bean any](ctx context.Context, x EngineMigration, sess Session, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
|
||||||
return db.IterateTableByColumn(ctx, x, sess, colName, cond, f)
|
|
||||||
}
|
|
||||||
@@ -45,15 +45,38 @@ func Test_DropTableColumns(t *testing.T) {
|
|||||||
x.SetMapper(names.GonicMapper{})
|
x.SetMapper(names.GonicMapper{})
|
||||||
|
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
for j := i; j < len(columns); j++ {
|
if err := x.Sync(new(DropTest)); err != nil {
|
||||||
|
t.Errorf("unable to create DropTest table: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
sess.Close()
|
||||||
|
t.Errorf("unable to begin transaction: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := base.DropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
|
||||||
|
sess.Close()
|
||||||
|
t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
sess.Close()
|
||||||
|
t.Errorf("unable to commit transaction: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sess.Close()
|
||||||
|
if err := x.DropTables(new(DropTest)); err != nil {
|
||||||
|
t.Errorf("unable to drop table: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := range columns[i+1:] {
|
||||||
if err := x.Sync(new(DropTest)); err != nil {
|
if err := x.Sync(new(DropTest)); err != nil {
|
||||||
t.Errorf("unable to create DropTest table: %v", err)
|
t.Errorf("unable to create DropTest table: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dropcols := columns[i:]
|
dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
|
||||||
if j > i {
|
|
||||||
dropcols = append([]string{columns[i]}, columns[j:]...)
|
|
||||||
}
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
if err := sess.Begin(); err != nil {
|
if err := sess.Begin(); err != nil {
|
||||||
sess.Close()
|
sess.Close()
|
||||||
@@ -62,11 +85,7 @@ func Test_DropTableColumns(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if err := base.DropTableColumns(sess, "drop_test", dropcols...); err != nil {
|
if err := base.DropTableColumns(sess, "drop_test", dropcols...); err != nil {
|
||||||
sess.Close()
|
sess.Close()
|
||||||
if j == i {
|
t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
|
||||||
t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
|
|
||||||
} else {
|
|
||||||
t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := sess.Commit(); err != nil {
|
if err := sess.Commit(); err != nil {
|
||||||
|
|||||||
@@ -431,16 +431,9 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
|
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
|
||||||
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
|
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
|
||||||
// Gitea 28.0.0 ends at migration ID number 355 (database version 356)
|
// Gitea 28.0.0 ends at migration ID number 355 (database version 356)
|
||||||
|
// HERE: 2 migrations from 29 are added since they don't change database structure
|
||||||
newMigration(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
|
newMigration(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
|
||||||
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
|
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
|
||||||
newMigration(358, "Track previous remember-me token hashes", v29.AddPreviousAuthTokenHash),
|
|
||||||
newMigration(359, "Add stage and number columns to action_task_step", v29.AddStageAndNumberToActionTaskStep),
|
|
||||||
newMigration(360, "Move branch protection settings to JSON", v29.MoveBranchProtectionConfigToJSON),
|
|
||||||
newMigration(361, "Drop legacy commit_id column from comment", v29.DropCommentCommitID),
|
|
||||||
newMigration(362, "Add reusable_workflow_ref column to action_run_job", v29.AddReusableWorkflowRefToActionRunJob),
|
|
||||||
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
|
|
||||||
newMigration(364, "Add new table project_workflow", v29.AddProjectWorkflow),
|
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func LoadTableSchemasMap(t *testing.T, x base.EngineMigration) map[string]*schem
|
|||||||
|
|
||||||
func mainTest(m *testing.M) int {
|
func mainTest(m *testing.M) int {
|
||||||
testlogger.Init()
|
testlogger.Init()
|
||||||
workPath, err := setting.PrepareIntegrationTestConfig()
|
err := setting.PrepareIntegrationTestConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return testlogger.MainErrorf("Unable to prepare integration test config: %v", err)
|
return testlogger.MainErrorf("Unable to prepare integration test config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -117,11 +117,7 @@ func mainTest(m *testing.M) int {
|
|||||||
setting.Database.SlowQueryThreshold = 0
|
setting.Database.SlowQueryThreshold = 0
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
setting.InitLoggersForTest()
|
setting.InitLoggersForTest()
|
||||||
exitCode := m.Run()
|
return m.Run()
|
||||||
if exitCode == 0 {
|
|
||||||
_ = os.RemoveAll(workPath)
|
|
||||||
}
|
|
||||||
return exitCode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MainTest(m *testing.M) {
|
func MainTest(m *testing.M) {
|
||||||
|
|||||||
@@ -78,17 +78,12 @@ func Test_ConvertScopedAccessTokens(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(0), count)
|
assert.Equal(t, int64(0), count)
|
||||||
|
|
||||||
session := x.NewSession()
|
|
||||||
defer session.Close()
|
|
||||||
assert.NoError(t, session.Begin())
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
_, err = session.Insert(&AccessToken{
|
_, err = x.Insert(&AccessToken{
|
||||||
Scope: string(tc.Old),
|
Scope: string(tc.Old),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.NoError(t, session.Commit())
|
|
||||||
|
|
||||||
// migrate the scopes
|
// migrate the scopes
|
||||||
err = ConvertScopedAccessTokens(t.Context(), x)
|
err = ConvertScopedAccessTokens(t.Context(), x)
|
||||||
|
|||||||
@@ -43,11 +43,13 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check that we have duplicates
|
// check that we have duplicates
|
||||||
for _, userID := range []int{1, 3} {
|
count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
|
||||||
count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, int64(2), count)
|
||||||
assert.Equal(t, int64(2), count)
|
|
||||||
}
|
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), count)
|
||||||
|
|
||||||
totalCount, err := x.Count(&UserBadgeBefore{})
|
totalCount, err := x.Count(&UserBadgeBefore{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -60,11 +62,13 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify the duplicates were removed
|
// verify the duplicates were removed
|
||||||
for _, userID := range []int{1, 3} {
|
count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
|
||||||
count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
assert.Equal(t, int64(1), count)
|
||||||
assert.Equal(t, int64(1), count)
|
|
||||||
}
|
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), count)
|
||||||
|
|
||||||
// check total count
|
// check total count
|
||||||
totalCount, err = x.Count(&UserBadgeBefore{})
|
totalCount, err = x.Count(&UserBadgeBefore{})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none.
|
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none.
|
||||||
// https://github.com/go-gitea/gitea/pull/34128 made non-admin teams use team_unit (authorize=none).
|
// https://github.com/go-gitea/gitea/pull/34128 made non-admin teams use team_unit (authorize=none).
|
||||||
// Any positive authorize now means blanket access on every unit; migrating legacy read/write
|
// authorize>=write now means blanket access on every unit; migrating legacy read/write
|
||||||
// to none preserves their existing team_unit-scoped access.
|
// to none preserves their existing team_unit-scoped access.
|
||||||
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
|
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
|
||||||
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3
|
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddPreviousAuthTokenHash(_ context.Context, x base.EngineMigration) error {
|
|
||||||
type AuthToken struct {
|
|
||||||
PreviousTokenHash string
|
|
||||||
RotatedUnix timeutil.TimeStamp
|
|
||||||
}
|
|
||||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
|
||||||
IgnoreDropIndices: true,
|
|
||||||
IgnoreConstrains: true,
|
|
||||||
}, new(AuthToken))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddStageAndNumberToActionTaskStep(_ context.Context, x base.EngineMigration) error {
|
|
||||||
type ActionTaskStep struct {
|
|
||||||
Stage int64 `xorm:"NOT NULL DEFAULT 0"`
|
|
||||||
Number int64 `xorm:"NOT NULL DEFAULT 0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
|
||||||
IgnoreConstrains: true,
|
|
||||||
IgnoreDropIndices: true,
|
|
||||||
}, new(ActionTaskStep))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
"gitea.dev/modules/json"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type protectedBranchConfigV358 struct {
|
|
||||||
CanPush bool `xorm:"NOT NULL DEFAULT false" json:"can_push,omitzero"`
|
|
||||||
EnableWhitelist bool `json:"enable_whitelist,omitzero"`
|
|
||||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"whitelist_user_ids,omitzero"`
|
|
||||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"whitelist_team_ids,omitzero"`
|
|
||||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_merge_whitelist,omitzero"`
|
|
||||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
|
|
||||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_user_ids,omitzero"`
|
|
||||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_team_ids,omitzero"`
|
|
||||||
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_bypass_allowlist,omitzero"`
|
|
||||||
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_user_ids,omitzero"`
|
|
||||||
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_team_ids,omitzero"`
|
|
||||||
CanForcePush bool `xorm:"NOT NULL DEFAULT false" json:"can_force_push,omitzero"`
|
|
||||||
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_force_push_allowlist,omitzero"`
|
|
||||||
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_user_ids,omitzero"`
|
|
||||||
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_team_ids,omitzero"`
|
|
||||||
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
|
|
||||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false" json:"enable_status_check,omitzero"`
|
|
||||||
StatusCheckContexts []string `xorm:"JSON TEXT" json:"status_check_contexts,omitzero"`
|
|
||||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_approvals_whitelist,omitzero"`
|
|
||||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_user_ids,omitzero"`
|
|
||||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_team_ids,omitzero"`
|
|
||||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0" json:"required_approvals,omitzero"`
|
|
||||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_rejected_reviews,omitzero"`
|
|
||||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false" json:"block_on_official_review_requests,omitzero"`
|
|
||||||
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_codeowner_reviews,omitzero"`
|
|
||||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false" json:"block_on_outdated_branch,omitzero"`
|
|
||||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"dismiss_stale_approvals,omitzero"`
|
|
||||||
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"ignore_stale_approvals,omitzero"`
|
|
||||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false" json:"require_signed_commits,omitzero"`
|
|
||||||
ProtectedFilePatterns string `xorm:"TEXT" json:"protected_file_patterns,omitzero"`
|
|
||||||
UnprotectedFilePatterns string `xorm:"TEXT" json:"unprotected_file_patterns,omitzero"`
|
|
||||||
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false" json:"block_admin_merge_override,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type protectedBranchV358 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
|
||||||
BranchName string `xorm:"UNIQUE(s)"`
|
|
||||||
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
|
||||||
Config string `xorm:"LONGTEXT"`
|
|
||||||
CreatedUnix timeutil.TimeStamp
|
|
||||||
UpdatedUnix timeutil.TimeStamp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protectedBranchV358) TableName() string { return "protected_branch" }
|
|
||||||
|
|
||||||
type intermediateProtectedBranch struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
NewConfig string `xorm:"LONGTEXT config"`
|
|
||||||
OldConfig protectedBranchConfigV358 `xorm:"extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (intermediateProtectedBranch) TableName() string { return "protected_branch" }
|
|
||||||
|
|
||||||
func MoveBranchProtectionConfigToJSON(ctx context.Context, x base.EngineMigration) error {
|
|
||||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(protectedBranchV358)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exists, err := x.Dialect().IsColumnExist(x.DB(), ctx, "protected_branch", "can_push")
|
|
||||||
if err != nil || !exists {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = base.EngineTransaction(x, func(sess base.Session) error {
|
|
||||||
err := base.IterateTableByColumn(ctx, x, sess, "id", nil, func(ctx context.Context, pb *intermediateProtectedBranch) error {
|
|
||||||
if pb.NewConfig != "" {
|
|
||||||
return nil // skip the already migrated fields
|
|
||||||
}
|
|
||||||
const deployKeyUserID int64 = -3 // frozen user.DeployKeyUserID
|
|
||||||
if pb.OldConfig.WhitelistDeployKeys {
|
|
||||||
pb.OldConfig.WhitelistUserIDs = append(pb.OldConfig.WhitelistUserIDs, deployKeyUserID)
|
|
||||||
}
|
|
||||||
if pb.OldConfig.ForcePushAllowlistDeployKeys {
|
|
||||||
pb.OldConfig.ForcePushAllowlistUserIDs = append(pb.OldConfig.ForcePushAllowlistUserIDs, deployKeyUserID)
|
|
||||||
}
|
|
||||||
configJson, err := json.Marshal(pb.OldConfig)
|
|
||||||
util.MustNoError(err)
|
|
||||||
_, err = sess.ID(pb.ID).Cols("config").Update(&protectedBranchV358{Config: string(configJson)})
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
legacyTableInfo, err := x.TableInfo(new(protectedBranchConfigV358))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return base.DropTableColumns(sess, "protected_branch", legacyTableInfo.ColumnsSeq()...)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// SQLite rebuilds the table when dropping columns, so restore its unique index.
|
|
||||||
return x.Sync(new(protectedBranchV358))
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/migrationtest"
|
|
||||||
"gitea.dev/modules/json"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type protectedBranchBeforeV358 struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
|
||||||
BranchName string `xorm:"UNIQUE(s)"`
|
|
||||||
Priority int64
|
|
||||||
CreatedUnix timeutil.TimeStamp
|
|
||||||
UpdatedUnix timeutil.TimeStamp
|
|
||||||
Config protectedBranchConfigV358 `xorm:"extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protectedBranchBeforeV358) TableName() string { return "protected_branch" }
|
|
||||||
|
|
||||||
func TestMoveBranchProtectionConfigToJSON(t *testing.T) {
|
|
||||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(protectedBranchBeforeV358))
|
|
||||||
defer deferrable()
|
|
||||||
if x == nil || t.Failed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const configJSON = `{
|
|
||||||
"can_push":true,
|
|
||||||
"enable_whitelist":true,
|
|
||||||
"whitelist_user_ids":[2,4,-3],
|
|
||||||
"whitelist_team_ids":[1,2],
|
|
||||||
"enable_merge_whitelist":true,
|
|
||||||
"merge_whitelist_user_ids":[3,5],
|
|
||||||
"merge_whitelist_team_ids":[3,4],
|
|
||||||
"enable_bypass_allowlist":true,
|
|
||||||
"bypass_allowlist_user_ids":[6,7],
|
|
||||||
"bypass_allowlist_team_ids":[5,6],
|
|
||||||
"can_force_push":true,
|
|
||||||
"enable_force_push_allowlist":true,
|
|
||||||
"force_push_allowlist_user_ids":[8,9,-3],
|
|
||||||
"force_push_allowlist_team_ids":[7,8],
|
|
||||||
"enable_status_check":true,
|
|
||||||
"status_check_contexts":["ci/*","lint"],
|
|
||||||
"enable_approvals_whitelist":true,
|
|
||||||
"approvals_whitelist_user_ids":[10,11],
|
|
||||||
"approvals_whitelist_team_ids":[9,10],
|
|
||||||
"required_approvals":2,
|
|
||||||
"block_on_rejected_reviews":true,
|
|
||||||
"block_on_official_review_requests":true,
|
|
||||||
"block_on_codeowner_reviews":true,
|
|
||||||
"block_on_outdated_branch":true,
|
|
||||||
"dismiss_stale_approvals":true,
|
|
||||||
"ignore_stale_approvals":true,
|
|
||||||
"require_signed_commits":true,
|
|
||||||
"protected_file_patterns":"*.go;*.md",
|
|
||||||
"unprotected_file_patterns":"docs/**",
|
|
||||||
"block_admin_merge_override":true
|
|
||||||
}`
|
|
||||||
var config protectedBranchConfigV358
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(configJSON), &config))
|
|
||||||
config.WhitelistUserIDs = []int64{2, 4}
|
|
||||||
config.ForcePushAllowlistUserIDs = []int64{8, 9}
|
|
||||||
config.WhitelistDeployKeys = true
|
|
||||||
config.ForcePushAllowlistDeployKeys = true
|
|
||||||
for i := range 101 {
|
|
||||||
branch := protectedBranchBeforeV358{RepoID: 1, BranchName: fmt.Sprintf("release-%d/*", i), Priority: int64(i), CreatedUnix: 123, UpdatedUnix: 456}
|
|
||||||
if i%2 == 0 {
|
|
||||||
branch.Config = config
|
|
||||||
} else if i == 1 {
|
|
||||||
branch.Config.StatusCheckContexts = []string{}
|
|
||||||
}
|
|
||||||
_, err := x.Insert(&branch)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
|
|
||||||
// A retry must preserve the migrated configuration and its indexes.
|
|
||||||
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
|
|
||||||
|
|
||||||
var branches []protectedBranchV358
|
|
||||||
require.NoError(t, x.Asc("id").Find(&branches))
|
|
||||||
require.Len(t, branches, 101)
|
|
||||||
for i, branch := range branches {
|
|
||||||
assert.EqualValues(t, i+1, branch.ID)
|
|
||||||
assert.EqualValues(t, 1, branch.RepoID)
|
|
||||||
assert.Equal(t, fmt.Sprintf("release-%d/*", i), branch.BranchName)
|
|
||||||
assert.EqualValues(t, i, branch.Priority)
|
|
||||||
assert.EqualValues(t, 123, branch.CreatedUnix)
|
|
||||||
assert.EqualValues(t, 456, branch.UpdatedUnix)
|
|
||||||
if i%2 == 0 {
|
|
||||||
assert.JSONEq(t, configJSON, branch.Config)
|
|
||||||
} else if i == 1 {
|
|
||||||
assert.JSONEq(t, `{"status_check_contexts":[]}`, branch.Config)
|
|
||||||
} else {
|
|
||||||
assert.JSONEq(t, "{}", branch.Config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tables, err := x.DBMetas()
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, table := range tables {
|
|
||||||
if table.Name == "protected_branch" {
|
|
||||||
assert.ElementsMatch(t, []string{"id", "repo_id", "branch_name", "priority", "config", "created_unix", "updated_unix"}, table.ColumnsSeq())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = x.Insert(&protectedBranchV358{RepoID: 1, BranchName: branches[0].BranchName})
|
|
||||||
require.Error(t, err, "the repository/rule unique constraint must survive the migration")
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DropCommentCommitID(ctx context.Context, x base.EngineMigration) error {
|
|
||||||
exists, err := x.Dialect().IsColumnExist(x.DB(), ctx, "comment", "commit_id")
|
|
||||||
if err != nil || !exists {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.EngineTransaction(x, func(sess base.Session) error {
|
|
||||||
return base.DropTableColumns(sess, "comment", "commit_id")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddReusableWorkflowRefToActionRunJob(_ context.Context, x base.EngineMigration) error {
|
|
||||||
type ActionRunJob struct {
|
|
||||||
ReusableWorkflowRef string `xorm:"VARCHAR(512) NOT NULL DEFAULT ''"`
|
|
||||||
}
|
|
||||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
|
||||||
IgnoreConstrains: true,
|
|
||||||
IgnoreDropIndices: true,
|
|
||||||
}, new(ActionRunJob))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddReleaseIDToReaction(_ctx context.Context, x base.EngineMigration) error {
|
|
||||||
// rows older than v123 can hold NULL, which the unique index doesn't treat as a duplicate of ''
|
|
||||||
if _, err := x.Exec("UPDATE reaction SET original_author = '' WHERE original_author IS NULL"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type Reaction struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
|
|
||||||
UserID int64 `xorm:"UNIQUE(s) NOT NULL"` // first unique column implies INDEX
|
|
||||||
IssueID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) NOT NULL"`
|
|
||||||
CommentID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) INDEX NOT NULL"`
|
|
||||||
ReleaseID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT(0)"`
|
|
||||||
OriginalAuthorID int64 `xorm:"UNIQUE(s) NOT NULL DEFAULT(0)"`
|
|
||||||
OriginalAuthor string `xorm:"UNIQUE(s) NOT NULL DEFAULT('')"`
|
|
||||||
Type string `xorm:"UNIQUE(s) NOT NULL"`
|
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
|
||||||
}
|
|
||||||
return x.Sync(new(Reaction))
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v29
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.dev/modelmigration/base"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddProjectWorkflow(_ context.Context, x base.EngineMigration) error {
|
|
||||||
type ProjectWorkflow struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
ProjectID int64 `xorm:"INDEX"`
|
|
||||||
WorkflowEvent string
|
|
||||||
WorkflowFilters string `xorm:"TEXT JSON"`
|
|
||||||
WorkflowActions string `xorm:"TEXT JSON"`
|
|
||||||
Enabled bool `xorm:"DEFAULT true NOT NULL"`
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
|
||||||
}
|
|
||||||
|
|
||||||
return x.Sync(&ProjectWorkflow{})
|
|
||||||
}
|
|
||||||
@@ -93,12 +93,7 @@ const artifactKeepForever timeutil.TimeStamp = 0
|
|||||||
|
|
||||||
func artifactExpiry(requested optional.Option[timeutil.TimeStamp]) timeutil.TimeStamp {
|
func artifactExpiry(requested optional.Option[timeutil.TimeStamp]) timeutil.TimeStamp {
|
||||||
if requested.Has() {
|
if requested.Has() {
|
||||||
now := timeutil.TimeStampNow()
|
return max(requested.Value(), artifactKeepForever+1)
|
||||||
expiry := max(requested.Value(), now.Add(timeutil.Day)) // v4 clients use the runner's clock
|
|
||||||
if setting.Actions.ArtifactRetentionDays > 0 {
|
|
||||||
expiry = min(expiry, now.Add(timeutil.Day*setting.Actions.ArtifactRetentionDays))
|
|
||||||
}
|
|
||||||
return expiry
|
|
||||||
}
|
}
|
||||||
if setting.Actions.ArtifactRetentionDays <= 0 {
|
if setting.Actions.ArtifactRetentionDays <= 0 {
|
||||||
return artifactKeepForever
|
return artifactKeepForever
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dev/modules/optional"
|
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/test"
|
|
||||||
"gitea.dev/modules/timeutil"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -31,21 +25,3 @@ func TestKeepLatestAttemptArtifacts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, []int64{1, 4, 5}, ids)
|
assert.Equal(t, []int64{1, 4, 5}, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArtifactExpiry(t *testing.T) {
|
|
||||||
defer timeutil.MockSet(time.Unix(1_000_000_000, 0))()
|
|
||||||
now := timeutil.TimeStampNow()
|
|
||||||
days := func(n int64) timeutil.TimeStamp { return now.Add(n * timeutil.Day) }
|
|
||||||
requested := optional.Some[timeutil.TimeStamp]
|
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.Actions.ArtifactRetentionDays, 90)()
|
|
||||||
assert.Equal(t, days(90), artifactExpiry(optional.None[timeutil.TimeStamp]()))
|
|
||||||
assert.Equal(t, days(5), artifactExpiry(requested(days(5))))
|
|
||||||
assert.Equal(t, days(1), artifactExpiry(requested(days(-2))), "runner clock behind must not expire the artifact at once")
|
|
||||||
assert.Equal(t, days(90), artifactExpiry(requested(days(1000))), "runner must not exceed the instance retention")
|
|
||||||
|
|
||||||
setting.Actions.ArtifactRetentionDays = 0
|
|
||||||
assert.Equal(t, artifactKeepForever, artifactExpiry(optional.None[timeutil.TimeStamp]()))
|
|
||||||
assert.Equal(t, days(1000), artifactExpiry(requested(days(1000))))
|
|
||||||
assert.Equal(t, days(1), artifactExpiry(requested(now)))
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package actions
|
|
||||||
|
|
||||||
// BelongingScope identifies the owner (user/org) or repository an Actions secret or variable belongs to.
|
|
||||||
// Both zero values mean "global scope".
|
|
||||||
// Always use the functions to create a BelongingScope, rather than creating one directly, to avoid accidentally creating an invalid scope.
|
|
||||||
type BelongingScope struct {
|
|
||||||
internalOwnerID int64
|
|
||||||
internalRepoID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func BelongingScopeOwner(ownerID int64) BelongingScope {
|
|
||||||
return BelongingScope{internalOwnerID: ownerID}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BelongingScopeRepo(repoID int64) BelongingScope {
|
|
||||||
return BelongingScope{internalRepoID: repoID}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BelongingScopeGlobal() BelongingScope {
|
|
||||||
return BelongingScope{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scope BelongingScope) GetOwnerRepoIDs() (ownerID, repoID int64) {
|
|
||||||
return scope.internalOwnerID, scope.internalRepoID
|
|
||||||
}
|
|
||||||
@@ -64,7 +64,7 @@ func (cfg *OwnerActionsConfig) GetMaxTokenPermissions() repo_model.ActionsTokenP
|
|||||||
return *cfg.MaxTokenPermissions
|
return *cfg.MaxTokenPermissions
|
||||||
}
|
}
|
||||||
// Default max is write for everything
|
// Default max is write for everything
|
||||||
return repo_model.MakeWriteAllActionsTokenPermissions()
|
return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClampPermissions ensures that the given permissions don't exceed the maximum
|
// ClampPermissions ensures that the given permissions don't exceed the maximum
|
||||||
|
|||||||
+14
-18
@@ -15,7 +15,6 @@ import (
|
|||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/container"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/json"
|
"gitea.dev/modules/json"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
@@ -297,9 +296,7 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
|
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
|
||||||
if len(runIDs) > 0 {
|
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
|
||||||
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
|
|
||||||
}
|
|
||||||
return runs, err
|
return runs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,27 +406,26 @@ func CancelPreviousJobsByRunConcurrency(ctx context.Context, attempt *ActionRunA
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jobsToCancel []*ActionRunJob
|
||||||
|
|
||||||
attempts, jobs, err := getConcurrencyEntriesToReplace(ctx, attempt.RepoID, attempt.ConcurrencyGroup, attempt.ConcurrencyCancel)
|
attempts, jobs, err := getConcurrencyEntriesToReplace(ctx, attempt.RepoID, attempt.ConcurrencyGroup, attempt.ConcurrencyCancel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
||||||
}
|
}
|
||||||
jobs = slices.DeleteFunc(jobs, func(job *ActionRunJob) bool { return job.RunID == attempt.RunID })
|
jobs = slices.DeleteFunc(jobs, func(job *ActionRunJob) bool { return job.RunID == attempt.RunID })
|
||||||
|
jobsToCancel = append(jobsToCancel, jobs...)
|
||||||
|
|
||||||
// cancel runs in the same concurrency group
|
// cancel runs in the same concurrency group
|
||||||
attempts = slices.DeleteFunc(attempts, func(concurrentAttempt *ActionRunAttempt) bool { return concurrentAttempt.RunID == attempt.RunID })
|
for _, concurrentAttempt := range attempts {
|
||||||
attemptJobs, err := getRunJobsByAttempts(ctx, attempts)
|
if concurrentAttempt.RunID == attempt.RunID {
|
||||||
if err != nil {
|
continue
|
||||||
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
|
|
||||||
}
|
|
||||||
return CancelJobs(ctx, append(jobs, attemptJobs...), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRunJobsByAttempts(ctx context.Context, attempts []*ActionRunAttempt) (jobs []*ActionRunJob, _ error) {
|
|
||||||
for chunk := range slices.Chunk(attempts, db.DefaultMaxInSize) {
|
|
||||||
attemptIDs := container.FilterSlice(chunk, func(attempt *ActionRunAttempt) (int64, bool) { return attempt.ID, true })
|
|
||||||
if err := db.GetEngine(ctx).In("run_attempt_id", attemptIDs).Find(&jobs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
jobs, err := GetRunJobsByRunAndAttemptID(ctx, concurrentAttempt.RunID, concurrentAttempt.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("find run %d attempt %d jobs: %w", concurrentAttempt.RunID, concurrentAttempt.ID, err)
|
||||||
|
}
|
||||||
|
jobsToCancel = append(jobsToCancel, jobs...)
|
||||||
}
|
}
|
||||||
return jobs, nil
|
|
||||||
|
return CancelJobs(ctx, jobsToCancel, false)
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ func UpdateRunAttempt(ctx context.Context, attempt *ActionRunAttempt, cols ...st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if run.LatestAttemptID != attempt.ID {
|
if run.LatestAttemptID != attempt.ID {
|
||||||
log.Warn("run %d cannot be updated by old attempt %d, latest attempt is %d", run.ID, attempt.ID, run.LatestAttemptID)
|
log.Warn("run %d cannot be updated by an old attempt %d", run.LatestAttemptID, attempt.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-31
@@ -110,8 +110,6 @@ type ActionRunJob struct {
|
|||||||
// ReusableWorkflowContent is the content of the reusable workflow specified by "uses:".
|
// ReusableWorkflowContent is the content of the reusable workflow specified by "uses:".
|
||||||
// Only set when IsReusableCaller is true.
|
// Only set when IsReusableCaller is true.
|
||||||
ReusableWorkflowContent []byte `xorm:"LONGBLOB"`
|
ReusableWorkflowContent []byte `xorm:"LONGBLOB"`
|
||||||
// ReusableWorkflowRef is the fully qualified ref, or the commit, that "uses:" resolved to when the content was loaded.
|
|
||||||
ReusableWorkflowRef string `xorm:"VARCHAR(512) NOT NULL DEFAULT ''"`
|
|
||||||
// CallSecrets encodes the reusable workflow caller's "secrets:" section:
|
// CallSecrets encodes the reusable workflow caller's "secrets:" section:
|
||||||
// - "" : no "secrets:" section (children only see auto-generated tokens).
|
// - "" : no "secrets:" section (children only see auto-generated tokens).
|
||||||
// - "inherit" : the caller wrote "secrets: inherit".
|
// - "inherit" : the caller wrote "secrets: inherit".
|
||||||
@@ -273,24 +271,6 @@ func GetLatestAttemptJobsByRun(ctx context.Context, run *ActionRun) (ActionJobLi
|
|||||||
return jobs, nil
|
return jobs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLatestAttemptJobsByRuns(ctx context.Context, runs []*ActionRun) (map[int64]ActionJobList, error) {
|
|
||||||
result := make(map[int64]ActionJobList, len(runs))
|
|
||||||
if len(runs) == 0 {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
attemptIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { return run.LatestAttemptID, run.LatestAttemptID != 0 })
|
|
||||||
legacyRunIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { return run.ID, run.LatestAttemptID == 0 })
|
|
||||||
cond := builder.In("run_attempt_id", attemptIDs).Or(builder.Eq{"run_attempt_id": 0}.And(builder.In("run_id", legacyRunIDs)))
|
|
||||||
var jobs []*ActionRunJob
|
|
||||||
if err := db.GetEngine(ctx).Where(cond).OrderBy("id").Find(&jobs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, job := range jobs {
|
|
||||||
result[job.RunID] = append(result[job.RunID], job)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllRunJobsByRepoAndRunID returns all jobs for a run across all attempts.
|
// GetAllRunJobsByRepoAndRunID returns all jobs for a run across all attempts.
|
||||||
func GetAllRunJobsByRepoAndRunID(ctx context.Context, repoID, runID int64) (ActionJobList, error) {
|
func GetAllRunJobsByRepoAndRunID(ctx context.Context, repoID, runID int64) (ActionJobList, error) {
|
||||||
var jobs []*ActionRunJob
|
var jobs []*ActionRunJob
|
||||||
@@ -705,18 +685,43 @@ func hasFailFastMatrixFailure(jobs []*ActionRunJob) bool {
|
|||||||
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
|
||||||
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
|
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
|
||||||
// Find all runs in the specified repository, reference, and workflow with non-final status
|
// Find all runs in the specified repository, reference, and workflow with non-final status
|
||||||
runIDs := builder.Select("id").From("action_run").Where(FindRunOptions{
|
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
WorkflowID: workflowID,
|
WorkflowID: workflowID,
|
||||||
TriggerEvent: event,
|
TriggerEvent: event,
|
||||||
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked, StatusCancelling},
|
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked, StatusCancelling},
|
||||||
}.ToConds())
|
})
|
||||||
var jobs []*ActionRunJob
|
if err != nil {
|
||||||
if err := db.GetEngine(ctx).Where(builder.In("run_id", runIDs)).Find(&jobs); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return CancelJobs(ctx, jobs, false)
|
|
||||||
|
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
|
||||||
|
if total == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelledJobs := make([]*ActionRunJob, 0, total)
|
||||||
|
|
||||||
|
// Iterate over each found run and cancel its associated jobs.
|
||||||
|
for _, run := range runs {
|
||||||
|
// Find all jobs associated with the current run.
|
||||||
|
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
|
||||||
|
RunID: run.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return cancelledJobs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cjs, err := CancelJobs(ctx, jobs, false)
|
||||||
|
if err != nil {
|
||||||
|
return cancelledJobs, err
|
||||||
|
}
|
||||||
|
cancelledJobs = append(cancelledJobs, cjs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nil to indicate successful cancellation of all running and waiting jobs.
|
||||||
|
return cancelledJobs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAncestorCallerIDs returns the IDs of the reusable workflow callers the job is nested in.
|
// GetAncestorCallerIDs returns the IDs of the reusable workflow callers the job is nested in.
|
||||||
@@ -733,7 +738,7 @@ func GetAncestorCallerIDs(ctx context.Context, job *ActionRunJob) (container.Set
|
|||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) ([]*ActionRunJob, error) {
|
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) (jobsToCancel []*ActionRunJob, _ error) {
|
||||||
if job.RawConcurrency == "" {
|
if job.RawConcurrency == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -753,14 +758,21 @@ func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID || callerIDs.Contains(j.ID) })
|
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID || callerIDs.Contains(j.ID) })
|
||||||
|
jobsToCancel = append(jobsToCancel, jobs...)
|
||||||
|
|
||||||
// cancel runs in the same concurrency group
|
// cancel runs in the same concurrency group
|
||||||
attempts = slices.DeleteFunc(attempts, func(attempt *ActionRunAttempt) bool { return attempt.ID == job.RunAttemptID })
|
for _, attempt := range attempts {
|
||||||
attemptJobs, err := getRunJobsByAttempts(ctx, attempts)
|
if attempt.ID == job.RunAttemptID {
|
||||||
if err != nil {
|
continue
|
||||||
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
|
}
|
||||||
|
jobs, err := GetRunJobsByRunAndAttemptID(ctx, attempt.RunID, attempt.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("find run %d attempt %d jobs: %w", attempt.RunID, attempt.ID, err)
|
||||||
|
}
|
||||||
|
jobsToCancel = append(jobsToCancel, jobs...)
|
||||||
}
|
}
|
||||||
return CancelJobs(ctx, append(jobs, attemptJobs...), false)
|
|
||||||
|
return CancelJobs(ctx, jobsToCancel, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelJobs cancels every cancellable job it is given, force skipping the graceful cancelling
|
// CancelJobs cancels every cancellable job it is given, force skipping the graceful cancelling
|
||||||
|
|||||||
@@ -10,6 +10,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAggregateJobStatus(t *testing.T) {
|
func TestAggregateJobStatus(t *testing.T) {
|
||||||
|
testStatuses := func(expected Status, statuses []Status) {
|
||||||
|
t.Helper()
|
||||||
|
var jobs []*ActionRunJob
|
||||||
|
for _, v := range statuses {
|
||||||
|
jobs = append(jobs, &ActionRunJob{Status: v})
|
||||||
|
}
|
||||||
|
actual := AggregateJobStatus(jobs)
|
||||||
|
if !assert.Equal(t, expected, actual) {
|
||||||
|
var statusStrings []string
|
||||||
|
for _, s := range statuses {
|
||||||
|
statusStrings = append(statusStrings, s.String())
|
||||||
|
}
|
||||||
|
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
statuses []Status
|
statuses []Status
|
||||||
expected Status
|
expected Status
|
||||||
@@ -76,17 +92,6 @@ func TestAggregateJobStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
var jobs []*ActionRunJob
|
testStatuses(c.expected, c.statuses)
|
||||||
for _, status := range c.statuses {
|
|
||||||
jobs = append(jobs, &ActionRunJob{Status: status})
|
|
||||||
}
|
|
||||||
actual := AggregateJobStatus(jobs)
|
|
||||||
if !assert.Equal(t, c.expected, actual) {
|
|
||||||
var statusStrings []string
|
|
||||||
for _, status := range c.statuses {
|
|
||||||
statusStrings = append(statusStrings, status.String())
|
|
||||||
}
|
|
||||||
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[c.expected])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+230
-239
@@ -16,267 +16,189 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunJobDatabase(t *testing.T) {
|
func TestGetPriorAttemptChildrenByParent(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
t.Run("prior attempt children by parent", func(t *testing.T) {
|
// 3 attempts of one run:
|
||||||
ctx := t.Context()
|
// 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
|
||||||
|
// 2: caller skipped, no children rows.
|
||||||
|
// 3: placeholder "current" attempt for the walkback subtest.
|
||||||
|
|
||||||
// 3 attempts of one run:
|
run := &ActionRun{
|
||||||
// 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
|
Title: "prior-children-test",
|
||||||
// 2: caller skipped, no children rows.
|
RepoID: 4,
|
||||||
// 3: placeholder "current" attempt for the walkback subtest.
|
Index: 9501,
|
||||||
|
OwnerID: 1,
|
||||||
|
WorkflowID: "matrix.yaml",
|
||||||
|
TriggerUserID: 1,
|
||||||
|
Ref: "refs/heads/master",
|
||||||
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
EventPayload: "{}",
|
||||||
|
Status: StatusSuccess,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, run))
|
||||||
|
|
||||||
run := &ActionRun{
|
const callerAttemptJobID int64 = 9001
|
||||||
Title: "prior-children-test",
|
insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
|
||||||
RepoID: 4,
|
t.Helper()
|
||||||
Index: 9501,
|
a := &ActionRunAttempt{
|
||||||
OwnerID: 1,
|
RepoID: run.RepoID,
|
||||||
WorkflowID: "matrix.yaml",
|
RunID: run.ID,
|
||||||
|
Attempt: num,
|
||||||
TriggerUserID: 1,
|
TriggerUserID: 1,
|
||||||
Ref: "refs/heads/master",
|
Status: status,
|
||||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
|
||||||
Event: "push",
|
|
||||||
TriggerEvent: "push",
|
|
||||||
EventPayload: "{}",
|
|
||||||
Status: StatusSuccess,
|
|
||||||
}
|
}
|
||||||
require.NoError(t, db.Insert(ctx, run))
|
require.NoError(t, db.Insert(ctx, a))
|
||||||
|
return a
|
||||||
const callerAttemptJobID int64 = 9001
|
}
|
||||||
insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
|
insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
a := &ActionRunAttempt{
|
caller := &ActionRunJob{
|
||||||
RepoID: run.RepoID,
|
RunID: run.ID,
|
||||||
RunID: run.ID,
|
RunAttemptID: attemptID,
|
||||||
Attempt: num,
|
RepoID: run.RepoID,
|
||||||
TriggerUserID: 1,
|
OwnerID: run.OwnerID,
|
||||||
Status: status,
|
CommitSHA: run.CommitSHA,
|
||||||
}
|
Name: "caller",
|
||||||
require.NoError(t, db.Insert(ctx, a))
|
JobID: "caller",
|
||||||
return a
|
Attempt: 1,
|
||||||
}
|
Status: status,
|
||||||
insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
|
AttemptJobID: callerAttemptJobID,
|
||||||
t.Helper()
|
IsReusableCaller: true,
|
||||||
caller := &ActionRunJob{
|
IsExpanded: expanded,
|
||||||
RunID: run.ID,
|
|
||||||
RunAttemptID: attemptID,
|
|
||||||
RepoID: run.RepoID,
|
|
||||||
OwnerID: run.OwnerID,
|
|
||||||
CommitSHA: run.CommitSHA,
|
|
||||||
Name: "caller",
|
|
||||||
JobID: "caller",
|
|
||||||
Attempt: 1,
|
|
||||||
Status: status,
|
|
||||||
AttemptJobID: callerAttemptJobID,
|
|
||||||
IsReusableCaller: true,
|
|
||||||
IsExpanded: expanded,
|
|
||||||
}
|
|
||||||
require.NoError(t, db.Insert(ctx, caller))
|
|
||||||
return caller
|
|
||||||
}
|
|
||||||
insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
|
|
||||||
t.Helper()
|
|
||||||
require.NoError(t, db.Insert(ctx, &ActionRunJob{
|
|
||||||
RunID: run.ID,
|
|
||||||
RunAttemptID: attemptID,
|
|
||||||
RepoID: run.RepoID,
|
|
||||||
OwnerID: run.OwnerID,
|
|
||||||
CommitSHA: run.CommitSHA,
|
|
||||||
Name: name,
|
|
||||||
JobID: jobID,
|
|
||||||
Attempt: 1,
|
|
||||||
Status: StatusSuccess,
|
|
||||||
AttemptJobID: attemptJobID,
|
|
||||||
ParentJobID: parentID,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, caller))
|
||||||
|
return caller
|
||||||
|
}
|
||||||
|
insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, db.Insert(ctx, &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RunAttemptID: attemptID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: name,
|
||||||
|
JobID: jobID,
|
||||||
|
Attempt: 1,
|
||||||
|
Status: StatusSuccess,
|
||||||
|
AttemptJobID: attemptJobID,
|
||||||
|
ParentJobID: parentID,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
attempt1 := insertAttempt(t, 1, StatusSuccess)
|
attempt1 := insertAttempt(t, 1, StatusSuccess)
|
||||||
caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
|
caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
|
||||||
insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
|
insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
|
||||||
insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
|
insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
|
||||||
insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
|
insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
|
||||||
insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
|
insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
|
||||||
|
|
||||||
attempt2 := insertAttempt(t, 2, StatusSkipped)
|
attempt2 := insertAttempt(t, 2, StatusSkipped)
|
||||||
insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
|
insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
|
||||||
|
|
||||||
// both subtests expect attempt 1's expansion, differing only in the "current" attempt id
|
// both subtests expect attempt 1's expansion, differing only in the "current" attempt id
|
||||||
assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
|
assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
|
// outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
|
||||||
assert.Len(t, out, 2)
|
assert.Len(t, out, 2)
|
||||||
assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
|
assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
|
||||||
assert.Len(t, out["summary"], 1)
|
assert.Len(t, out["summary"], 1)
|
||||||
|
|
||||||
require.NotNil(t, out["work"]["work (alpha)"])
|
require.NotNil(t, out["work"]["work (alpha)"])
|
||||||
require.NotNil(t, out["work"]["work (beta)"])
|
require.NotNil(t, out["work"]["work (beta)"])
|
||||||
require.NotNil(t, out["work"]["work (gamma)"])
|
require.NotNil(t, out["work"]["work (gamma)"])
|
||||||
require.NotNil(t, out["summary"]["summary"])
|
require.NotNil(t, out["summary"]["summary"])
|
||||||
|
|
||||||
assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
|
assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
|
||||||
assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
|
assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
|
||||||
assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
|
assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
|
||||||
assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
|
assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
|
t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
|
||||||
// "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
|
// "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
|
||||||
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
|
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertAttempt1Children(t, out)
|
assertAttempt1Children(t, out)
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
|
|
||||||
attempt3 := insertAttempt(t, 3, StatusRunning)
|
|
||||||
// "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
|
|
||||||
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assertAttempt1Children(t, out)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("cancel reusable callers", func(t *testing.T) {
|
t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
attempt3 := insertAttempt(t, 3, StatusRunning)
|
||||||
name, title, workflowID string
|
// "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
|
||||||
index int64
|
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
|
||||||
status Status
|
require.NoError(t, err)
|
||||||
}{
|
assertAttempt1Children(t, out)
|
||||||
{"NestedBlockedReusableCaller", "cancel-nested-caller", "caller.yaml", 9701, StatusBlocked},
|
|
||||||
{"CallerWaitsForCancellingChild", "caller-cancelling-child", "test.yaml", 9811, StatusRunning},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
ctx := t.Context()
|
|
||||||
|
|
||||||
run := &ActionRun{
|
|
||||||
Title: tc.title,
|
|
||||||
RepoID: 4,
|
|
||||||
Index: tc.index,
|
|
||||||
OwnerID: 1,
|
|
||||||
WorkflowID: tc.workflowID,
|
|
||||||
TriggerUserID: 1,
|
|
||||||
Ref: "refs/heads/master",
|
|
||||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
|
||||||
Event: "push",
|
|
||||||
TriggerEvent: "push",
|
|
||||||
EventPayload: "{}",
|
|
||||||
Status: tc.status,
|
|
||||||
}
|
|
||||||
require.NoError(t, db.Insert(ctx, run))
|
|
||||||
|
|
||||||
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: tc.status}
|
|
||||||
require.NoError(t, db.Insert(ctx, attempt))
|
|
||||||
run.LatestAttemptID = attempt.ID
|
|
||||||
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
|
||||||
|
|
||||||
newJob := func(job *ActionRunJob) *ActionRunJob {
|
|
||||||
job.RunID = run.ID
|
|
||||||
job.RunAttemptID = attempt.ID
|
|
||||||
job.RepoID = run.RepoID
|
|
||||||
job.OwnerID = run.OwnerID
|
|
||||||
job.CommitSHA = run.CommitSHA
|
|
||||||
job.JobID = job.Name
|
|
||||||
job.Attempt = 1
|
|
||||||
job.Status = tc.status
|
|
||||||
require.NoError(t, db.Insert(ctx, job))
|
|
||||||
return job
|
|
||||||
}
|
|
||||||
|
|
||||||
// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
|
|
||||||
if tc.status == StatusBlocked {
|
|
||||||
// outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
|
|
||||||
outer := newJob(&ActionRunJob{Name: "outer", AttemptJobID: 1, IsReusableCaller: true, CallUses: "./.gitea/workflows/lib.yml"})
|
|
||||||
inner := newJob(&ActionRunJob{Name: "inner", AttemptJobID: 2, ParentJobID: outer.ID, IsReusableCaller: true, CallUses: "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1"})
|
|
||||||
|
|
||||||
// Cancel all jobs of the attempt, ordered by id (parent before child).
|
|
||||||
jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = CancelJobs(ctx, jobs, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, j := range []*ActionRunJob{outer, inner} {
|
|
||||||
got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
|
|
||||||
assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
|
|
||||||
}
|
|
||||||
gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
|
|
||||||
assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
|
|
||||||
gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
|
|
||||||
assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
|
|
||||||
} else {
|
|
||||||
outer := newJob(&ActionRunJob{Name: "outer", IsReusableCaller: true, IsExpanded: true})
|
|
||||||
inner := newJob(&ActionRunJob{Name: "inner", ParentJobID: outer.ID, IsReusableCaller: true, IsExpanded: true})
|
|
||||||
child := newJob(&ActionRunJob{Name: "child", ParentJobID: inner.ID})
|
|
||||||
|
|
||||||
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
|
|
||||||
require.NoError(t, db.Insert(ctx, runner))
|
|
||||||
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
|
|
||||||
require.NoError(t, db.Insert(ctx, task))
|
|
||||||
child.TaskID = task.ID
|
|
||||||
_, err := UpdateRunJob(ctx, child, nil, "task_id")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, cancelled, 3)
|
|
||||||
for _, job := range []*ActionRunJob{outer, inner, child} {
|
|
||||||
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, job := range []*ActionRunJob{outer, inner, child} {
|
|
||||||
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
|
||||||
}
|
|
||||||
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("batch load and cancel run jobs", func(t *testing.T) {
|
// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
|
||||||
ctx := t.Context()
|
func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
|
run := &ActionRun{
|
||||||
run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
|
Title: "cancel-nested-caller",
|
||||||
require.NoError(t, db.Insert(ctx, legacyRun, run))
|
RepoID: 4,
|
||||||
require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID}))
|
Index: 9701,
|
||||||
oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1}
|
OwnerID: 1,
|
||||||
latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2}
|
WorkflowID: "caller.yaml",
|
||||||
require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt))
|
TriggerUserID: 1,
|
||||||
for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} {
|
Ref: "refs/heads/master",
|
||||||
require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID}))
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
EventPayload: "{}",
|
||||||
|
Status: StatusBlocked,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, run))
|
||||||
|
|
||||||
|
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusBlocked}
|
||||||
|
require.NoError(t, db.Insert(ctx, attempt))
|
||||||
|
run.LatestAttemptID = attempt.ID
|
||||||
|
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
||||||
|
|
||||||
|
newJob := func(name string, attemptJobID, parentID int64, callUses string) *ActionRunJob {
|
||||||
|
job := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RunAttemptID: attempt.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: name,
|
||||||
|
JobID: name,
|
||||||
|
Attempt: 1,
|
||||||
|
Status: StatusBlocked,
|
||||||
|
AttemptJobID: attemptJobID,
|
||||||
|
IsReusableCaller: true,
|
||||||
|
CallUses: callUses,
|
||||||
|
ParentJobID: parentID,
|
||||||
}
|
}
|
||||||
run.LatestAttemptID = latestAttempt.ID
|
require.NoError(t, db.Insert(ctx, job))
|
||||||
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
return job
|
||||||
|
}
|
||||||
|
|
||||||
runs := RunList{legacyRun, run}
|
// outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
|
||||||
jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs)
|
outer := newJob("outer", 1, 0, "./.gitea/workflows/lib.yml")
|
||||||
require.NoError(t, err)
|
inner := newJob("inner", 2, outer.ID, "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1")
|
||||||
for _, listedRun := range runs {
|
|
||||||
expected, err := GetLatestAttemptJobsByRun(ctx, listedRun)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEmpty(t, expected)
|
|
||||||
assert.Equal(t, expected, jobsByRun[listedRun.ID])
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts, err := runs.GetLatestAttempts(ctx)
|
// Cancel all jobs of the attempt, ordered by id (parent before child).
|
||||||
require.NoError(t, err)
|
jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
|
||||||
require.Len(t, attempts, 1)
|
require.NoError(t, err)
|
||||||
assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID)
|
_, err = CancelJobs(ctx, jobs, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
chunkedAttempts := []*ActionRunAttempt{oldAttempt}
|
for _, j := range []*ActionRunJob{outer, inner} {
|
||||||
for i := range db.DefaultMaxInSize {
|
got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
|
||||||
chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)})
|
assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
|
||||||
}
|
}
|
||||||
attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt))
|
gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
|
||||||
require.NoError(t, err)
|
assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
|
||||||
assert.Len(t, attemptJobs, 3)
|
gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
|
||||||
|
assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
|
||||||
cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, cancelled, 4)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSettleRunAfterCancel(t *testing.T) {
|
func TestSettleRunAfterCancel(t *testing.T) {
|
||||||
@@ -476,3 +398,72 @@ func TestForceCancelJobs(t *testing.T) {
|
|||||||
assert.Equal(t, StatusCancelled, callerAfter.Status)
|
assert.Equal(t, StatusCancelled, callerAfter.Status)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
run := &ActionRun{
|
||||||
|
Title: "caller-cancelling-child",
|
||||||
|
RepoID: 4,
|
||||||
|
Index: 9811,
|
||||||
|
OwnerID: 1,
|
||||||
|
WorkflowID: "test.yaml",
|
||||||
|
TriggerUserID: 1,
|
||||||
|
Ref: "refs/heads/master",
|
||||||
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
EventPayload: "{}",
|
||||||
|
Status: StatusRunning,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, run))
|
||||||
|
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning}
|
||||||
|
require.NoError(t, db.Insert(ctx, attempt))
|
||||||
|
run.LatestAttemptID = attempt.ID
|
||||||
|
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
|
||||||
|
|
||||||
|
newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob {
|
||||||
|
job := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RunAttemptID: attempt.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: name,
|
||||||
|
JobID: name,
|
||||||
|
Attempt: 1,
|
||||||
|
Status: StatusRunning,
|
||||||
|
ParentJobID: parentID,
|
||||||
|
IsReusableCaller: isCaller,
|
||||||
|
IsExpanded: isCaller,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, job))
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
outer := newJob("outer", 0, true)
|
||||||
|
inner := newJob("inner", outer.ID, true)
|
||||||
|
child := newJob("child", inner.ID, false)
|
||||||
|
|
||||||
|
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
|
||||||
|
require.NoError(t, db.Insert(ctx, runner))
|
||||||
|
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
|
||||||
|
require.NoError(t, db.Insert(ctx, task))
|
||||||
|
child.TaskID = task.ID
|
||||||
|
_, err := UpdateRunJob(ctx, child, nil, "task_id")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, cancelled, 3)
|
||||||
|
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||||
|
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, job := range []*ActionRunJob{outer, inner, child} {
|
||||||
|
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
|
||||||
|
}
|
||||||
|
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"maps"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
@@ -44,21 +42,6 @@ func (runs RunList) LoadTriggerUser(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLatestAttempts returns the latest attempts of the runs keyed by attempt ID
|
|
||||||
func (runs RunList) GetLatestAttempts(ctx context.Context) (map[int64]*ActionRunAttempt, error) {
|
|
||||||
attemptIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
|
||||||
return run.LatestAttemptID, run.LatestAttemptID != 0
|
|
||||||
})
|
|
||||||
attempts := make(map[int64]*ActionRunAttempt, len(attemptIDs))
|
|
||||||
if len(attemptIDs) == 0 {
|
|
||||||
return attempts, nil
|
|
||||||
}
|
|
||||||
if err := db.GetEngine(ctx).In("id", attemptIDs).Find(&attempts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return attempts, ActionRunAttemptList(slices.Collect(maps.Values(attempts))).LoadTriggerUser(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (runs RunList) LoadRepos(ctx context.Context) error {
|
func (runs RunList) LoadRepos(ctx context.Context) error {
|
||||||
repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
||||||
return run.RepoID, run.Repo == nil
|
return run.RepoID, run.Repo == nil
|
||||||
|
|||||||
@@ -200,21 +200,13 @@ func (r *ActionRunner) GenerateAndFillToken() {
|
|||||||
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
|
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
|
||||||
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
|
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
|
||||||
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
|
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
|
||||||
return len(jobRunsOn) > 0 && !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
|
return !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(&ActionRunner{})
|
db.RegisterModel(&ActionRunner{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRunnersMapByIDs(ctx context.Context, ids []int64) (map[int64]*ActionRunner, error) {
|
|
||||||
runners := make(map[int64]*ActionRunner, len(ids))
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return runners, nil
|
|
||||||
}
|
|
||||||
return runners, db.GetEngine(ctx).In("id", ids).Find(&runners)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRunnerOptions
|
// FindRunnerOptions
|
||||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
// repoID != 0 and WithAvailable == false means any runner for the given repo
|
// repoID != 0 and WithAvailable == false means any runner for the given repo
|
||||||
|
|||||||
@@ -12,32 +12,72 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldPersistRunnerTimestamps(t *testing.T) {
|
func TestShouldPersistLastOnline(t *testing.T) {
|
||||||
for _, scenario := range []struct {
|
now := time.Now()
|
||||||
name string
|
tests := []struct {
|
||||||
persist func(timeutil.TimeStamp, time.Time) bool
|
name string
|
||||||
interval time.Duration
|
last timeutil.TimeStamp
|
||||||
freshAge time.Duration
|
want bool
|
||||||
}{
|
}{
|
||||||
{"online", ShouldPersistLastOnline, RunnerHeartbeatInterval, 5 * time.Second},
|
{
|
||||||
{"active", ShouldPersistLastActive, RunnerActiveInterval, time.Second},
|
name: "fresh, skip write",
|
||||||
} {
|
last: timeutil.TimeStamp(now.Add(-5 * time.Second).Unix()),
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
want: false,
|
||||||
now := time.Now()
|
},
|
||||||
for _, tt := range []struct {
|
{
|
||||||
name string
|
name: "exactly at interval, write",
|
||||||
last timeutil.TimeStamp
|
last: timeutil.TimeStamp(now.Add(-RunnerHeartbeatInterval).Unix()),
|
||||||
want bool
|
want: true,
|
||||||
}{
|
},
|
||||||
{"fresh, skip write", timeutil.TimeStamp(now.Add(-scenario.freshAge).Unix()), false},
|
{
|
||||||
{"exactly at interval, write", timeutil.TimeStamp(now.Add(-scenario.interval).Unix()), true},
|
name: "stale, write",
|
||||||
{"stale, write", timeutil.TimeStamp(now.Add(-2 * scenario.interval).Unix()), true},
|
last: timeutil.TimeStamp(now.Add(-2 * RunnerHeartbeatInterval).Unix()),
|
||||||
{"zero (never seen), write", 0, true},
|
want: true,
|
||||||
} {
|
},
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
{
|
||||||
assert.Equal(t, tt.want, scenario.persist(tt.last, now))
|
name: "zero (never seen), write",
|
||||||
})
|
last: 0,
|
||||||
}
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, ShouldPersistLastOnline(tt.last, now))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldPersistLastActive(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
last timeutil.TimeStamp
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "fresh, skip write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-1 * time.Second).Unix()),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exactly at interval, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-RunnerActiveInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stale, write",
|
||||||
|
last: timeutil.TimeStamp(now.Add(-2 * RunnerActiveInterval).Unix()),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero (never seen), write",
|
||||||
|
last: 0,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, ShouldPersistLastActive(tt.last, now))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,5 +86,4 @@ func TestCanMatchLabelsCaseInsensitive(t *testing.T) {
|
|||||||
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
|
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
|
||||||
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
|
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
|
||||||
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
|
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
|
||||||
assert.False(t, runner.CanMatchLabels(nil))
|
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
@@ -120,12 +119,3 @@ func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRu
|
|||||||
}
|
}
|
||||||
return &runnerToken, nil
|
return &runnerToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateActiveRunnerToken returns the latest runner token, or a new one if there is none or it is no longer active
|
|
||||||
func GetOrCreateActiveRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
|
||||||
token, err := GetLatestRunnerToken(ctx, ownerID, repoID)
|
|
||||||
if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
|
|
||||||
return NewRunnerToken(ctx, ownerID, repoID)
|
|
||||||
}
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
@@ -105,7 +105,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
|
|||||||
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
|
||||||
// There is no other place we can do this because the app.ini will be changed manually
|
// There is no other place we can do this because the app.ini will be changed manually
|
||||||
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
|
||||||
return nil, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
|
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
|
||||||
}
|
}
|
||||||
// cancel running cron jobs of this repository and delete old schedules
|
// cancel running cron jobs of this repository and delete old schedules
|
||||||
jobs, err := CancelPreviousJobs(
|
jobs, err := CancelPreviousJobs(
|
||||||
@@ -116,7 +116,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
|
|||||||
webhook_module.HookEventSchedule,
|
webhook_module.HookEventSchedule,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jobs, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
|
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
|
||||||
}
|
}
|
||||||
return jobs, nil
|
return jobs, nil
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,10 @@ func (s Status) HasRun() bool {
|
|||||||
return s.In(StatusSuccess, StatusFailure)
|
return s.In(StatusSuccess, StatusFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Status) IsUnknown() bool {
|
||||||
|
return s == StatusUnknown
|
||||||
|
}
|
||||||
|
|
||||||
func (s Status) IsSuccess() bool {
|
func (s Status) IsSuccess() bool {
|
||||||
return s == StatusSuccess
|
return s == StatusSuccess
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,28 +11,41 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatusResultConversions(t *testing.T) {
|
func TestStatusAsResult(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
status Status
|
status Status
|
||||||
result runnerv1.Result
|
want runnerv1.Result
|
||||||
fromResult bool
|
|
||||||
}{
|
}{
|
||||||
{StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED, true},
|
{StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED},
|
||||||
{StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED, false},
|
{StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED},
|
||||||
{StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED, false},
|
{StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED},
|
||||||
{StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED, false},
|
{StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED},
|
||||||
{StatusSuccess, runnerv1.Result_RESULT_SUCCESS, true},
|
{StatusSuccess, runnerv1.Result_RESULT_SUCCESS},
|
||||||
{StatusFailure, runnerv1.Result_RESULT_FAILURE, true},
|
{StatusFailure, runnerv1.Result_RESULT_FAILURE},
|
||||||
{StatusCancelled, runnerv1.Result_RESULT_CANCELLED, true},
|
{StatusCancelled, runnerv1.Result_RESULT_CANCELLED},
|
||||||
{StatusCancelling, runnerv1.Result_RESULT_CANCELLED, false},
|
{StatusCancelling, runnerv1.Result_RESULT_CANCELLED},
|
||||||
{StatusSkipped, runnerv1.Result_RESULT_SKIPPED, true},
|
{StatusSkipped, runnerv1.Result_RESULT_SKIPPED},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
assert.Equal(t, tt.result, tt.status.AsResult(), "status=%s", tt.status)
|
assert.Equal(t, tt.want, tt.status.AsResult(), "status=%s", tt.status)
|
||||||
if tt.fromResult {
|
}
|
||||||
assert.Equal(t, tt.status, StatusFromResult(tt.result), "result=%s", tt.result)
|
}
|
||||||
}
|
|
||||||
|
func TestStatusFromResult(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
result runnerv1.Result
|
||||||
|
want Status
|
||||||
|
}{
|
||||||
|
{runnerv1.Result_RESULT_UNSPECIFIED, StatusUnknown},
|
||||||
|
{runnerv1.Result_RESULT_SUCCESS, StatusSuccess},
|
||||||
|
{runnerv1.Result_RESULT_FAILURE, StatusFailure},
|
||||||
|
{runnerv1.Result_RESULT_CANCELLED, StatusCancelled},
|
||||||
|
{runnerv1.Result_RESULT_SKIPPED, StatusSkipped},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
assert.Equal(t, tt.want, StatusFromResult(tt.result), "result=%s", tt.result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-14
@@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -483,11 +482,6 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
|
|||||||
for _, v := range state.Steps {
|
for _, v := range state.Steps {
|
||||||
stepStates[v.Id] = v
|
stepStates[v.Id] = v
|
||||||
}
|
}
|
||||||
reportedSteps := state.ReportedSteps
|
|
||||||
if err := validateReportedSteps(reportedSteps); err != nil {
|
|
||||||
log.Warn("Ignore the steps reported for task %d: %v", state.Id, err)
|
|
||||||
reportedSteps = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only one request can update the task because the final state needs to be calculated with all job states.
|
// Only one request can update the task because the final state needs to be calculated with all job states.
|
||||||
// Otherwise, concurrent requests with transaction will make the SQL read stale job state and result in wrong final state.
|
// Otherwise, concurrent requests with transaction will make the SQL read stale job state and result in wrong final state.
|
||||||
@@ -544,14 +538,22 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reportedSteps) > 0 {
|
|
||||||
return updateReportedSteps(ctx, task, reportedSteps, now)
|
|
||||||
}
|
|
||||||
if slices.ContainsFunc(task.Steps, (*ActionTaskStep).IsReported) {
|
|
||||||
return nil // the indexes of the steps of the workflow file no longer match the stored steps
|
|
||||||
}
|
|
||||||
for _, step := range task.Steps {
|
for _, step := range task.Steps {
|
||||||
step.applyState(stepStates[step.Index], now)
|
var result runnerv1.Result
|
||||||
|
if v, ok := stepStates[step.Index]; ok {
|
||||||
|
result = v.Result
|
||||||
|
step.LogIndex = v.LogIndex
|
||||||
|
step.LogLength = v.LogLength
|
||||||
|
if step.Started == 0 && v.StartedAt != nil {
|
||||||
|
step.Started = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result != runnerv1.Result_RESULT_UNSPECIFIED {
|
||||||
|
step.Status = StatusFromResult(result)
|
||||||
|
step.Stopped = util.IfZero(step.Stopped, now)
|
||||||
|
} else if step.Started != 0 {
|
||||||
|
step.Status = StatusRunning
|
||||||
|
}
|
||||||
if _, err := db.GetEngine(ctx).ID(step.ID).Update(step); err != nil {
|
if _, err := db.GetEngine(ctx).ID(step.ID).Update(step); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -635,7 +637,13 @@ func StopTask(ctx context.Context, taskID int64, status Status) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, step := range task.Steps {
|
for _, step := range task.Steps {
|
||||||
step.stop(status, now)
|
if !step.Status.IsDone() {
|
||||||
|
step.Status = status
|
||||||
|
if step.Started == 0 {
|
||||||
|
step.Started = now
|
||||||
|
}
|
||||||
|
step.Stopped = now
|
||||||
|
}
|
||||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
if _, err := e.ID(step.ID).Update(step); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/container"
|
"gitea.dev/modules/container"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
"gitea.dev/modules/util"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@@ -42,29 +41,6 @@ func (tasks TaskList) LoadJobs(ctx context.Context) error {
|
|||||||
return jobsList.LoadAttributes(ctx, true)
|
return jobsList.LoadAttributes(ctx, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tasks TaskList) LoadSteps(ctx context.Context) error {
|
|
||||||
taskIDs := container.FilterSlice(tasks, func(task *ActionTask) (int64, bool) {
|
|
||||||
return task.ID, task.Steps == nil
|
|
||||||
})
|
|
||||||
if len(taskIDs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var steps []*ActionTaskStep
|
|
||||||
if err := db.GetEngine(ctx).In("task_id", taskIDs).OrderBy("task_id, `index`").Find(&steps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stepsByTask := make(map[int64][]*ActionTaskStep, len(taskIDs))
|
|
||||||
for _, step := range steps {
|
|
||||||
stepsByTask[step.TaskID] = append(stepsByTask[step.TaskID], step)
|
|
||||||
}
|
|
||||||
for _, task := range tasks {
|
|
||||||
if task.Steps == nil {
|
|
||||||
task.Steps = util.SliceNilAsEmpty(stepsByTask[task.ID])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tasks TaskList) LoadAttributes(ctx context.Context) error {
|
func (tasks TaskList) LoadAttributes(ctx context.Context) error {
|
||||||
return tasks.LoadJobs(ctx)
|
return tasks.LoadJobs(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,10 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/container"
|
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
"gitea.dev/modules/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActionTaskStep represents a step of ActionTask
|
// ActionTaskStep represents a step of ActionTask
|
||||||
@@ -25,8 +21,6 @@ type ActionTaskStep struct {
|
|||||||
Status Status `xorm:"index"`
|
Status Status `xorm:"index"`
|
||||||
LogIndex int64
|
LogIndex int64
|
||||||
LogLength int64
|
LogLength int64
|
||||||
Stage int64 `xorm:"NOT NULL DEFAULT 0"` // a runnerv1.StepStage, unspecified for the steps parsed from the workflow file instead of reported by the runner
|
|
||||||
Number int64 `xorm:"NOT NULL DEFAULT 0"` // the index in the workflow file of the step a PRE, MAIN or POST stage belongs to
|
|
||||||
Started timeutil.TimeStamp
|
Started timeutil.TimeStamp
|
||||||
Stopped timeutil.TimeStamp
|
Stopped timeutil.TimeStamp
|
||||||
Created timeutil.TimeStamp `xorm:"created"`
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
@@ -37,27 +31,6 @@ func (step *ActionTaskStep) Duration() time.Duration {
|
|||||||
return calculateDuration(step.Started, step.Stopped, step.Status, step.Updated)
|
return calculateDuration(step.Started, step.Stopped, step.Status, step.Updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StepStage returns the stored stage, which can be one added to the enum after this Gitea version
|
|
||||||
func (step *ActionTaskStep) StepStage() runnerv1.StepStage {
|
|
||||||
return runnerv1.StepStage(step.Stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReported reports whether the runner reported the step instead of Gitea parsing it from the workflow file.
|
|
||||||
func (step *ActionTaskStep) IsReported() bool {
|
|
||||||
return step.StepStage() != runnerv1.StepStage_STEP_STAGE_UNSPECIFIED
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkflowStepIndex returns the index of the step in the workflow file, or -1 for a step that is not written there.
|
|
||||||
func (step *ActionTaskStep) WorkflowStepIndex() int64 {
|
|
||||||
switch step.StepStage() {
|
|
||||||
case runnerv1.StepStage_STEP_STAGE_UNSPECIFIED:
|
|
||||||
return step.Index
|
|
||||||
case runnerv1.StepStage_STEP_STAGE_MAIN:
|
|
||||||
return step.Number
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(ActionTaskStep))
|
db.RegisterModel(new(ActionTaskStep))
|
||||||
}
|
}
|
||||||
@@ -66,120 +39,3 @@ func GetTaskStepsByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskStep,
|
|||||||
var steps []*ActionTaskStep
|
var steps []*ActionTaskStep
|
||||||
return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("`index` ASC").Find(&steps)
|
return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("`index` ASC").Find(&steps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyState applies the state the runner reported for the step, nil meaning it reported none.
|
|
||||||
func (step *ActionTaskStep) applyState(state *runnerv1.StepState, now timeutil.TimeStamp) {
|
|
||||||
var result runnerv1.Result
|
|
||||||
if state != nil {
|
|
||||||
result = state.Result
|
|
||||||
step.LogIndex = state.LogIndex
|
|
||||||
step.LogLength = state.LogLength
|
|
||||||
if step.Started == 0 && state.StartedAt != nil {
|
|
||||||
step.Started = now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result != runnerv1.Result_RESULT_UNSPECIFIED {
|
|
||||||
step.Status = StatusFromResult(result)
|
|
||||||
step.Stopped = util.IfZero(step.Stopped, now)
|
|
||||||
} else if step.Started != 0 {
|
|
||||||
step.Status = StatusRunning
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop finishes a step that had not finished when its task stopped
|
|
||||||
func (step *ActionTaskStep) stop(status Status, now timeutil.TimeStamp) {
|
|
||||||
if step.Status.IsDone() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
step.Status = status
|
|
||||||
step.Started = util.IfZero(step.Started, now)
|
|
||||||
step.Stopped = now
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxReportedSteps bounds the steps a runner can make Gitea store for one task.
|
|
||||||
const maxReportedSteps = 1000
|
|
||||||
|
|
||||||
type reportedStepKey struct {
|
|
||||||
stage runnerv1.StepStage
|
|
||||||
number int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateReportedSteps(steps []*runnerv1.StepState) error {
|
|
||||||
if len(steps) > maxReportedSteps {
|
|
||||||
return fmt.Errorf("%d steps exceed the limit of %d", len(steps), maxReportedSteps)
|
|
||||||
}
|
|
||||||
seen := make(container.Set[reportedStepKey], len(steps))
|
|
||||||
for i, v := range steps {
|
|
||||||
if v.Stage <= runnerv1.StepStage_STEP_STAGE_UNSPECIFIED { // a stage added later is shown by its name
|
|
||||||
return fmt.Errorf("step %d has invalid stage %d", i, v.Stage)
|
|
||||||
}
|
|
||||||
if v.Id != int64(i) || v.Number < 0 || v.LogIndex < 0 || v.LogLength < 0 {
|
|
||||||
return fmt.Errorf("step %d has invalid id %d, number %d or log range %d+%d", i, v.Id, v.Number, v.LogIndex, v.LogLength)
|
|
||||||
}
|
|
||||||
if !seen.Add(reportedStepKey{v.Stage, v.Number}) {
|
|
||||||
return fmt.Errorf("step %d duplicates stage %s of number %d", i, v.Stage, v.Number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateReportedSteps replaces the steps of the task with the ones the runner reported.
|
|
||||||
// A step keeps its times and status when the runner reports it again at another position.
|
|
||||||
func updateReportedSteps(ctx context.Context, task *ActionTask, reported []*runnerv1.StepState, now timeutil.TimeStamp) error {
|
|
||||||
e := db.GetEngine(ctx)
|
|
||||||
sameLayout := len(task.Steps) == len(reported)
|
|
||||||
for i := 0; sameLayout && i < len(reported); i++ {
|
|
||||||
step := task.Steps[i]
|
|
||||||
sameLayout = step.IsReported() && reportedStepKey{step.StepStage(), step.Number} == reportedStepKey{reported[i].Stage, reported[i].Number}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sameLayout {
|
|
||||||
for i, step := range task.Steps {
|
|
||||||
step.Name = util.EllipsisDisplayString(reported[i].Name, 255)
|
|
||||||
step.applyState(reported[i], now)
|
|
||||||
if task.Status.IsDone() {
|
|
||||||
step.stop(task.Status, now)
|
|
||||||
}
|
|
||||||
if _, err := e.ID(step.ID).Update(step); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
known := make(map[reportedStepKey]*ActionTaskStep, len(task.Steps))
|
|
||||||
for _, step := range task.Steps {
|
|
||||||
if step.IsReported() {
|
|
||||||
known[reportedStepKey{step.StepStage(), step.Number}] = step
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps := make([]*ActionTaskStep, len(reported))
|
|
||||||
for i, v := range reported {
|
|
||||||
key := reportedStepKey{v.Stage, v.Number}
|
|
||||||
step := &ActionTaskStep{
|
|
||||||
Name: util.EllipsisDisplayString(v.Name, 255),
|
|
||||||
TaskID: task.ID,
|
|
||||||
Index: int64(i),
|
|
||||||
RepoID: task.RepoID,
|
|
||||||
Stage: int64(key.stage),
|
|
||||||
Number: key.number,
|
|
||||||
Status: StatusWaiting,
|
|
||||||
}
|
|
||||||
if old, ok := known[key]; ok {
|
|
||||||
step.Status, step.Started, step.Stopped = old.Status, old.Started, old.Stopped
|
|
||||||
}
|
|
||||||
step.applyState(v, now)
|
|
||||||
if task.Status.IsDone() {
|
|
||||||
step.stop(task.Status, now)
|
|
||||||
}
|
|
||||||
steps[i] = step
|
|
||||||
}
|
|
||||||
if _, err := e.Delete(&ActionTaskStep{TaskID: task.ID}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := e.Insert(steps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
task.Steps = steps
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+128
-206
@@ -241,106 +241,136 @@ func TestStopTaskCancellingKeepsReportTime(t *testing.T) {
|
|||||||
// its job to the waiting queue and deletes the task and its steps, so a failure
|
// its job to the waiting queue and deletes the task and its steps, so a failure
|
||||||
// while assembling the runner response cannot strand the job in running state.
|
// while assembling the runner response cannot strand the job in running state.
|
||||||
func TestReleaseTaskForRunner(t *testing.T) {
|
func TestReleaseTaskForRunner(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
name string
|
|
||||||
runTitle string
|
|
||||||
runIndex int64
|
|
||||||
jobName string
|
|
||||||
batchSize int
|
|
||||||
}{
|
|
||||||
{"release", "release-task-test-run", 9902, "release-job", 0},
|
|
||||||
{"pagination", "pagination-test-run", 9903, "target-job", 2},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
if tt.batchSize != 0 {
|
|
||||||
defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
|
|
||||||
pickTaskBatchSize = tt.batchSize
|
|
||||||
}
|
|
||||||
|
|
||||||
run := &ActionRun{
|
run := &ActionRun{
|
||||||
Title: tt.runTitle,
|
Title: "release-task-test-run",
|
||||||
RepoID: 1,
|
RepoID: 1,
|
||||||
OwnerID: 2,
|
OwnerID: 2,
|
||||||
WorkflowID: "test.yaml",
|
WorkflowID: "test.yaml",
|
||||||
Index: tt.runIndex,
|
Index: 9902,
|
||||||
TriggerUserID: 2,
|
TriggerUserID: 2,
|
||||||
Ref: "refs/heads/main",
|
Ref: "refs/heads/main",
|
||||||
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
Event: "push",
|
Event: "push",
|
||||||
TriggerEvent: "push",
|
TriggerEvent: "push",
|
||||||
Status: StatusWaiting,
|
Status: StatusWaiting,
|
||||||
}
|
|
||||||
require.NoError(t, db.Insert(t.Context(), run))
|
|
||||||
|
|
||||||
if tt.batchSize != 0 {
|
|
||||||
// Five waiting jobs the runner cannot run, then one it can.
|
|
||||||
// With a page size of 2 the matching job only appears on the third page.
|
|
||||||
for i := range 5 {
|
|
||||||
mismatch := &ActionRunJob{
|
|
||||||
RunID: run.ID,
|
|
||||||
RepoID: run.RepoID,
|
|
||||||
OwnerID: run.OwnerID,
|
|
||||||
CommitSHA: run.CommitSHA,
|
|
||||||
Name: "mismatch-" + string(rune('a'+i)),
|
|
||||||
Attempt: 1,
|
|
||||||
JobID: "mismatch-" + string(rune('a'+i)),
|
|
||||||
Status: StatusWaiting,
|
|
||||||
RunsOn: []string{"windows-latest"},
|
|
||||||
}
|
|
||||||
require.NoError(t, db.Insert(t.Context(), mismatch))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
job := &ActionRunJob{
|
|
||||||
RunID: run.ID,
|
|
||||||
RepoID: run.RepoID,
|
|
||||||
OwnerID: run.OwnerID,
|
|
||||||
CommitSHA: run.CommitSHA,
|
|
||||||
Name: tt.jobName,
|
|
||||||
Attempt: 1,
|
|
||||||
JobID: tt.jobName,
|
|
||||||
Status: StatusWaiting,
|
|
||||||
RunsOn: []string{"ubuntu-latest"},
|
|
||||||
WorkflowPayload: []byte("on: push\njobs:\n " + tt.jobName + ":\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
|
||||||
}
|
|
||||||
require.NoError(t, db.Insert(t.Context(), job))
|
|
||||||
|
|
||||||
runner := &ActionRunner{
|
|
||||||
UUID: tt.name + "-runner-uuid",
|
|
||||||
Name: tt.name + "-runner",
|
|
||||||
AgentLabels: []string{"ubuntu-latest"},
|
|
||||||
}
|
|
||||||
runner.GenerateAndFillToken()
|
|
||||||
require.NoError(t, db.Insert(t.Context(), runner))
|
|
||||||
|
|
||||||
task, ok, err := CreateTaskForRunner(t.Context(), runner)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.NotNil(t, task)
|
|
||||||
|
|
||||||
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
|
|
||||||
if tt.batchSize != 0 {
|
|
||||||
assert.Equal(t, StatusRunning, claimed.Status)
|
|
||||||
assert.Equal(t, task.ID, claimed.TaskID)
|
|
||||||
} else {
|
|
||||||
require.Equal(t, StatusRunning, claimed.Status)
|
|
||||||
require.Equal(t, task.ID, claimed.TaskID)
|
|
||||||
|
|
||||||
require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
|
|
||||||
|
|
||||||
// Job is back in the waiting queue with no task assigned.
|
|
||||||
released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
|
|
||||||
assert.Equal(t, StatusWaiting, released.Status)
|
|
||||||
assert.Zero(t, released.TaskID)
|
|
||||||
assert.Zero(t, released.Started)
|
|
||||||
|
|
||||||
// The task and its steps are gone.
|
|
||||||
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
|
|
||||||
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), run))
|
||||||
|
|
||||||
|
job := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: "release-job",
|
||||||
|
Attempt: 1,
|
||||||
|
JobID: "release-job",
|
||||||
|
Status: StatusWaiting,
|
||||||
|
RunsOn: []string{"ubuntu-latest"},
|
||||||
|
WorkflowPayload: []byte("on: push\njobs:\n release-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), job))
|
||||||
|
|
||||||
|
runner := &ActionRunner{
|
||||||
|
UUID: "release-runner-uuid",
|
||||||
|
Name: "release-runner",
|
||||||
|
AgentLabels: []string{"ubuntu-latest"},
|
||||||
|
}
|
||||||
|
runner.GenerateAndFillToken()
|
||||||
|
require.NoError(t, db.Insert(t.Context(), runner))
|
||||||
|
|
||||||
|
task, ok, err := CreateTaskForRunner(t.Context(), runner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotNil(t, task)
|
||||||
|
|
||||||
|
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
|
||||||
|
require.Equal(t, StatusRunning, claimed.Status)
|
||||||
|
require.Equal(t, task.ID, claimed.TaskID)
|
||||||
|
|
||||||
|
require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
|
||||||
|
|
||||||
|
// Job is back in the waiting queue with no task assigned.
|
||||||
|
released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
|
||||||
|
assert.Equal(t, StatusWaiting, released.Status)
|
||||||
|
assert.Zero(t, released.TaskID)
|
||||||
|
assert.Zero(t, released.Started)
|
||||||
|
|
||||||
|
// The task and its steps are gone.
|
||||||
|
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
|
||||||
|
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCreateTaskForRunnerPagination verifies that a job sitting beyond the first page is still claimed
|
||||||
|
func TestCreateTaskForRunnerPagination(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
|
||||||
|
pickTaskBatchSize = 2
|
||||||
|
|
||||||
|
run := &ActionRun{
|
||||||
|
Title: "pagination-test-run",
|
||||||
|
RepoID: 1,
|
||||||
|
OwnerID: 2,
|
||||||
|
WorkflowID: "test.yaml",
|
||||||
|
Index: 9903,
|
||||||
|
TriggerUserID: 2,
|
||||||
|
Ref: "refs/heads/main",
|
||||||
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
|
||||||
|
Event: "push",
|
||||||
|
TriggerEvent: "push",
|
||||||
|
Status: StatusWaiting,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), run))
|
||||||
|
|
||||||
|
// Five waiting jobs the runner cannot run, then one it can.
|
||||||
|
// With a page size of 2 the matching job only appears on the third page.
|
||||||
|
for i := range 5 {
|
||||||
|
mismatch := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: "mismatch-" + string(rune('a'+i)),
|
||||||
|
Attempt: 1,
|
||||||
|
JobID: "mismatch-" + string(rune('a'+i)),
|
||||||
|
Status: StatusWaiting,
|
||||||
|
RunsOn: []string{"windows-latest"},
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), mismatch))
|
||||||
|
}
|
||||||
|
|
||||||
|
target := &ActionRunJob{
|
||||||
|
RunID: run.ID,
|
||||||
|
RepoID: run.RepoID,
|
||||||
|
OwnerID: run.OwnerID,
|
||||||
|
CommitSHA: run.CommitSHA,
|
||||||
|
Name: "target-job",
|
||||||
|
Attempt: 1,
|
||||||
|
JobID: "target-job",
|
||||||
|
Status: StatusWaiting,
|
||||||
|
RunsOn: []string{"ubuntu-latest"},
|
||||||
|
WorkflowPayload: []byte("on: push\njobs:\n target-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(t.Context(), target))
|
||||||
|
|
||||||
|
runner := &ActionRunner{
|
||||||
|
UUID: "pagination-runner-uuid",
|
||||||
|
Name: "pagination-runner",
|
||||||
|
AgentLabels: []string{"ubuntu-latest"},
|
||||||
|
}
|
||||||
|
runner.GenerateAndFillToken()
|
||||||
|
require.NoError(t, db.Insert(t.Context(), runner))
|
||||||
|
|
||||||
|
task, ok, err := CreateTaskForRunner(t.Context(), runner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotNil(t, task)
|
||||||
|
|
||||||
|
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: target.ID})
|
||||||
|
assert.Equal(t, StatusRunning, claimed.Status)
|
||||||
|
assert.Equal(t, task.ID, claimed.TaskID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type failFirstStepWrite struct{ fired atomic.Bool }
|
type failFirstStepWrite struct{ fired atomic.Bool }
|
||||||
@@ -377,114 +407,6 @@ func TestUpdateTaskByStateIsAtomic(t *testing.T) {
|
|||||||
assert.GreaterOrEqual(t, unittest.AssertExistsAndLoadBean(t, &ActionTaskStep{TaskID: task.ID}).Started, before)
|
assert.GreaterOrEqual(t, unittest.AssertExistsAndLoadBean(t, &ActionTaskStep{TaskID: task.ID}).Started, before)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateTaskByStateReportedSteps(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
task, _ := newRunningTaskForCancelling(t, "reported-steps-job", true)
|
|
||||||
require.NoError(t, db.Insert(t.Context(), &ActionTaskStep{Name: "Run echo", TaskID: task.ID, RepoID: task.RepoID, Status: StatusWaiting}))
|
|
||||||
|
|
||||||
report := func(t *testing.T, steps []*runnerv1.StepState, reported ...*runnerv1.StepState) []*ActionTaskStep {
|
|
||||||
t.Helper()
|
|
||||||
_, err := UpdateTaskByState(t.Context(), task.RunnerID, &runnerv1.TaskState{Id: task.ID, Steps: steps, ReportedSteps: reported})
|
|
||||||
require.NoError(t, err)
|
|
||||||
got, err := GetTaskStepsByTaskID(t.Context(), task.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return got
|
|
||||||
}
|
|
||||||
started := timestamppb.Now()
|
|
||||||
setup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started}
|
|
||||||
main := &runnerv1.StepState{Id: 1, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN}
|
|
||||||
legacy := []*runnerv1.StepState{{Id: 0, StartedAt: started, LogLength: 3}}
|
|
||||||
|
|
||||||
t.Run("invalid reported steps fall back to the workflow steps", func(t *testing.T) {
|
|
||||||
got := report(t, legacy, setup, &runnerv1.StepState{Id: 1, Stage: runnerv1.StepStage_STEP_STAGE_SETUP})
|
|
||||||
require.Len(t, got, 1)
|
|
||||||
assert.False(t, got[0].IsReported())
|
|
||||||
assert.Equal(t, StatusRunning, got[0].Status)
|
|
||||||
assert.EqualValues(t, 3, got[0].LogLength)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("reported steps replace the workflow steps", func(t *testing.T) {
|
|
||||||
got := report(t, legacy, setup, main)
|
|
||||||
require.Len(t, got, 2)
|
|
||||||
assert.Equal(t, runnerv1.StepStage_STEP_STAGE_SETUP, got[0].StepStage())
|
|
||||||
assert.EqualValues(t, -1, got[0].WorkflowStepIndex())
|
|
||||||
assert.Equal(t, StatusRunning, got[0].Status)
|
|
||||||
assert.NotZero(t, got[0].Started)
|
|
||||||
assert.Equal(t, "Run echo", got[1].Name)
|
|
||||||
assert.Equal(t, StatusWaiting, got[1].Status)
|
|
||||||
assert.EqualValues(t, 0, got[1].WorkflowStepIndex())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("an inserted step keeps the state of the others", func(t *testing.T) {
|
|
||||||
before, err := GetTaskStepsByTaskID(t.Context(), task.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
doneSetup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started, Result: runnerv1.Result_RESULT_SUCCESS, LogLength: 4}
|
|
||||||
pre := &runnerv1.StepState{Id: 1, Name: "Pre Run checkout", Stage: runnerv1.StepStage_STEP_STAGE_PRE, StartedAt: started, LogIndex: 4, LogLength: 2}
|
|
||||||
movedMain := &runnerv1.StepState{Id: 2, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN}
|
|
||||||
got := report(t, legacy, doneSetup, pre, movedMain)
|
|
||||||
require.Len(t, got, 3)
|
|
||||||
assert.Equal(t, StatusSuccess, got[0].Status)
|
|
||||||
assert.Equal(t, before[0].Started, got[0].Started)
|
|
||||||
assert.Equal(t, runnerv1.StepStage_STEP_STAGE_PRE, got[1].StepStage())
|
|
||||||
assert.Equal(t, StatusRunning, got[1].Status)
|
|
||||||
assert.EqualValues(t, 4, got[1].LogIndex)
|
|
||||||
assert.EqualValues(t, 2, got[2].Index)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("workflow steps no longer apply once reported", func(t *testing.T) {
|
|
||||||
before, err := GetTaskStepsByTaskID(t.Context(), task.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
got := report(t, []*runnerv1.StepState{{Id: 0, Result: runnerv1.Result_RESULT_FAILURE}})
|
|
||||||
assert.Equal(t, before, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unfinished steps stop with the task", func(t *testing.T) {
|
|
||||||
doneSetup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started, Result: runnerv1.Result_RESULT_SUCCESS}
|
|
||||||
runningMain := &runnerv1.StepState{Id: 1, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN, StartedAt: started}
|
|
||||||
cleanup := &runnerv1.StepState{Id: 2, Name: "Complete job", Stage: runnerv1.StepStage_STEP_STAGE_CLEANUP}
|
|
||||||
_, err := UpdateTaskByState(t.Context(), task.RunnerID, &runnerv1.TaskState{
|
|
||||||
Id: task.ID, Result: runnerv1.Result_RESULT_FAILURE,
|
|
||||||
ReportedSteps: []*runnerv1.StepState{doneSetup, runningMain, cleanup},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
got, err := GetTaskStepsByTaskID(t.Context(), task.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, got, 3)
|
|
||||||
assert.Equal(t, StatusSuccess, got[0].Status)
|
|
||||||
for _, step := range got[1:] {
|
|
||||||
assert.Equal(t, StatusFailure, step.Status, step.Name)
|
|
||||||
assert.NotZero(t, step.Started, step.Name)
|
|
||||||
assert.NotZero(t, step.Stopped, step.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateReportedSteps(t *testing.T) {
|
|
||||||
step := func(id int64, stage runnerv1.StepStage, number int64) *runnerv1.StepState {
|
|
||||||
return &runnerv1.StepState{Id: id, Stage: stage, Number: number}
|
|
||||||
}
|
|
||||||
assert.NoError(t, validateReportedSteps([]*runnerv1.StepState{
|
|
||||||
step(0, runnerv1.StepStage_STEP_STAGE_SETUP, 0),
|
|
||||||
step(1, runnerv1.StepStage_STEP_STAGE_PRE, 0),
|
|
||||||
step(2, runnerv1.StepStage_STEP_STAGE_MAIN, 0),
|
|
||||||
step(3, runnerv1.StepStage_STEP_STAGE_MAIN, 1),
|
|
||||||
step(4, runnerv1.StepStage_STEP_STAGE_CLEANUP, 0),
|
|
||||||
step(5, runnerv1.StepStage(100), 0), // a stage added later, with several steps told apart by number
|
|
||||||
step(6, runnerv1.StepStage(100), 1),
|
|
||||||
}))
|
|
||||||
for name, steps := range map[string][]*runnerv1.StepState{
|
|
||||||
"unspecified stage": {step(0, runnerv1.StepStage_STEP_STAGE_UNSPECIFIED, 0)},
|
|
||||||
"negative stage": {step(0, runnerv1.StepStage(-1), 0)},
|
|
||||||
"id not position": {step(1, runnerv1.StepStage_STEP_STAGE_SETUP, 0)},
|
|
||||||
"negative number": {step(0, runnerv1.StepStage_STEP_STAGE_MAIN, -1)},
|
|
||||||
"duplicate main": {step(0, runnerv1.StepStage_STEP_STAGE_MAIN, 0), step(1, runnerv1.StepStage_STEP_STAGE_MAIN, 0)},
|
|
||||||
"duplicate setup": {step(0, runnerv1.StepStage_STEP_STAGE_SETUP, 0), step(1, runnerv1.StepStage_STEP_STAGE_SETUP, 0)},
|
|
||||||
"too many": make([]*runnerv1.StepState, maxReportedSteps+1),
|
|
||||||
} {
|
|
||||||
assert.Error(t, validateReportedSteps(steps), name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRunningTaskForCancelling inserts a running run/job/task assigned to a fresh runner,
|
// newRunningTaskForCancelling inserts a running run/job/task assigned to a fresh runner,
|
||||||
// which is the state every cancellation test starts from.
|
// which is the state every cancellation test starts from.
|
||||||
func newRunningTaskForCancelling(t *testing.T, name string, hasCancellingSupport bool) (*ActionTask, *ActionRunJob) {
|
func newRunningTaskForCancelling(t *testing.T, name string, hasCancellingSupport bool) (*ActionTask, *ActionRunJob) {
|
||||||
|
|||||||
@@ -89,3 +89,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status, fallb
|
|||||||
}
|
}
|
||||||
return max(timeSince(s), 0).Truncate(time.Second)
|
return max(timeSince(s), 0).Truncate(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
|
||||||
|
func (s *ActionSchedule) ToActionRun() *ActionRun {
|
||||||
|
return &ActionRun{
|
||||||
|
Title: s.Title,
|
||||||
|
RepoID: s.RepoID,
|
||||||
|
Repo: s.Repo,
|
||||||
|
OwnerID: s.OwnerID,
|
||||||
|
WorkflowID: s.WorkflowID,
|
||||||
|
TriggerUserID: s.TriggerUserID,
|
||||||
|
TriggerUser: s.TriggerUser,
|
||||||
|
Ref: s.Ref,
|
||||||
|
CommitSHA: s.CommitSHA,
|
||||||
|
Event: s.Event,
|
||||||
|
EventPayload: s.EventPayload,
|
||||||
|
Created: s.Created,
|
||||||
|
Updated: s.Updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,14 +15,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLogIndexes_ToDB(t *testing.T) {
|
func TestLogIndexes_ToDB(t *testing.T) {
|
||||||
inputIndexes := LogIndexes{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64}
|
tests := []struct {
|
||||||
got, err := inputIndexes.ToDB()
|
indexes LogIndexes
|
||||||
require.NoError(t, err)
|
}{
|
||||||
|
{
|
||||||
|
indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
got, err := tt.indexes.ToDB()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
indexes := LogIndexes{}
|
indexes := LogIndexes{}
|
||||||
require.NoError(t, indexes.FromDB(got))
|
require.NoError(t, indexes.FromDB(got))
|
||||||
|
|
||||||
assert.Equal(t, inputIndexes, indexes)
|
assert.Equal(t, tt.indexes, indexes)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_calculateDuration(t *testing.T) {
|
func Test_calculateDuration(t *testing.T) {
|
||||||
|
|||||||
+18
-13
@@ -48,7 +48,13 @@ func init() {
|
|||||||
db.RegisterModel(new(ActionVariable))
|
db.RegisterModel(new(ActionVariable))
|
||||||
}
|
}
|
||||||
|
|
||||||
func InsertVariable(ctx context.Context, scope BelongingScope, name, data, description string) (*ActionVariable, error) {
|
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, description string) (*ActionVariable, error) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
// It's trying to create a variable that belongs to a repository, but OwnerID has been set accidentally.
|
||||||
|
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||||
|
ownerID = 0
|
||||||
|
}
|
||||||
|
|
||||||
if utf8.RuneCountInString(data) > VariableDataMaxLength {
|
if utf8.RuneCountInString(data) > VariableDataMaxLength {
|
||||||
return nil, util.NewInvalidArgumentErrorf("data too long")
|
return nil, util.NewInvalidArgumentErrorf("data too long")
|
||||||
}
|
}
|
||||||
@@ -56,8 +62,8 @@ func InsertVariable(ctx context.Context, scope BelongingScope, name, data, descr
|
|||||||
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
|
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
|
||||||
|
|
||||||
variable := &ActionVariable{
|
variable := &ActionVariable{
|
||||||
OwnerID: scope.internalOwnerID,
|
OwnerID: ownerID,
|
||||||
RepoID: scope.internalRepoID,
|
RepoID: repoID,
|
||||||
Name: strings.ToUpper(name),
|
Name: strings.ToUpper(name),
|
||||||
Data: data,
|
Data: data,
|
||||||
Description: description,
|
Description: description,
|
||||||
@@ -67,10 +73,10 @@ func InsertVariable(ctx context.Context, scope BelongingScope, name, data, descr
|
|||||||
|
|
||||||
type FindVariablesOpts struct {
|
type FindVariablesOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
IDs []int64
|
IDs []int64
|
||||||
Name string
|
RepoID int64
|
||||||
|
OwnerID int64 // it will be ignored if RepoID is set
|
||||||
BelongingScope BelongingScope
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindVariablesOpts) ToOrders() string {
|
func (opts FindVariablesOpts) ToOrders() string {
|
||||||
@@ -90,13 +96,12 @@ func (opts FindVariablesOpts) ToConds() builder.Cond {
|
|||||||
|
|
||||||
// Since we now support instance-level variables,
|
// Since we now support instance-level variables,
|
||||||
// there is no need to check for null values for `owner_id` and `repo_id`
|
// there is no need to check for null values for `owner_id` and `repo_id`
|
||||||
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
cond = cond.And(builder.Eq{"repo_id": repoID})
|
if opts.RepoID != 0 { // if RepoID is set
|
||||||
if repoID != 0 { // if RepoID is set
|
|
||||||
// ignore OwnerID and treat it as 0
|
// ignore OwnerID and treat it as 0
|
||||||
cond = cond.And(builder.Eq{"owner_id": 0})
|
cond = cond.And(builder.Eq{"owner_id": 0})
|
||||||
} else {
|
} else {
|
||||||
cond = cond.And(builder.Eq{"owner_id": ownerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Name != "" {
|
if opts.Name != "" {
|
||||||
@@ -147,14 +152,14 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Org / User level
|
// Org / User level
|
||||||
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeOwner(run.Repo.OwnerID)})
|
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo level
|
// Repo level
|
||||||
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeRepo(run.RepoID)})
|
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -436,6 +436,12 @@ type GetFeedsOptions struct {
|
|||||||
DontCount bool // do counting in GetFeeds
|
DontCount bool // do counting in GetFeeds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *GetFeedsOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.IncludePrivate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ActivityReadable return whether doer can read activities of user
|
// ActivityReadable return whether doer can read activities of user
|
||||||
func ActivityReadable(user, doer *user_model.User) bool {
|
func ActivityReadable(user, doer *user_model.User) bool {
|
||||||
return !user.KeepActivityPrivate ||
|
return !user.KeepActivityPrivate ||
|
||||||
@@ -472,13 +478,13 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check activity visibility for actor ( similar to activityReadable() )
|
// check activity visibility for actor ( similar to activityReadable() )
|
||||||
if !user_model.DoerCanViewPrivate(opts.Actor) {
|
if opts.Actor == nil {
|
||||||
cond = cond.And(builder.In("act_user_id",
|
cond = cond.And(builder.In("act_user_id",
|
||||||
builder.Select("`user`.id").Where(
|
builder.Select("`user`.id").Where(
|
||||||
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
|
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
|
||||||
).From("`user`"),
|
).From("`user`"),
|
||||||
))
|
))
|
||||||
} else if !user_model.DoerIsSiteAdminFullAccess(opts.Actor) {
|
} else if !opts.Actor.IsAdmin {
|
||||||
uidCond := builder.Select("`user`.id").From("`user`").Where(
|
uidCond := builder.Select("`user`.id").From("`user`").Where(
|
||||||
builder.Eq{"keep_activity_private": false}.
|
builder.Eq{"keep_activity_private": false}.
|
||||||
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
|
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
|
||||||
@@ -503,7 +509,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check readable repositories by doer/actor
|
// check readable repositories by doer/actor
|
||||||
if !user_model.DoerIsSiteAdminFullAccess(opts.Actor) {
|
if opts.Actor == nil || !opts.Actor.IsAdmin {
|
||||||
cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor)))
|
cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +539,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.IncludePrivate || user_model.IsPublicOnlyDoer(opts.Actor) {
|
if !opts.IncludePrivate {
|
||||||
cond = cond.And(builder.Eq{"`action`.is_private": false})
|
cond = cond.And(builder.Eq{"`action`.is_private": false})
|
||||||
}
|
}
|
||||||
if !opts.IncludeDeleted {
|
if !opts.IncludeDeleted {
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||||||
var err error
|
var err error
|
||||||
var cond builder.Cond
|
var cond builder.Cond
|
||||||
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
|
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
|
||||||
if opts.RequestedUser != nil && user_model.DoerCanViewPrivate(opts.Actor) && (user_model.DoerIsSiteAdminFullAccess(opts.Actor) || opts.Actor.ID == opts.RequestedUser.ID) {
|
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
|
||||||
cond = builder.Eq{
|
cond = builder.Eq{
|
||||||
"user_id": opts.RequestedUser.ID,
|
"user_id": opts.RequestedUser.ID,
|
||||||
}.And(
|
}.And(
|
||||||
|
|||||||
@@ -104,14 +104,19 @@ func TestConsistencyUpdateAction(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// Get rid of incorrectly set created_unix
|
// Get rid of incorrectly set created_unix
|
||||||
//
|
//
|
||||||
for _, wantCount := range []int{1, 0} {
|
count, err := activities_model.CountActionCreatedUnixString(t.Context())
|
||||||
count, err := activities_model.CountActionCreatedUnixString(t.Context())
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
assert.EqualValues(t, 1, count)
|
||||||
assert.EqualValues(t, wantCount, count)
|
count, err = activities_model.FixActionCreatedUnixString(t.Context())
|
||||||
count, err = activities_model.FixActionCreatedUnixString(t.Context())
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
assert.EqualValues(t, 1, count)
|
||||||
assert.EqualValues(t, wantCount, count)
|
|
||||||
}
|
count, err = activities_model.CountActionCreatedUnixString(t.Context())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, count)
|
||||||
|
count, err = activities_model.FixActionCreatedUnixString(t.Context())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, count)
|
||||||
|
|
||||||
//
|
//
|
||||||
// XORM must be happy now
|
// XORM must be happy now
|
||||||
|
|||||||
@@ -228,15 +228,30 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
|
|||||||
|
|
||||||
repoIDs := nl.getPendingRepoIDs()
|
repoIDs := nl.getPendingRepoIDs()
|
||||||
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||||
err := db.QueryAndProcessByChunk(repoIDs, func(chunk []int64) (out []*repo_model.Repository, err error) {
|
left := len(repoIDs)
|
||||||
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
|
for left > 0 {
|
||||||
return out, err
|
limit := min(left, db.DefaultMaxInSize)
|
||||||
}, func(repo *repo_model.Repository) error {
|
rows, err := db.GetEngine(ctx).
|
||||||
repos[repo.ID] = repo
|
In("id", repoIDs[:limit]).
|
||||||
return nil
|
Rows(new(repo_model.Repository))
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
return nil, nil, err
|
||||||
return nil, nil, err
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var repo repo_model.Repository
|
||||||
|
err = rows.Scan(&repo)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repos[repo.ID] = &repo
|
||||||
|
}
|
||||||
|
_ = rows.Close()
|
||||||
|
|
||||||
|
left -= limit
|
||||||
|
repoIDs = repoIDs[limit:]
|
||||||
}
|
}
|
||||||
|
|
||||||
failed := []int{}
|
failed := []int{}
|
||||||
@@ -284,15 +299,30 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
|
|||||||
|
|
||||||
issueIDs := nl.getPendingIssueIDs()
|
issueIDs := nl.getPendingIssueIDs()
|
||||||
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
|
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
|
||||||
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*issues_model.Issue, err error) {
|
left := len(issueIDs)
|
||||||
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
|
for left > 0 {
|
||||||
return out, err
|
limit := min(left, db.DefaultMaxInSize)
|
||||||
}, func(issue *issues_model.Issue) error {
|
rows, err := db.GetEngine(ctx).
|
||||||
issues[issue.ID] = issue
|
In("id", issueIDs[:limit]).
|
||||||
return nil
|
Rows(new(issues_model.Issue))
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var issue issues_model.Issue
|
||||||
|
err = rows.Scan(&issue)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issues[issue.ID] = &issue
|
||||||
|
}
|
||||||
|
_ = rows.Close()
|
||||||
|
|
||||||
|
left -= limit
|
||||||
|
issueIDs = issueIDs[limit:]
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
@@ -361,15 +391,30 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
|
|||||||
|
|
||||||
userIDs := nl.getUserIDs()
|
userIDs := nl.getUserIDs()
|
||||||
users := make(map[int64]*user_model.User, len(userIDs))
|
users := make(map[int64]*user_model.User, len(userIDs))
|
||||||
err := db.QueryAndProcessByChunk(userIDs, func(chunk []int64) (out []*user_model.User, err error) {
|
left := len(userIDs)
|
||||||
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
|
for left > 0 {
|
||||||
return out, err
|
limit := min(left, db.DefaultMaxInSize)
|
||||||
}, func(user *user_model.User) error {
|
rows, err := db.GetEngine(ctx).
|
||||||
users[user.ID] = user
|
In("id", userIDs[:limit]).
|
||||||
return nil
|
Rows(new(user_model.User))
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var user user_model.User
|
||||||
|
err = rows.Scan(&user)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
users[user.ID] = &user
|
||||||
|
}
|
||||||
|
_ = rows.Close()
|
||||||
|
|
||||||
|
left -= limit
|
||||||
|
userIDs = userIDs[limit:]
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
@@ -394,15 +439,30 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
|||||||
|
|
||||||
commentIDs := nl.getPendingCommentIDs()
|
commentIDs := nl.getPendingCommentIDs()
|
||||||
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
|
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||||
err := db.QueryAndProcessByChunk(commentIDs, func(chunk []int64) (out []*issues_model.Comment, err error) {
|
left := len(commentIDs)
|
||||||
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
|
for left > 0 {
|
||||||
return out, err
|
limit := min(left, db.DefaultMaxInSize)
|
||||||
}, func(comment *issues_model.Comment) error {
|
rows, err := db.GetEngine(ctx).
|
||||||
comments[comment.ID] = comment
|
In("id", commentIDs[:limit]).
|
||||||
return nil
|
Rows(new(issues_model.Comment))
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var comment issues_model.Comment
|
||||||
|
err = rows.Scan(&comment)
|
||||||
|
if err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
comments[comment.ID] = &comment
|
||||||
|
}
|
||||||
|
_ = rows.Close()
|
||||||
|
|
||||||
|
left -= limit
|
||||||
|
commentIDs = commentIDs[limit:]
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateOrUpdateIssueNotifications(t *testing.T) {
|
func TestCreateOrUpdateIssueNotifications(t *testing.T) {
|
||||||
@@ -110,71 +109,6 @@ func TestNotificationsForUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotificationListLoadAttributes(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
first := &activities_model.Notification{RepoID: 1, IssueID: 1, UserID: 1, CommentID: 2}
|
|
||||||
last := &activities_model.Notification{RepoID: 2, IssueID: 4, UserID: 2, CommentID: 8}
|
|
||||||
loaded := &activities_model.Notification{
|
|
||||||
RepoID: 1, IssueID: 1, UserID: 1, CommentID: 2,
|
|
||||||
Repository: &repo_model.Repository{ID: 1},
|
|
||||||
Issue: &issues_model.Issue{ID: 1},
|
|
||||||
User: &user_model.User{ID: 1},
|
|
||||||
Comment: &issues_model.Comment{ID: 2},
|
|
||||||
}
|
|
||||||
saved := *loaded
|
|
||||||
list := activities_model.NotificationList{first}
|
|
||||||
var wantFailures []int
|
|
||||||
for i := range db.DefaultMaxInSize {
|
|
||||||
id := unittest.NonexistentID - int64(i)
|
|
||||||
wantFailures = append(wantFailures, len(list))
|
|
||||||
list = append(list, &activities_model.Notification{RepoID: id, IssueID: id, UserID: id, CommentID: id})
|
|
||||||
}
|
|
||||||
withoutIssue := &activities_model.Notification{RepoID: 1}
|
|
||||||
list = append(list, last, loaded, withoutIssue)
|
|
||||||
|
|
||||||
repos, failures, err := list.LoadRepos(t.Context())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, wantFailures, failures)
|
|
||||||
require.Len(t, repos, 2)
|
|
||||||
assert.Equal(t, int64(1), repos[0].ID)
|
|
||||||
assert.Equal(t, int64(2), repos[1].ID)
|
|
||||||
failures, err = list.LoadIssues(t.Context())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, wantFailures, failures)
|
|
||||||
failures, err = list.LoadUsers(t.Context())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, failures)
|
|
||||||
failures, err = list.LoadComments(t.Context())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, failures)
|
|
||||||
|
|
||||||
for _, notification := range []*activities_model.Notification{first, last} {
|
|
||||||
require.NotNil(t, notification.Repository)
|
|
||||||
assert.Equal(t, notification.RepoID, notification.Repository.ID)
|
|
||||||
require.NotNil(t, notification.Issue)
|
|
||||||
assert.Equal(t, notification.IssueID, notification.Issue.ID)
|
|
||||||
assert.Same(t, notification.Repository, notification.Issue.Repo)
|
|
||||||
require.NotNil(t, notification.User)
|
|
||||||
assert.Equal(t, notification.UserID, notification.User.ID)
|
|
||||||
require.NotNil(t, notification.Comment)
|
|
||||||
assert.Equal(t, notification.CommentID, notification.Comment.ID)
|
|
||||||
assert.Same(t, notification.Issue, notification.Comment.Issue)
|
|
||||||
}
|
|
||||||
assert.Same(t, saved.Repository, loaded.Repository)
|
|
||||||
assert.Same(t, saved.Issue, loaded.Issue)
|
|
||||||
assert.Same(t, saved.User, loaded.User)
|
|
||||||
assert.Same(t, saved.Comment, loaded.Comment)
|
|
||||||
for _, notification := range list[1 : db.DefaultMaxInSize+1] {
|
|
||||||
assert.Nil(t, notification.Repository)
|
|
||||||
assert.Nil(t, notification.Issue)
|
|
||||||
assert.Nil(t, notification.User)
|
|
||||||
assert.Nil(t, notification.Comment)
|
|
||||||
}
|
|
||||||
assert.Nil(t, withoutIssue.Issue)
|
|
||||||
assert.Nil(t, withoutIssue.User)
|
|
||||||
assert.Nil(t, withoutIssue.Comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotification_GetRepo(t *testing.T) {
|
func TestNotification_GetRepo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
|
notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
|
||||||
@@ -196,17 +130,23 @@ func TestNotification_GetIssue(t *testing.T) {
|
|||||||
func TestGetNotificationCount(t *testing.T) {
|
func TestGetNotificationCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
for count, status := range []activities_model.NotificationStatus{
|
cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
|
||||||
activities_model.NotificationStatusRead,
|
UserID: user.ID,
|
||||||
activities_model.NotificationStatusUnread,
|
Status: []activities_model.NotificationStatus{
|
||||||
} {
|
activities_model.NotificationStatusRead,
|
||||||
cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
|
},
|
||||||
UserID: user.ID,
|
})
|
||||||
Status: []activities_model.NotificationStatus{status},
|
assert.NoError(t, err)
|
||||||
})
|
assert.EqualValues(t, 0, cnt)
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, count, cnt)
|
cnt, err = db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
|
||||||
}
|
UserID: user.ID,
|
||||||
|
Status: []activities_model.NotificationStatus{
|
||||||
|
activities_model.NotificationStatusUnread,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetNotificationStatus(t *testing.T) {
|
func TestSetNotificationStatus(t *testing.T) {
|
||||||
|
|||||||
@@ -83,17 +83,23 @@ func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom
|
|||||||
|
|
||||||
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
|
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
|
||||||
func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
|
func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
|
||||||
authors, err := git.GetCodeActivityAuthors(ctx, repo, timeFrom)
|
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("OpenRepository: %w", err)
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
code, err := gitRepo.GetCodeActivityStats(ctx, timeFrom, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("FillFromGit: %w", err)
|
return nil, fmt.Errorf("FillFromGit: %w", err)
|
||||||
}
|
}
|
||||||
if authors == nil {
|
if code.Authors == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
users := make(map[int64]*ActivityAuthorData)
|
users := make(map[int64]*ActivityAuthorData)
|
||||||
var unknownUserID int64
|
var unknownUserID int64
|
||||||
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
|
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
|
||||||
for _, v := range authors {
|
for _, v := range code.Authors {
|
||||||
if len(v.Email) == 0 {
|
if len(v.Email) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ type ErrGPGKeyIDAlreadyUsed struct {
|
|||||||
KeyID string
|
KeyID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrGPGKeyIDAlreadyUsed.
|
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrKeyNameAlreadyUsed.
|
||||||
func IsErrGPGKeyIDAlreadyUsed(err error) bool {
|
func IsErrGPGKeyIDAlreadyUsed(err error) bool {
|
||||||
_, ok := err.(ErrGPGKeyIDAlreadyUsed)
|
_, ok := err.(ErrGPGKeyIDAlreadyUsed)
|
||||||
return ok
|
return ok
|
||||||
|
|||||||
@@ -18,6 +18,43 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestCheckArmoredGPGKeyString(t *testing.T) {
|
||||||
|
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
|
||||||
|
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
|
||||||
|
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
|
||||||
|
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
|
||||||
|
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
|
||||||
|
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
|
||||||
|
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
|
||||||
|
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
|
||||||
|
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
|
||||||
|
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
|
||||||
|
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
|
||||||
|
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
|
||||||
|
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
|
||||||
|
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
|
||||||
|
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
|
||||||
|
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
|
||||||
|
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
|
||||||
|
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
|
||||||
|
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
|
||||||
|
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
|
||||||
|
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
|
||||||
|
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
|
||||||
|
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
|
||||||
|
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
|
||||||
|
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
|
||||||
|
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
|
||||||
|
=i9b7
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----`
|
||||||
|
|
||||||
|
key, err := CheckArmoredGPGKeyString(testGPGArmor)
|
||||||
|
assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", key)
|
||||||
|
// TODO verify value of key
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckArmoredbrainpoolP256r1GPGKeyString(t *testing.T) {
|
func TestCheckArmoredbrainpoolP256r1GPGKeyString(t *testing.T) {
|
||||||
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
Version: GnuPG v2
|
Version: GnuPG v2
|
||||||
@@ -71,10 +108,6 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
|
|||||||
=i9b7
|
=i9b7
|
||||||
-----END PGP PUBLIC KEY BLOCK-----`
|
-----END PGP PUBLIC KEY BLOCK-----`
|
||||||
keys, err := CheckArmoredGPGKeyString(testGPGArmor)
|
keys, err := CheckArmoredGPGKeyString(testGPGArmor)
|
||||||
assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", keys)
|
|
||||||
// TODO verify value of key
|
|
||||||
|
|
||||||
keys, err = CheckArmoredGPGKeyString(testGPGArmor)
|
|
||||||
require.NotEmpty(t, keys)
|
require.NotEmpty(t, keys)
|
||||||
|
|
||||||
ekey := keys[0]
|
ekey := keys[0]
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/consts"
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
@@ -136,7 +135,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Note: chmod command does not support in Windows.
|
// Note: chmod command does not support in Windows.
|
||||||
if !consts.IsWindows {
|
if !setting.IsWindows {
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,18 +23,18 @@ import (
|
|||||||
|
|
||||||
func Test_SSHParsePublicKey(t *testing.T) {
|
func Test_SSHParsePublicKey(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
keyType string
|
skipSSHKeygen bool
|
||||||
length int
|
keyType string
|
||||||
fp string
|
length int
|
||||||
content string
|
content string
|
||||||
}{
|
}{
|
||||||
{"rsa-1024", "rsa", 1024, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
|
{"rsa-1024", false, "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
|
||||||
{"rsa-2048", "rsa", 2048, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
|
{"rsa-2048", false, "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
|
||||||
{"ecdsa-256", "ecdsa", 256, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
|
{"ecdsa-256", false, "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
|
||||||
{"ecdsa-384", "ecdsa", 384, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
|
{"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
|
||||||
{"ecdsa-sk", "ecdsa-sk", 256, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
|
{"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
|
||||||
{"ed25519-sk", "ed25519-sk", 256, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
|
{"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -44,10 +44,6 @@ func Test_SSHParsePublicKey(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tc.keyType, keyTypeN)
|
assert.Equal(t, tc.keyType, keyTypeN)
|
||||||
assert.Equal(t, tc.length, lengthN)
|
assert.Equal(t, tc.length, lengthN)
|
||||||
|
|
||||||
fpN, err := calcFingerprintNative(tc.content)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tc.fp, fpN)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -147,6 +143,32 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_calcFingerprint(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
skipSSHKeygen bool
|
||||||
|
fp string
|
||||||
|
content string
|
||||||
|
}{
|
||||||
|
{"rsa-1024", false, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
|
||||||
|
{"rsa-2048", false, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
|
||||||
|
{"ecdsa-256", false, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
|
||||||
|
{"ecdsa-384", false, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
|
||||||
|
{"ecdsa-sk", true, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
|
||||||
|
{"ed25519-sk", true, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Run("Native", func(t *testing.T) {
|
||||||
|
fpN, err := calcFingerprintNative(tc.content)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.fp, fpN)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Generated with "ssh-keygen -C test@rekor.dev -f id_rsa"
|
// Generated with "ssh-keygen -C test@rekor.dev -f id_rsa"
|
||||||
sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ var (
|
|||||||
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
|
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
|
||||||
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
|
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
|
||||||
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
|
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
|
||||||
UserWebAuthRename = define("user:webauth:rename", "Renamed WebAuthn key {previous_credential} of user {scope} to {credential}.")
|
|
||||||
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
||||||
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
||||||
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
||||||
@@ -185,10 +184,6 @@ var (
|
|||||||
ProjectUpdate = define("project:update", "Updated project {project} in {scope}.")
|
ProjectUpdate = define("project:update", "Updated project {project} in {scope}.")
|
||||||
ProjectDelete = define("project:delete", "Deleted project {project} from {scope}.")
|
ProjectDelete = define("project:delete", "Deleted project {project} from {scope}.")
|
||||||
|
|
||||||
ProjectWorkflowCreate = define("project:workflow:create", "Created workflow {workflow_id} for {event} in project {project} of {scope}.")
|
|
||||||
ProjectWorkflowUpdate = define("project:workflow:update", "Updated workflow {workflow_id} for {event} in project {project} of {scope}.")
|
|
||||||
ProjectWorkflowDelete = define("project:workflow:delete", "Deleted workflow {workflow_id} for {event} from project {project} of {scope}.")
|
|
||||||
|
|
||||||
WikiPageCreate = define("wiki:page:create", "Created wiki page {page} in repository {scope}.")
|
WikiPageCreate = define("wiki:page:create", "Created wiki page {page} in repository {scope}.")
|
||||||
WikiPageUpdate = define("wiki:page:update", "Updated wiki page {page} in repository {scope}.")
|
WikiPageUpdate = define("wiki:page:update", "Updated wiki page {page} in repository {scope}.")
|
||||||
WikiPageDelete = define("wiki:page:delete", "Deleted wiki page {page} from repository {scope}.")
|
WikiPageDelete = define("wiki:page:delete", "Deleted wiki page {page} from repository {scope}.")
|
||||||
|
|||||||
@@ -17,29 +17,35 @@ import (
|
|||||||
func TestFindEventsScopeFilters(t *testing.T) {
|
func TestFindEventsScopeFilters(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
for _, event := range []*Event{
|
events := []*Event{
|
||||||
{Action: UserCreate, ScopeType: ScopeUser, ScopeID: 5, Origin: OriginUI, TimestampUnix: timeutil.TimeStamp(1)},
|
{Action: UserCreate, ScopeType: ScopeUser, ScopeID: 5, Origin: OriginUI, TimestampUnix: timeutil.TimeStamp(1)},
|
||||||
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 5, Origin: OriginAPI, TimestampUnix: timeutil.TimeStamp(1)},
|
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 5, Origin: OriginAPI, TimestampUnix: timeutil.TimeStamp(1)},
|
||||||
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 6, Origin: OriginCLI, TimestampUnix: timeutil.TimeStamp(1)},
|
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 6, Origin: OriginCLI, TimestampUnix: timeutil.TimeStamp(1)},
|
||||||
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 7, Origin: OriginSystem, TimestampUnix: timeutil.TimeStamp(1)},
|
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 7, Origin: OriginSystem, TimestampUnix: timeutil.TimeStamp(1)},
|
||||||
} {
|
}
|
||||||
|
for _, event := range events {
|
||||||
require.NoError(t, InsertEvent(t.Context(), event))
|
require.NoError(t, InsertEvent(t.Context(), event))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range []struct {
|
byType, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository})
|
||||||
options EventSearchOptions
|
require.NoError(t, err)
|
||||||
count int
|
assert.Len(t, byType, 3)
|
||||||
}{
|
|
||||||
{EventSearchOptions{ScopeType: ScopeRepository}, 3},
|
byID, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeID: 5})
|
||||||
{EventSearchOptions{ScopeID: 5}, 2},
|
require.NoError(t, err)
|
||||||
{EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5}, 1},
|
assert.Len(t, byID, 2)
|
||||||
{EventSearchOptions{Origin: OriginAPI}, 1},
|
|
||||||
{EventSearchOptions{Origin: OriginSystem}, 1},
|
byScope, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5})
|
||||||
} {
|
require.NoError(t, err)
|
||||||
events, _, err := FindEvents(t.Context(), &test.options)
|
assert.Len(t, byScope, 1)
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, events, test.count)
|
byOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginAPI})
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, byOrigin, 1)
|
||||||
|
|
||||||
|
bySystemOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginSystem})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, bySystemOrigin, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindEventsActionPrefixFilter(t *testing.T) {
|
func TestFindEventsActionPrefixFilter(t *testing.T) {
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ type AccessTokenScope string
|
|||||||
// for all categories, write implies read
|
// for all categories, write implies read
|
||||||
const (
|
const (
|
||||||
AccessTokenScopeAll AccessTokenScope = "all"
|
AccessTokenScopeAll AccessTokenScope = "all"
|
||||||
AccessTokenScopeNone AccessTokenScope = "no-token-scope" // no token permission scopes, so no API permissions
|
AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
|
||||||
AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
|
|
||||||
|
|
||||||
AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
|
AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
|
||||||
AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
|
AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
|
||||||
@@ -148,7 +147,6 @@ var allAccessTokenScopes = []AccessTokenScope{
|
|||||||
// allAccessTokenScopeBits contains all access token scopes.
|
// allAccessTokenScopeBits contains all access token scopes.
|
||||||
var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
|
var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
|
||||||
AccessTokenScopeAll: accessTokenScopeAllBits,
|
AccessTokenScopeAll: accessTokenScopeAllBits,
|
||||||
AccessTokenScopeNone: 0,
|
|
||||||
AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
|
AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
|
||||||
AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
|
AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
|
||||||
AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
|
AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
|
||||||
@@ -213,6 +211,11 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
|
|||||||
return scopes
|
return scopes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsCategory checks if a list of categories contains a specific category
|
||||||
|
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
|
||||||
|
return slices.Contains(categories, category)
|
||||||
|
}
|
||||||
|
|
||||||
// GetScopeLevelFromAccessMode converts permission access mode to scope level
|
// GetScopeLevelFromAccessMode converts permission access mode to scope level
|
||||||
func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
|
func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
|
||||||
switch mode {
|
switch mode {
|
||||||
@@ -283,14 +286,12 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if bitmap == 0 && slices.Contains(s.StringSlice(), string(AccessTokenScopeNone)) {
|
|
||||||
return AccessTokenScopeNone, nil
|
|
||||||
}
|
|
||||||
return bitmap.toScope(), nil
|
return bitmap.toScope(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s AccessTokenScope) HasPermissionScope() bool {
|
func (s AccessTokenScope) HasPermissionScope() bool {
|
||||||
return s != "" && s != AccessTokenScopeNone && s != AccessTokenScopePublicOnly
|
return s != "" && s != AccessTokenScopePublicOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicOnly checks if this token scope is limited to public resources
|
// PublicOnly checks if this token scope is limited to public resources
|
||||||
|
|||||||
@@ -18,14 +18,8 @@ type scopeTestNormalize struct {
|
|||||||
|
|
||||||
func TestAccessTokenScope_Normalize(t *testing.T) {
|
func TestAccessTokenScope_Normalize(t *testing.T) {
|
||||||
assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories())
|
assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories())
|
||||||
assert.False(t, AccessTokenScopeNone.HasPermissionScope())
|
|
||||||
tests := []scopeTestNormalize{
|
tests := []scopeTestNormalize{
|
||||||
{"", "", nil},
|
{"", "", nil},
|
||||||
{"no-token-scope", "no-token-scope", nil},
|
|
||||||
{"no-token-scope,no-token-scope", "no-token-scope", nil},
|
|
||||||
{"no-token-scope,read:user", "read:user", nil},
|
|
||||||
{"no-token-scope,public-only", "public-only", nil},
|
|
||||||
{"no-token-scope,all", "all", nil},
|
|
||||||
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
|
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
|
||||||
{"all", "all", nil},
|
{"all", "all", nil},
|
||||||
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
|
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
|
||||||
@@ -64,20 +58,10 @@ func TestAccessTokenScope_HasScope(t *testing.T) {
|
|||||||
{"all", "write:package", true, nil},
|
{"all", "write:package", true, nil},
|
||||||
{"write:package", "all", false, nil},
|
{"write:package", "all", false, nil},
|
||||||
{"public-only", "read:issue", false, nil},
|
{"public-only", "read:issue", false, nil},
|
||||||
{"no-token-scope", "all", false, nil},
|
|
||||||
{"no-token-scope", "no-token-scope", true, nil},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, scope := range GetAccessTokenCategories() {
|
for _, scope := range GetAccessTokenCategories() {
|
||||||
tests = append(tests,
|
tests = append(tests,
|
||||||
scopeTestHasScope{
|
|
||||||
AccessTokenScopeNone,
|
|
||||||
AccessTokenScope("read:" + scope), false, nil,
|
|
||||||
},
|
|
||||||
scopeTestHasScope{
|
|
||||||
AccessTokenScopeNone,
|
|
||||||
AccessTokenScope("write:" + scope), false, nil,
|
|
||||||
},
|
|
||||||
scopeTestHasScope{
|
scopeTestHasScope{
|
||||||
AccessTokenScope("read:" + scope),
|
AccessTokenScope("read:" + scope),
|
||||||
AccessTokenScope("read:" + scope), true, nil,
|
AccessTokenScope("read:" + scope), true, nil,
|
||||||
|
|||||||
+131
-127
@@ -14,148 +14,97 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccessTokenLifecycle(t *testing.T) {
|
func TestNewAccessToken(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
token := &auth_model.AccessToken{
|
||||||
|
UID: 3,
|
||||||
|
Name: "Token C",
|
||||||
|
}
|
||||||
|
assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
|
||||||
|
unittest.AssertExistsAndLoadBean(t, token)
|
||||||
|
|
||||||
t.Run("GetAccessTokenBySHA", func(t *testing.T) {
|
invalidToken := &auth_model.AccessToken{
|
||||||
token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36")
|
ID: token.ID, // duplicate
|
||||||
assert.NoError(t, err)
|
UID: 2,
|
||||||
assert.Equal(t, int64(1), token.UID)
|
Name: "Token F",
|
||||||
assert.Equal(t, "Token A", token.Name)
|
}
|
||||||
assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
|
assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken))
|
||||||
assert.Equal(t, "e4efbf36", token.TokenLastEight)
|
}
|
||||||
|
|
||||||
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash")
|
func TestAccessTokenByNameExists(t *testing.T) {
|
||||||
assert.Error(t, err)
|
name := "Token Gitea"
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
|
|
||||||
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "")
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
assert.Error(t, err)
|
token := &auth_model.AccessToken{
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
UID: 3,
|
||||||
})
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("ListAccessTokens", func(t *testing.T) {
|
// Check to make sure it doesn't exists already
|
||||||
tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1})
|
exist, err := auth_model.AccessTokenByNameExists(t.Context(), token)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, tokens, 2) {
|
assert.False(t, exist)
|
||||||
assert.Equal(t, int64(1), tokens[0].UID)
|
|
||||||
assert.Equal(t, int64(1), tokens[1].UID)
|
|
||||||
assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A")
|
|
||||||
assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B")
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2})
|
// Save it to the database
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
|
||||||
if assert.Len(t, tokens, 1) {
|
unittest.AssertExistsAndLoadBean(t, token)
|
||||||
assert.Equal(t, int64(2), tokens[0].UID)
|
|
||||||
assert.Equal(t, "Token A", tokens[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100})
|
// This token must be found by name in the DB now
|
||||||
assert.NoError(t, err)
|
exist, err = auth_model.AccessTokenByNameExists(t.Context(), token)
|
||||||
assert.Empty(t, tokens)
|
assert.NoError(t, err)
|
||||||
})
|
assert.True(t, exist)
|
||||||
|
|
||||||
t.Run("NewAccessToken", func(t *testing.T) {
|
user4Token := &auth_model.AccessToken{
|
||||||
token := &auth_model.AccessToken{
|
UID: 4,
|
||||||
UID: 3,
|
Name: name,
|
||||||
Name: "Token C",
|
}
|
||||||
}
|
|
||||||
assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
|
|
||||||
unittest.AssertExistsAndLoadBean(t, token)
|
|
||||||
|
|
||||||
invalidToken := &auth_model.AccessToken{
|
// Name matches but different user ID, this shouldn't exists in the
|
||||||
ID: token.ID, // duplicate
|
// database
|
||||||
UID: 2,
|
exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token)
|
||||||
Name: "Token F",
|
assert.NoError(t, err)
|
||||||
}
|
assert.False(t, exist)
|
||||||
assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken))
|
}
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("AccessTokenByNameExists", func(t *testing.T) {
|
func TestGetAccessTokenBySHA(t *testing.T) {
|
||||||
name := "Token Gitea"
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), token.UID)
|
||||||
|
assert.Equal(t, "Token A", token.Name)
|
||||||
|
assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
|
||||||
|
assert.Equal(t, "e4efbf36", token.TokenLastEight)
|
||||||
|
|
||||||
token := &auth_model.AccessToken{
|
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash")
|
||||||
UID: 3,
|
assert.Error(t, err)
|
||||||
Name: name,
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
}
|
|
||||||
|
|
||||||
// Check to make sure it doesn't exists already
|
_, err = auth_model.GetAccessTokenBySHA(t.Context(), "")
|
||||||
exist, err := auth_model.AccessTokenByNameExists(t.Context(), token)
|
assert.Error(t, err)
|
||||||
assert.NoError(t, err)
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
assert.False(t, exist)
|
}
|
||||||
|
|
||||||
// Save it to the database
|
func TestListAccessTokens(t *testing.T) {
|
||||||
assert.NoError(t, auth_model.NewAccessToken(t.Context(), token))
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
unittest.AssertExistsAndLoadBean(t, token)
|
tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, tokens, 2) {
|
||||||
|
assert.Equal(t, int64(1), tokens[0].UID)
|
||||||
|
assert.Equal(t, int64(1), tokens[1].UID)
|
||||||
|
assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A")
|
||||||
|
assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B")
|
||||||
|
}
|
||||||
|
|
||||||
// This token must be found by name in the DB now
|
tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2})
|
||||||
exist, err = auth_model.AccessTokenByNameExists(t.Context(), token)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
if assert.Len(t, tokens, 1) {
|
||||||
assert.True(t, exist)
|
assert.Equal(t, int64(2), tokens[0].UID)
|
||||||
|
assert.Equal(t, "Token A", tokens[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
user4Token := &auth_model.AccessToken{
|
tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100})
|
||||||
UID: 4,
|
assert.NoError(t, err)
|
||||||
Name: name,
|
assert.Empty(t, tokens)
|
||||||
}
|
|
||||||
|
|
||||||
// Name matches but different user ID, this shouldn't exists in the
|
|
||||||
// database
|
|
||||||
exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, exist)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("RegenerateAccessToken", func(t *testing.T) {
|
|
||||||
const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36"
|
|
||||||
|
|
||||||
// prime the successful-lookup cache with the old token value, as a real request would
|
|
||||||
before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "Token A", before.Name)
|
|
||||||
|
|
||||||
regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, before.ID, regenerated.ID)
|
|
||||||
assert.Equal(t, before.Name, regenerated.Name)
|
|
||||||
assert.Equal(t, before.Scope, regenerated.Scope)
|
|
||||||
assert.NotEqual(t, before.TokenHash, regenerated.TokenHash)
|
|
||||||
assert.NotEmpty(t, regenerated.Token)
|
|
||||||
|
|
||||||
// the old token value must stop authenticating, even though it was cached as successful above
|
|
||||||
_, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
|
|
||||||
// the new token value must authenticate
|
|
||||||
found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, before.ID, found.ID)
|
|
||||||
assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix)
|
|
||||||
|
|
||||||
// wrong owner
|
|
||||||
_, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
|
|
||||||
// nonexistent token
|
|
||||||
_, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("DeleteAccessTokenByID", func(t *testing.T) {
|
|
||||||
token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(1), token.UID)
|
|
||||||
|
|
||||||
assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1))
|
|
||||||
unittest.AssertNotExistsBean(t, token)
|
|
||||||
|
|
||||||
err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateAccessToken(t *testing.T) {
|
func TestUpdateAccessToken(t *testing.T) {
|
||||||
@@ -167,3 +116,58 @@ func TestUpdateAccessToken(t *testing.T) {
|
|||||||
assert.NoError(t, auth_model.UpdateAccessToken(t.Context(), token))
|
assert.NoError(t, auth_model.UpdateAccessToken(t.Context(), token))
|
||||||
unittest.AssertExistsAndLoadBean(t, token)
|
unittest.AssertExistsAndLoadBean(t, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegenerateAccessToken(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36"
|
||||||
|
|
||||||
|
// prime the successful-lookup cache with the old token value, as a real request would
|
||||||
|
before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Token A", before.Name)
|
||||||
|
|
||||||
|
regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, before.ID, regenerated.ID)
|
||||||
|
assert.Equal(t, before.Name, regenerated.Name)
|
||||||
|
assert.Equal(t, before.Scope, regenerated.Scope)
|
||||||
|
assert.NotEqual(t, before.TokenHash, regenerated.TokenHash)
|
||||||
|
assert.NotEmpty(t, regenerated.Token)
|
||||||
|
|
||||||
|
// the old token value must stop authenticating, even though it was cached as successful above
|
||||||
|
_, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
|
||||||
|
// the new token value must authenticate
|
||||||
|
found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, before.ID, found.ID)
|
||||||
|
assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix)
|
||||||
|
|
||||||
|
// wrong owner
|
||||||
|
_, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
|
||||||
|
// nonexistent token
|
||||||
|
_, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteAccessTokenByID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), token.UID)
|
||||||
|
|
||||||
|
assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1))
|
||||||
|
unittest.AssertNotExistsBean(t, token)
|
||||||
|
|
||||||
|
err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
}
|
||||||
@@ -20,9 +20,6 @@ type AuthToken struct { //nolint:revive // export stutter
|
|||||||
TokenHash string
|
TokenHash string
|
||||||
UserID int64 `xorm:"INDEX"`
|
UserID int64 `xorm:"INDEX"`
|
||||||
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
|
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
|
||||||
PreviousTokenHash string // Identifies stale cookies without authenticating them.
|
|
||||||
RotatedUnix timeutil.TimeStamp // Bounds the grace window for stale cookies.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -49,7 +46,7 @@ func GetAuthTokenByID(ctx context.Context, id string) (*AuthToken, error) {
|
|||||||
|
|
||||||
func UpdateAuthTokenByID(ctx context.Context, t *AuthToken, oldHash string) (bool, error) {
|
func UpdateAuthTokenByID(ctx context.Context, t *AuthToken, oldHash string) (bool, error) {
|
||||||
affected, err := db.GetEngine(ctx).ID(t.ID).Where("token_hash = ?", oldHash).
|
affected, err := db.GetEngine(ctx).ID(t.ID).Where("token_hash = ?", oldHash).
|
||||||
Cols("token_hash", "expires_unix", "previous_token_hash", "rotated_unix").Update(t)
|
Cols("token_hash", "expires_unix").Update(t)
|
||||||
return affected == 1, err
|
return affected == 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -25,6 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
|
// Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
|
||||||
@@ -596,17 +597,22 @@ func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, er
|
|||||||
Grant *OAuth2Grant `xorm:"extends"`
|
Grant *OAuth2Grant `xorm:"extends"`
|
||||||
Application *OAuth2Application `xorm:"extends"`
|
Application *OAuth2Application `xorm:"extends"`
|
||||||
}
|
}
|
||||||
var joinedOAuth2Grants []*joinedOAuth2Grant
|
var results *xorm.Rows
|
||||||
err := db.GetEngine(ctx).
|
var err error
|
||||||
|
if results, err = db.GetEngine(ctx).
|
||||||
Table("oauth2_grant").
|
Table("oauth2_grant").
|
||||||
Where("user_id = ?", uid).
|
Where("user_id = ?", uid).
|
||||||
Join("INNER", "oauth2_application", "application_id = oauth2_application.id").
|
Join("INNER", "oauth2_application", "application_id = oauth2_application.id").
|
||||||
Find(&joinedOAuth2Grants)
|
Rows(new(joinedOAuth2Grant)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer results.Close()
|
||||||
grants := make([]*OAuth2Grant, 0)
|
grants := make([]*OAuth2Grant, 0)
|
||||||
for _, joinedGrant := range joinedOAuth2Grants {
|
for results.Next() {
|
||||||
|
joinedGrant := new(joinedOAuth2Grant)
|
||||||
|
if err := results.Scan(joinedGrant); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
joinedGrant.Grant.Application = joinedGrant.Application
|
joinedGrant.Grant.Application = joinedGrant.Application
|
||||||
grants = append(grants, joinedGrant.Grant)
|
grants = append(grants, joinedGrant.Grant)
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-104
@@ -57,105 +57,13 @@ func TestOAuth2AuthorizationCode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2ApplicationAndGrant(t *testing.T) {
|
func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||||
t.Run("GetOAuth2ApplicationByClientID", func(t *testing.T) {
|
secret, err := app.GenerateClientSecret(t.Context())
|
||||||
app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138")
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
assert.NotEmpty(t, secret)
|
||||||
assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID)
|
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
|
||||||
|
|
||||||
app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id")
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, app)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Application_GetGrantByUserID", func(t *testing.T) {
|
|
||||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
|
||||||
grant, err := app.GetGrantByUserID(t.Context(), 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(1), grant.UserID)
|
|
||||||
|
|
||||||
grant, err = app.GetGrantByUserID(t.Context(), 34923458)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Nil(t, grant)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetOAuth2GrantByID", func(t *testing.T) {
|
|
||||||
grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(1), grant.ID)
|
|
||||||
|
|
||||||
grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Nil(t, grant)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Grant_ScopeContains", func(t *testing.T) {
|
|
||||||
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"})
|
|
||||||
assert.True(t, grant.ScopeContains("openid"))
|
|
||||||
assert.True(t, grant.ScopeContains("profile"))
|
|
||||||
assert.False(t, grant.ScopeContains("profil"))
|
|
||||||
assert.False(t, grant.ScopeContains("profile2"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetOAuth2GrantsByUserID", func(t *testing.T) {
|
|
||||||
result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, result, 1)
|
|
||||||
assert.Equal(t, int64(1), result[0].ID)
|
|
||||||
assert.Equal(t, result[0].ApplicationID, result[0].Application.ID)
|
|
||||||
|
|
||||||
result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Empty(t, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Grant_GenerateNewAuthorizationCode", func(t *testing.T) {
|
|
||||||
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1})
|
|
||||||
code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, code)
|
|
||||||
assert.Greater(t, len(code.Code), 32) // secret length > 32
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Application_CreateGrant", func(t *testing.T) {
|
|
||||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
|
||||||
grant, err := app.CreateGrant(t.Context(), 2, "")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, grant)
|
|
||||||
assert.Equal(t, int64(2), grant.UserID)
|
|
||||||
assert.Equal(t, int64(1), grant.ApplicationID)
|
|
||||||
assert.Empty(t, grant.Scope)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Grant_IncreaseCounter", func(t *testing.T) {
|
|
||||||
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
|
|
||||||
stale := *grant
|
|
||||||
assert.NoError(t, grant.IncreaseCounter(t.Context()))
|
|
||||||
assert.Equal(t, int64(2), grant.Counter)
|
|
||||||
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
|
|
||||||
err := stale.IncreaseCounter(t.Context())
|
|
||||||
assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OAuth2Application_GenerateClientSecret", func(t *testing.T) {
|
|
||||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
|
||||||
secret, err := app.GenerateClientSecret(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, secret)
|
|
||||||
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
|
|
||||||
assert.True(t, app.ValidateClientSecret([]byte(secret)))
|
|
||||||
assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew")))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CreateOAuth2Application", func(t *testing.T) {
|
|
||||||
app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "newapp", app.Name)
|
|
||||||
assert.Len(t, app.ClientID, 36)
|
|
||||||
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) {
|
func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) {
|
||||||
@@ -198,12 +106,15 @@ func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
|
func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
|
||||||
for _, redirectURI := range []string{"http://127.0.0.1", "http://127.0.0.1/"} {
|
app := &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1"}}
|
||||||
app := &auth_model.OAuth2Application{RedirectURIs: []string{redirectURI}}
|
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
|
||||||
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
|
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
|
||||||
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
|
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
|
||||||
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
|
|
||||||
}
|
app = &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1/"}}
|
||||||
|
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
|
||||||
|
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
|
||||||
|
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing.T) {
|
func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing.T) {
|
||||||
@@ -247,16 +158,128 @@ func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||||
|
secret, err := app.GenerateClientSecret(t.Context())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, app.ValidateClientSecret([]byte(secret)))
|
||||||
|
assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOAuth2ApplicationByClientID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID)
|
||||||
|
|
||||||
|
app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateOAuth2Application(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "newapp", app.Name)
|
||||||
|
assert.Len(t, app.ClientID, 36)
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"})
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuth2Application_TableName(t *testing.T) {
|
func TestOAuth2Application_TableName(t *testing.T) {
|
||||||
assert.Equal(t, "oauth2_application", new(auth_model.OAuth2Application).TableName())
|
assert.Equal(t, "oauth2_application", new(auth_model.OAuth2Application).TableName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Application_GetGrantByUserID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||||
|
grant, err := app.GetGrantByUserID(t.Context(), 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), grant.UserID)
|
||||||
|
|
||||||
|
grant, err = app.GetGrantByUserID(t.Context(), 34923458)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, grant)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Application_CreateGrant(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||||
|
grant, err := app.CreateGrant(t.Context(), 2, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, grant)
|
||||||
|
assert.Equal(t, int64(2), grant.UserID)
|
||||||
|
assert.Equal(t, int64(1), grant.ApplicationID)
|
||||||
|
assert.Empty(t, grant.Scope)
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////// Grant
|
//////////////////// Grant
|
||||||
|
|
||||||
|
func TestGetOAuth2GrantByID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), grant.ID)
|
||||||
|
|
||||||
|
grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, grant)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
|
||||||
|
assert.NoError(t, grant.IncreaseCounter(t.Context()))
|
||||||
|
assert.Equal(t, int64(2), grant.Counter)
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Grant_IncreaseCounterRejectsStaleCounter(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
|
||||||
|
stale := *grant
|
||||||
|
|
||||||
|
assert.NoError(t, grant.IncreaseCounter(t.Context()))
|
||||||
|
err := stale.IncreaseCounter(t.Context())
|
||||||
|
assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Grant_ScopeContains(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"})
|
||||||
|
assert.True(t, grant.ScopeContains("openid"))
|
||||||
|
assert.True(t, grant.ScopeContains("profile"))
|
||||||
|
assert.False(t, grant.ScopeContains("profil"))
|
||||||
|
assert.False(t, grant.ScopeContains("profile2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1})
|
||||||
|
code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, code)
|
||||||
|
assert.Greater(t, len(code.Code), 32) // secret length > 32
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuth2Grant_TableName(t *testing.T) {
|
func TestOAuth2Grant_TableName(t *testing.T) {
|
||||||
assert.Equal(t, "oauth2_grant", new(auth_model.OAuth2Grant).TableName())
|
assert.Equal(t, "oauth2_grant", new(auth_model.OAuth2Grant).TableName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetOAuth2GrantsByUserID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
assert.Equal(t, int64(1), result[0].ID)
|
||||||
|
assert.Equal(t, result[0].ApplicationID, result[0].Application.ID)
|
||||||
|
|
||||||
|
result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRevokeOAuth2Grant(t *testing.T) {
|
func TestRevokeOAuth2Grant(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
assert.NoError(t, auth_model.RevokeOAuth2Grant(t.Context(), 1, 1))
|
assert.NoError(t, auth_model.RevokeOAuth2Grant(t.Context(), 1, 1))
|
||||||
|
|||||||
+72
-29
@@ -5,6 +5,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/timeutil"
|
"gitea.dev/modules/timeutil"
|
||||||
@@ -12,46 +13,49 @@ import (
|
|||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Session represents a session compatible for go-chi session
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Key string `xorm:"pk CHAR(16)"` // the limit is from legacy go-chi/session
|
Key string `xorm:"pk CHAR(16)"` // has to be Key to match with go-chi/session
|
||||||
Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb
|
Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb - this may need to be increased
|
||||||
LastAccessTime timeutil.TimeStamp `xorm:"expiry"` // last access time, the field name is from legacy go-chi/session, we don't want to change it at the moment
|
Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session
|
||||||
}
|
}
|
||||||
|
|
||||||
const DbSessionLastAccessTime = "expiry" // maybe we can make a deeper clean up in the future, just keep this PR focused
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Session))
|
db.RegisterModel(new(Session))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSession stores the data of the session with provided id, creating the session only if create is set
|
// UpdateSession updates the session with provided id
|
||||||
func UpdateSession(ctx context.Context, key string, data []byte, create bool) error {
|
func UpdateSession(ctx context.Context, key string, data []byte) error {
|
||||||
session := &Session{Key: key, Data: data, LastAccessTime: timeutil.TimeStampNow()}
|
_, err := db.GetEngine(ctx).ID(key).Update(&Session{
|
||||||
update := func() (int64, error) {
|
Data: data,
|
||||||
return db.GetEngine(ctx).ID(key).Cols("data", DbSessionLastAccessTime).Update(session)
|
Expiry: timeutil.TimeStampNow(),
|
||||||
}
|
})
|
||||||
if updated, err := update(); err != nil || updated > 0 || !create {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
insertErr := db.Insert(ctx, session)
|
|
||||||
if insertErr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// the row exists if a concurrent request inserted it, or if MySQL reported an unchanged row as not updated
|
|
||||||
if exist, err := db.Exist[Session](ctx, builder.Eq{"`key`": key}); err != nil || !exist {
|
|
||||||
return insertErr
|
|
||||||
}
|
|
||||||
_, err := update()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateSessionLastAccessTime(ctx context.Context, key string) error {
|
// ReadSession reads the data for the provided session
|
||||||
_, err := db.GetEngine(ctx).ID(key).Cols(DbSessionLastAccessTime).Update(&Session{LastAccessTime: timeutil.TimeStampNow()})
|
func ReadSession(ctx context.Context, key string) (*Session, error) {
|
||||||
return err
|
return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
|
||||||
|
session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !exist {
|
||||||
|
session = &Session{
|
||||||
|
Key: key,
|
||||||
|
Expiry: timeutil.TimeStampNow(),
|
||||||
|
}
|
||||||
|
if err := db.Insert(ctx, session); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(ctx context.Context, key string) (*Session, bool, error) {
|
// ExistSession checks if a session exists
|
||||||
return db.Get[Session](ctx, builder.Eq{"`key`": key})
|
func ExistSession(ctx context.Context, key string) (bool, error) {
|
||||||
|
return db.Exist[Session](ctx, builder.Eq{"`key`": key})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestroySession destroys a session
|
// DestroySession destroys a session
|
||||||
@@ -62,8 +66,47 @@ func DestroySession(ctx context.Context, key string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegenerateSession regenerates a session from the old id
|
||||||
|
func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, error) {
|
||||||
|
return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
|
||||||
|
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if has {
|
||||||
|
return nil, fmt.Errorf("session Key: %s already exists", newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !has {
|
||||||
|
if err := db.Insert(ctx, &Session{
|
||||||
|
Key: oldKey,
|
||||||
|
Expiry: timeutil.TimeStampNow(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(ctx, "UPDATE `session` SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey})
|
||||||
|
if err != nil {
|
||||||
|
// is not exist, it should be impossible
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountSessions returns the number of sessions
|
||||||
|
func CountSessions(ctx context.Context) (int64, error) {
|
||||||
|
return db.GetEngine(ctx).Count(&Session{})
|
||||||
|
}
|
||||||
|
|
||||||
// CleanupSessions cleans up expired sessions
|
// CleanupSessions cleans up expired sessions
|
||||||
func CleanupSessions(ctx context.Context, maxLifetime int64) error {
|
func CleanupSessions(ctx context.Context, maxLifetime int64) error {
|
||||||
_, err := db.GetEngine(ctx).Where(DbSessionLastAccessTime+" <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
|
_, err := db.GetEngine(ctx).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -16,68 +16,67 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTwoFactorLifecycle(t *testing.T) {
|
func TestTwoFactorValidateAndConsumeTOTP(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
t.Run("TwoFactorValidateAndConsumeTOTP", func(t *testing.T) {
|
key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"})
|
||||||
key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"})
|
require.NoError(t, err)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tfa := &auth_model.TwoFactor{UID: 1}
|
tfa := &auth_model.TwoFactor{UID: 1}
|
||||||
require.NoError(t, tfa.SetSecret(key.Secret()))
|
require.NoError(t, tfa.SetSecret(key.Secret()))
|
||||||
require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa))
|
require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa))
|
||||||
|
|
||||||
passcode, err := totp.GenerateCode(key.Secret(), time.Now())
|
passcode, err := totp.GenerateCode(key.Secret(), time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// first use of a valid passcode succeeds
|
// first use of a valid passcode succeeds
|
||||||
ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode)
|
ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
// replaying the same passcode is refused, even when still inside the TOTP validity window
|
// replaying the same passcode is refused, even when still inside the TOTP validity window
|
||||||
reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID)
|
reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode)
|
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
// an invalid passcode is rejected without consuming anything
|
// an invalid passcode is rejected without consuming anything
|
||||||
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000")
|
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("DisableTwoFactor", func(t *testing.T) {
|
func TestDisableTwoFactor(t *testing.T) {
|
||||||
ctx := t.Context()
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
const uid = 1000 // a uid with no user/2FA fixtures
|
|
||||||
|
const uid = 1000 // a uid with no user/2FA fixtures
|
||||||
// Enroll TOTP and register a WebAuthn credential.
|
|
||||||
tfa := &auth_model.TwoFactor{UID: uid}
|
// Enroll TOTP and register a WebAuthn credential.
|
||||||
require.NoError(t, tfa.SetSecret("test-secret"))
|
tfa := &auth_model.TwoFactor{UID: uid}
|
||||||
require.NoError(t, auth_model.NewTwoFactor(ctx, tfa))
|
require.NoError(t, tfa.SetSecret("test-secret"))
|
||||||
_, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
|
require.NoError(t, auth_model.NewTwoFactor(ctx, tfa))
|
||||||
require.NoError(t, err)
|
_, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")})
|
||||||
|
require.NoError(t, err)
|
||||||
has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
|
||||||
require.NoError(t, err)
|
has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
||||||
require.True(t, has)
|
require.NoError(t, err)
|
||||||
|
require.True(t, has)
|
||||||
// Both records are removed and counted separately.
|
|
||||||
totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid)
|
// Both records are removed and counted separately.
|
||||||
require.NoError(t, err)
|
totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid)
|
||||||
assert.EqualValues(t, 1, totp)
|
require.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, webAuthn)
|
assert.EqualValues(t, 1, totp)
|
||||||
|
assert.EqualValues(t, 1, webAuthn)
|
||||||
has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
|
||||||
require.NoError(t, err)
|
has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid)
|
||||||
assert.False(t, has)
|
require.NoError(t, err)
|
||||||
|
assert.False(t, has)
|
||||||
// A second call on a user without 2FA is a no-op.
|
|
||||||
totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid)
|
// A second call on a user without 2FA is a no-op.
|
||||||
require.NoError(t, err)
|
totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid)
|
||||||
assert.EqualValues(t, 0, totp)
|
require.NoError(t, err)
|
||||||
assert.EqualValues(t, 0, webAuthn)
|
assert.EqualValues(t, 0, totp)
|
||||||
})
|
assert.EqualValues(t, 0, webAuthn)
|
||||||
}
|
}
|
||||||
Loaded 100 of 1422 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user