mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 06:20:24 +00:00
Compare commits
290 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e5dbfa169 | ||
|
|
88ba174119 | ||
|
|
0e577ed6c9 | ||
|
|
a6e141f805 | ||
|
|
9d37ac68ee | ||
|
|
32b8d732b8 | ||
|
|
5b6c702f41 | ||
|
|
88fd372d9a | ||
|
|
753e289da5 | ||
|
|
efe52db86f | ||
|
|
cbaf97b867 | ||
|
|
ba1c3e0288 | ||
|
|
03312e4f46 | ||
|
|
2d5dd62cf3 | ||
|
|
b21b173f6e | ||
|
|
6b516e2721 | ||
|
|
3f0a8b4424 | ||
|
|
dcf1e7ce09 | ||
|
|
a59879402e | ||
|
|
c25cbd6fc4 | ||
|
|
6b75654cc2 | ||
|
|
a8cae6d511 | ||
|
|
e5eb5f8e63 | ||
|
|
a4d623148d | ||
|
|
0cdbef74f0 | ||
|
|
3fc3942356 | ||
|
|
d2a7fc1458 | ||
|
|
9a5d9397a4 | ||
|
|
169ea1d991 | ||
|
|
508bb7f2ae | ||
|
|
4e40724199 | ||
|
|
92863bb103 | ||
|
|
0b3192b8af | ||
|
|
31621e9b97 | ||
|
|
0295658650 | ||
|
|
49f9cc7c4d | ||
|
|
6132d0e406 | ||
|
|
3a35c5353e | ||
|
|
ffd10d37a6 | ||
|
|
115f8594cf | ||
|
|
5022be3029 | ||
|
|
f55f3481f2 | ||
|
|
326ae6ad67 | ||
|
|
bf958fa355 | ||
|
|
69cf1f3333 | ||
|
|
5ccb9e815a | ||
|
|
8bb8ae30e1 | ||
|
|
1cdef7d39f | ||
|
|
09aaa129a2 | ||
|
|
59787fc2a0 | ||
|
|
4cebc5d1d5 | ||
|
|
0af17c5f8a | ||
|
|
c07ea09050 | ||
|
|
6f5bef54b0 | ||
|
|
c1ac671b55 | ||
|
|
525a377c24 | ||
|
|
0b2415a05a | ||
|
|
ed32a0fb5a | ||
|
|
780526b1a8 | ||
|
|
76b83c4467 | ||
|
|
d27cd9f722 | ||
|
|
e89312de9b | ||
|
|
9b88e77c19 | ||
|
|
d63724ceab | ||
|
|
555d88070d | ||
|
|
e9710af24f | ||
|
|
a2557f0f42 | ||
|
|
743b3b4cd9 | ||
|
|
7e205c5718 | ||
|
|
ee8ad6581c | ||
|
|
8edcb8d4db | ||
|
|
b6658076a9 | ||
|
|
731334e973 | ||
|
|
07a6b6ce82 | ||
|
|
c1dc213c9b | ||
|
|
9d323c5125 | ||
|
|
d867b25e72 | ||
|
|
7fc236c589 | ||
|
|
67250869d3 | ||
|
|
cb05be1a09 | ||
|
|
b5e7a72e10 | ||
|
|
948f8cc61a | ||
|
|
1acf630dbf | ||
|
|
eb58d6c9d0 | ||
|
|
254a44b97b | ||
|
|
75cfa31af5 | ||
|
|
3e74c5224f | ||
|
|
cc60e3d693 | ||
|
|
68be312467 | ||
|
|
e0777227d3 | ||
|
|
065a3a23f4 | ||
|
|
25e7a0b91b | ||
|
|
81c46e4a7c | ||
|
|
5c05973994 | ||
|
|
fd0a2086b0 | ||
|
|
32c9bbee08 | ||
|
|
bc7c8e3c84 | ||
|
|
be3fe4ff60 | ||
|
|
993b419fe3 | ||
|
|
cc5f118af8 | ||
|
|
7d2a9bb0fc | ||
|
|
733a390ecd | ||
|
|
70f7260e66 | ||
|
|
abcfb46691 | ||
|
|
6171e7ef7a | ||
|
|
37412e6a00 | ||
|
|
2425ae7725 | ||
|
|
9977df96d5 | ||
|
|
93296305f9 | ||
|
|
73b30acbd0 | ||
|
|
f05ff7ec5b | ||
|
|
90c4397d57 | ||
|
|
f8eafe8c90 | ||
|
|
5e5ad79d10 | ||
|
|
94ef440a1c | ||
|
|
900306e65a | ||
|
|
c9d8682f90 | ||
|
|
48218c654b | ||
|
|
b17ed16f31 | ||
|
|
0d94308619 | ||
|
|
cb1cb2c0af | ||
|
|
10643ceb9b | ||
|
|
9d275907c5 | ||
|
|
ef5479af71 | ||
|
|
db622afd87 | ||
|
|
a562140896 | ||
|
|
9f7533c1f1 | ||
|
|
7a86a870c6 | ||
|
|
1ddd5faa5c | ||
|
|
2ed98ac848 | ||
|
|
1b6fe54e08 | ||
|
|
4001ab027a | ||
|
|
529b14291d | ||
|
|
0034e55965 | ||
|
|
33d6ecfca6 | ||
|
|
9c4fc72985 | ||
|
|
23b541ce5a | ||
|
|
a85c527709 | ||
|
|
f9b3630911 | ||
|
|
469cd0847e | ||
|
|
d49da9d238 | ||
|
|
c37b4d38b1 | ||
|
|
8b4aa4478f | ||
|
|
6cd3f0263d | ||
|
|
178a0a25f8 | ||
|
|
99299a5685 | ||
|
|
4b8d118cce | ||
|
|
f596bd0324 | ||
|
|
9ccaf473eb | ||
|
|
a11d0db2e1 | ||
|
|
bacd8f365d | ||
|
|
46d890c8e1 | ||
|
|
83459905d1 | ||
|
|
766c9c64f5 | ||
|
|
b6b5592e7f | ||
|
|
00269b3a0b | ||
|
|
c7b5026f59 | ||
|
|
6236a4cc99 | ||
|
|
39f677c0db | ||
|
|
a4b575fd75 | ||
|
|
2c0c48f50e | ||
|
|
da8898822c | ||
|
|
60332ed111 | ||
|
|
eea5ac9639 | ||
|
|
8cb776dcac | ||
|
|
922573ba2d | ||
|
|
1cd81146a9 | ||
|
|
3fe02a2175 | ||
|
|
88a0551f54 | ||
|
|
8cdfe1d57a | ||
|
|
1b6e124087 | ||
|
|
179fbdb04e | ||
|
|
0d97b8e9da | ||
|
|
94a55fc666 | ||
|
|
cf26e4c891 | ||
|
|
5f432e32c8 | ||
|
|
a797a71dea | ||
|
|
86898a7d05 | ||
|
|
a1be012c4a | ||
|
|
aad64a9508 | ||
|
|
2b42fdaa26 | ||
|
|
fd28fd896b | ||
|
|
9de142eb7f | ||
|
|
ca00f99c3f | ||
|
|
40aa3a5c7d | ||
|
|
160cd930ff | ||
|
|
9fe0cbee02 | ||
|
|
92b95414e8 | ||
|
|
bf68be21e7 | ||
|
|
d9176897d0 | ||
|
|
988d702480 | ||
|
|
b0267f7d79 | ||
|
|
e636cd2765 | ||
|
|
543c2d93ad | ||
|
|
72e57743d9 | ||
|
|
d1b69632aa | ||
|
|
43075c080a | ||
|
|
16fbcff8fa | ||
|
|
6a5dda7116 | ||
|
|
e16dc2ebfd | ||
|
|
4e6a782a89 | ||
|
|
65044ca765 | ||
|
|
73ad72949a | ||
|
|
703256e50e | ||
|
|
b2617cf0bb | ||
|
|
7069203e3e | ||
|
|
ace9bd2a68 | ||
|
|
4b2969ab84 | ||
|
|
64d19661ce | ||
|
|
3982685c35 | ||
|
|
24af9cf8ee | ||
|
|
c55d7ba9d4 | ||
|
|
503bcf1237 | ||
|
|
8b7327c344 | ||
|
|
8154ea5bea | ||
|
|
bdd2a1def7 | ||
|
|
8f48841c68 | ||
|
|
8a6d76cff4 | ||
|
|
dd968f147d | ||
|
|
0b9e11d96b | ||
|
|
92f1b6fdd2 | ||
|
|
f18873f83b | ||
|
|
9abc1b0144 | ||
|
|
6a879e79df | ||
|
|
15b4c5efe8 | ||
|
|
e14e220651 | ||
|
|
90ca611695 | ||
|
|
80d840c128 | ||
|
|
df86b495dc | ||
|
|
2d2029c598 | ||
|
|
6a99b6b0c1 | ||
|
|
e4bd84b574 | ||
|
|
267f90c97a | ||
|
|
2027ccd994 | ||
|
|
1d0503d6b5 | ||
|
|
ce27a5993c | ||
|
|
fd489b6963 | ||
|
|
3291f06749 | ||
|
|
728936ccd9 | ||
|
|
2fc3144de4 | ||
|
|
4121d5ec85 | ||
|
|
6e8939952c | ||
|
|
c01b13d119 | ||
|
|
8fb287f9d8 | ||
|
|
f37f794670 | ||
|
|
77dbc35138 | ||
|
|
d728fddec5 | ||
|
|
6fa7bf933a | ||
|
|
b09cf32cb9 | ||
|
|
5add2e0dee | ||
|
|
2469344824 | ||
|
|
8f5dd81537 | ||
|
|
d23d895220 | ||
|
|
645b29395c | ||
|
|
46d2f15c94 | ||
|
|
26da41171a | ||
|
|
8d49d59877 | ||
|
|
3763a88c67 | ||
|
|
9e3c3e5d53 | ||
|
|
5803c596b6 | ||
|
|
34937d9305 | ||
|
|
7a34a7fc6d | ||
|
|
bfebd42a3d | ||
|
|
ba6794348e | ||
|
|
6726b6e3e9 | ||
|
|
676940a853 | ||
|
|
a9bd068d00 | ||
|
|
939a3ada66 | ||
|
|
2176403a8d | ||
|
|
8387974e2e | ||
|
|
5c13563682 | ||
|
|
7886e74b25 | ||
|
|
3ec8e96646 | ||
|
|
eaceb845ea | ||
|
|
cacbe76b13 | ||
|
|
a6ee3e61cc | ||
|
|
edc8e19ab0 | ||
|
|
7d7e75c43f | ||
|
|
5afb9467d5 | ||
|
|
0a25c2a7fd | ||
|
|
b68caa311f | ||
|
|
326809a133 | ||
|
|
e823e8cd69 | ||
|
|
bdb87ac3d3 | ||
|
|
9c41a5f717 | ||
|
|
7c7c6ba3b7 | ||
|
|
989804fcc3 | ||
|
|
8707cc10d0 | ||
|
|
75906efe93 | ||
|
|
c6a1d64dc1 |
1082 changed files with 42223 additions and 15027 deletions
|
|
@ -19,7 +19,6 @@ forgejo.org/models/auth
|
||||||
forgejo.org/models/db
|
forgejo.org/models/db
|
||||||
TruncateBeans
|
TruncateBeans
|
||||||
TruncateBeansCascade
|
TruncateBeansCascade
|
||||||
InTransaction
|
|
||||||
DumpTables
|
DumpTables
|
||||||
GetTableNames
|
GetTableNames
|
||||||
extendBeansForCascade
|
extendBeansForCascade
|
||||||
|
|
@ -59,7 +58,6 @@ forgejo.org/models/user
|
||||||
IsErrUserSettingIsNotExist
|
IsErrUserSettingIsNotExist
|
||||||
GetUserAllSettings
|
GetUserAllSettings
|
||||||
DeleteUserSetting
|
DeleteUserSetting
|
||||||
GetFederatedUser
|
|
||||||
|
|
||||||
forgejo.org/modules/activitypub
|
forgejo.org/modules/activitypub
|
||||||
NewContext
|
NewContext
|
||||||
|
|
@ -89,7 +87,6 @@ forgejo.org/modules/eventsource
|
||||||
Event.String
|
Event.String
|
||||||
|
|
||||||
forgejo.org/modules/forgefed
|
forgejo.org/modules/forgefed
|
||||||
NewForgeFollow
|
|
||||||
NewForgeUndoLike
|
NewForgeUndoLike
|
||||||
ForgeUndoLike.UnmarshalJSON
|
ForgeUndoLike.UnmarshalJSON
|
||||||
ForgeUndoLike.Validate
|
ForgeUndoLike.Validate
|
||||||
|
|
@ -135,6 +132,9 @@ forgejo.org/modules/json
|
||||||
StdJSON.Indent
|
StdJSON.Indent
|
||||||
|
|
||||||
forgejo.org/modules/log
|
forgejo.org/modules/log
|
||||||
|
eventWriterBuffer.Close
|
||||||
|
eventWriterBuffer.Write
|
||||||
|
eventWriterBuffer.GetString
|
||||||
NewEventWriterBuffer
|
NewEventWriterBuffer
|
||||||
|
|
||||||
forgejo.org/modules/markup
|
forgejo.org/modules/markup
|
||||||
|
|
@ -225,9 +225,6 @@ forgejo.org/routers/web/org
|
||||||
forgejo.org/services/context
|
forgejo.org/services/context
|
||||||
GetPrivateContext
|
GetPrivateContext
|
||||||
|
|
||||||
forgejo.org/services/federation
|
|
||||||
FollowRemoteActor
|
|
||||||
|
|
||||||
forgejo.org/services/notify
|
forgejo.org/services/notify
|
||||||
UnregisterNotifier
|
UnregisterNotifier
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Gitea DevContainer",
|
"name": "forgejo-dev",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
||||||
"features": {
|
"features": {
|
||||||
// installs nodejs into container
|
// installs nodejs into container
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"baseBranchPatterns": [
|
"baseBranchPatterns": [
|
||||||
"$default",
|
"$default",
|
||||||
"/^v11\\.\\d+/forgejo$/",
|
"/^v11\\.\\d+/forgejo$/",
|
||||||
"/^v14\\.\\d+/forgejo$/"
|
"/^v15\\.\\d+/forgejo$/"
|
||||||
],
|
],
|
||||||
"postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths", "npmDedupe"],
|
"postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths", "npmDedupe"],
|
||||||
"prConcurrentLimit": 10,
|
"prConcurrentLimit": 10,
|
||||||
|
|
@ -138,6 +138,13 @@
|
||||||
],
|
],
|
||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Run end-to-end tests for some dependencies",
|
||||||
|
"matchPackageNames": [
|
||||||
|
"code.forgejo.org/forgejo/runner/**"
|
||||||
|
],
|
||||||
|
"addLabels": ["run-end-to-end-tests"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Disable indirect updates for stable branches",
|
"description": "Disable indirect updates for stable branches",
|
||||||
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
||||||
|
|
@ -152,6 +159,12 @@
|
||||||
"matchUpdateTypes": ["major"],
|
"matchUpdateTypes": ["major"],
|
||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Disable updates for old stable branches but still allow security updates",
|
||||||
|
"matchBaseBranches": ["v11.0/forgejo", "v14.0/forgejo"],
|
||||||
|
"matchUpdateTypes": ["minor", "patch", "digest"],
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Require approval for stable branches (must be last rule to override all others)",
|
"description": "Require approval for stable branches (must be last rule to override all others)",
|
||||||
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
"matchBaseBranches": ["/^v\\d+\\.\\d+\\/forgejo$/"],
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ jobs:
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
${{ toJSON(github) }}
|
${{ toJSON(github) }}
|
||||||
EOF
|
EOF
|
||||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.7
|
- uses: https://data.forgejo.org/actions/git-backporting@v4.9.1
|
||||||
with:
|
with:
|
||||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||||
strategy: ort
|
strategy: ort
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ name: Integration tests for the release process
|
||||||
enable-email-notifications: true
|
enable-email-notifications: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORGEJO_VERSION: 11.0.11 # renovate: datasource=docker depName=data.forgejo.org/forgejo/forgejo
|
FORGEJO_VERSION: 11.0.13 # renovate: datasource=docker depName=data.forgejo.org/forgejo/forgejo
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||||
|
|
||||||
- id: forgejo
|
- id: forgejo
|
||||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.8
|
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.11
|
||||||
with:
|
with:
|
||||||
user: root
|
user: root
|
||||||
password: admin1234
|
password: admin1234
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ jobs:
|
||||||
|
|
||||||
- name: build container & release
|
- name: build container & release
|
||||||
if: ${{ secrets.TOKEN != '' }}
|
if: ${{ secrets.TOKEN != '' }}
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
|
@ -183,7 +183,7 @@ jobs:
|
||||||
|
|
||||||
- name: build rootless container
|
- name: build rootless container
|
||||||
if: ${{ secrets.TOKEN != '' }}
|
if: ${{ secrets.TOKEN != '' }}
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||||
with:
|
with:
|
||||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ jobs:
|
||||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||||
|
|
||||||
- name: copy & sign
|
- name: copy & sign
|
||||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.5.1
|
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.6.0
|
||||||
with:
|
with:
|
||||||
from-forgejo: ${{ vars.FORGEJO }}
|
from-forgejo: ${{ vars.FORGEJO }}
|
||||||
to-forgejo: ${{ vars.FORGEJO }}
|
to-forgejo: ${{ vars.FORGEJO }}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ on:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RNA_WORKDIR: /srv/rna
|
RNA_WORKDIR: /srv/rna
|
||||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
RNA_VERSION: v1.7.0 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ on:
|
||||||
- labeled
|
- labeled
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
RNA_VERSION: v1.7.0 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-notes:
|
release-notes:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ linters:
|
||||||
- govet
|
- govet
|
||||||
- importas
|
- importas
|
||||||
- ineffassign
|
- ineffassign
|
||||||
|
- modernize
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- revive
|
- revive
|
||||||
|
|
@ -45,6 +46,25 @@ linters:
|
||||||
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
|
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
|
||||||
- pkg: gopkg.in/yaml.v3
|
- pkg: gopkg.in/yaml.v3
|
||||||
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
|
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
|
||||||
|
migration-isolation:
|
||||||
|
list-mode: lax
|
||||||
|
files:
|
||||||
|
- "**/models/forgejo_migrations/**"
|
||||||
|
deny:
|
||||||
|
- pkg: "forgejo.org/models"
|
||||||
|
desc: >
|
||||||
|
Migrations must not import application models. Application models will be the most recent schema for
|
||||||
|
Forgejo, while migrations will be operating against the database schema that existed when they were
|
||||||
|
authored.
|
||||||
|
- pkg: "forgejo.org/services"
|
||||||
|
desc: >
|
||||||
|
Migrations must not import application services. Application services will reference application
|
||||||
|
models which will use the most recent schema for Forgejo, while migrations will be operating against the
|
||||||
|
database schema that existed when they were authored.
|
||||||
|
allow:
|
||||||
|
- "forgejo.org/models/db"
|
||||||
|
- "forgejo.org/models/gitea_migrations/base"
|
||||||
|
- "forgejo.org/models/gitea_migrations/test"
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
|
@ -204,9 +224,6 @@ linters:
|
||||||
- path: models/dbfs/dbfile.go
|
- path: models/dbfs/dbfile.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
- path: models/forgefed/federationhost_repository.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: models/forgejo_migrations_legacy/v32.go
|
- path: models/forgejo_migrations_legacy/v32.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
|
|
@ -264,9 +281,6 @@ linters:
|
||||||
- path: models/repo/repo.go
|
- path: models/repo/repo.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
- path: models/user/user_repository.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: modules/git/commit.go
|
- path: modules/git/commit.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
|
|
@ -333,21 +347,6 @@ linters:
|
||||||
- path: services/actions/trust.go
|
- path: services/actions/trust.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
- path: services/auth/basic.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/httpsign.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/oauth2.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/reverseproxy.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/auth/session.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/contexttest/context_tests.go
|
- path: services/contexttest/context_tests.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
|
|
@ -360,9 +359,6 @@ linters:
|
||||||
- path: routers/api/packages/conan/auth.go
|
- path: routers/api/packages/conan/auth.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
- path: services/federation/signature_service.go
|
|
||||||
linters:
|
|
||||||
- nilnil
|
|
||||||
- path: services/issue/commit.go
|
- path: services/issue/commit.go
|
||||||
linters:
|
linters:
|
||||||
- nilnil
|
- nilnil
|
||||||
|
|
|
||||||
17
.mockery.yml
Normal file
17
.mockery.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
formatter: gofmt
|
||||||
|
template: testify
|
||||||
|
packages:
|
||||||
|
forgejo.org/modules/nosql:
|
||||||
|
config:
|
||||||
|
filename: mocks.go # make mocks public so that external packages can use
|
||||||
|
forgejo.org/services/auth/method:
|
||||||
|
forgejo.org/services/authz:
|
||||||
|
config:
|
||||||
|
filename: authorization_reducer_mock.go # make mocks public so that external packages can use
|
||||||
|
code.forgejo.org/go-chi/cache:
|
||||||
|
interfaces:
|
||||||
|
Cache:
|
||||||
|
config:
|
||||||
|
pkgname: cache
|
||||||
|
dir: modules/cache
|
||||||
|
filename: mocks.go # make mocks public, not `_test.go`, so that external packages can mock caching
|
||||||
|
|
@ -1 +1 @@
|
||||||
24.14.1
|
24.15.0
|
||||||
|
|
@ -7,6 +7,7 @@ branch-from-version: 'v%[1]d.%[2]d/forgejo'
|
||||||
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
||||||
supported-release-count: 3
|
supported-release-count: 3
|
||||||
branch-known:
|
branch-known:
|
||||||
|
# replace with v15 when v11 becomes EOL
|
||||||
- 'v11.0/forgejo'
|
- 'v11.0/forgejo'
|
||||||
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
||||||
render-header: |
|
render-header: |
|
||||||
|
|
|
||||||
11
.semgrep/config/logic.yaml
Normal file
11
.semgrep/config/logic.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
rules:
|
||||||
|
- id: forgejo-logic-suspicious-OwnerID-check
|
||||||
|
pattern: |-
|
||||||
|
$X.OwnerID > 0
|
||||||
|
languages:
|
||||||
|
- go
|
||||||
|
severity: ERROR
|
||||||
|
message: >
|
||||||
|
Many resources like comments or runners cannot only be owned by regular users, which have positive IDs, but also
|
||||||
|
by predefined system users like Ghost or Forgejo Actions that have negative IDs. In those cases, ownership checks
|
||||||
|
should only exclude 0: `OwnerID != 0`.
|
||||||
35
.semgrep/tests/logic.go
Normal file
35
.semgrep/tests/logic.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import "xorm.io/builder"
|
||||||
|
|
||||||
|
type FindRunJobOptions struct {
|
||||||
|
RepoID int64
|
||||||
|
OwnerID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindRunJobOptions) Bad() builder.Cond {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if opts.RepoID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
|
}
|
||||||
|
// ruleid:forgejo-logic-suspicious-OwnerID-check
|
||||||
|
if opts.OwnerID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindRunJobOptions) Good() builder.Cond {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if opts.RepoID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
|
}
|
||||||
|
// ok:forgejo-logic-suspicious-OwnerID-check
|
||||||
|
if opts.OwnerID != 0 {
|
||||||
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
24
Makefile
24
Makefile
|
|
@ -39,16 +39,16 @@ XGO_VERSION := go-1.21.x
|
||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 # renovate: datasource=go
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 # renovate: datasource=go
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 # renovate: datasource=go
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
|
||||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses/v2@v2.0.1 # renovate: datasource=go
|
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses/v2@v2.0.1 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.43.0 # renovate: datasource=go
|
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.45.0 # renovate: datasource=go
|
||||||
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
||||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
RENOVATE_NPM_PACKAGE ?= renovate@43.170.20 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||||
RENOVATE_NPM_PACKAGE ?= renovate@43.86.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v3@v3.7.0 # renovate: datasource=go
|
||||||
|
|
||||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||||
|
|
@ -245,7 +245,7 @@ help:
|
||||||
@echo " - generate-license update license files"
|
@echo " - generate-license update license files"
|
||||||
@echo " - generate-gitignore update gitignore files"
|
@echo " - generate-gitignore update gitignore files"
|
||||||
@echo " - generate-manpage generate manpage"
|
@echo " - generate-manpage generate manpage"
|
||||||
@echo " - generate-gomock generate gomock files"
|
@echo " - generate-mockery generate mockery files"
|
||||||
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
||||||
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
||||||
@echo " - generate-swagger generate the swagger spec from code comments"
|
@echo " - generate-swagger generate the swagger spec from code comments"
|
||||||
|
|
@ -562,12 +562,12 @@ test: test-frontend test-backend
|
||||||
.PHONY: test-backend
|
.PHONY: test-backend
|
||||||
test-backend: | compute-go-test-packages
|
test-backend: | compute-go-test-packages
|
||||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
.PHONY: test-remote-cacher
|
.PHONY: test-remote-cacher
|
||||||
test-remote-cacher:
|
test-remote-cacher:
|
||||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
||||||
|
|
||||||
.PHONY: test-frontend
|
.PHONY: test-frontend
|
||||||
test-frontend: node_modules
|
test-frontend: node_modules
|
||||||
|
|
@ -592,7 +592,7 @@ test-check:
|
||||||
.PHONY: test\#%
|
.PHONY: test\#%
|
||||||
test\#%: | compute-go-test-packages
|
test\#%: | compute-go-test-packages
|
||||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||||
|
|
||||||
coverage-merge:
|
coverage-merge:
|
||||||
rm -fr coverage/merged ; mkdir -p coverage/merged
|
rm -fr coverage/merged ; mkdir -p coverage/merged
|
||||||
|
|
@ -968,8 +968,8 @@ deps-tools:
|
||||||
$(GO) install $(XGO_PACKAGE)
|
$(GO) install $(XGO_PACKAGE)
|
||||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||||
$(GO) install $(GOMOCK_PACKAGE)
|
|
||||||
$(GO) install $(ERRORTYPE_PACKAGE)
|
$(GO) install $(ERRORTYPE_PACKAGE)
|
||||||
|
$(GO) install $(MOCKERY_PACKAGE)
|
||||||
|
|
||||||
node_modules: package-lock.json
|
node_modules: package-lock.json
|
||||||
npm install --no-save
|
npm install --no-save
|
||||||
|
|
@ -1024,9 +1024,9 @@ generate-license:
|
||||||
generate-gitignore:
|
generate-gitignore:
|
||||||
$(GO) run build/generate-gitignores.go
|
$(GO) run build/generate-gitignores.go
|
||||||
|
|
||||||
.PHONY: generate-gomock
|
.PHONY: generate-mockery
|
||||||
generate-gomock:
|
generate-mockery:
|
||||||
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
$(GO) run $(MOCKERY_PACKAGE)
|
||||||
|
|
||||||
.PHONY: generate-images
|
.PHONY: generate-images
|
||||||
generate-images: | node_modules
|
generate-images: | node_modules
|
||||||
|
|
|
||||||
57
assets/go-licenses.json
generated
57
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -6,32 +6,12 @@ translation_meta.test
|
||||||
# this also gets instantiated as a Messenger once
|
# this also gets instantiated as a Messenger once
|
||||||
repo.migrate.migrating_failed.error
|
repo.migrate.migrating_failed.error
|
||||||
|
|
||||||
# models/system/notice.go: func (n *Notice) TrStr() string
|
|
||||||
admin.notices.type_1
|
|
||||||
admin.notices.type_2
|
|
||||||
|
|
||||||
# modules/setting/ui.go
|
# modules/setting/ui.go
|
||||||
themes.names.
|
themes.names.
|
||||||
|
|
||||||
# services/context/context.go
|
# services/context/context.go
|
||||||
relativetime.
|
relativetime.
|
||||||
|
|
||||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
|
||||||
repo.issues.close
|
|
||||||
repo.pulls.close
|
|
||||||
|
|
||||||
# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr
|
|
||||||
repo.issues.ref_closing_from
|
|
||||||
repo.issues.ref_issue_from
|
|
||||||
repo.issues.ref_pull_from
|
|
||||||
repo.issues.ref_reopening_from
|
|
||||||
|
|
||||||
# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type)
|
|
||||||
projects.
|
|
||||||
projects.type-1.display_name
|
|
||||||
projects.type-2.display_name
|
|
||||||
projects.type-3.display_name
|
|
||||||
|
|
||||||
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
||||||
# tests/integration/repo_archive_text_test.go
|
# tests/integration/repo_archive_text_test.go
|
||||||
repo.settings.
|
repo.settings.
|
||||||
|
|
|
||||||
|
|
@ -37,144 +37,172 @@ func HandleGoFile(handler llu.Handler, fname string, src any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Inspect(node, func(n ast.Node) bool {
|
ast.Inspect(node, func(n ast.Node) bool {
|
||||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
return HandleGoNode(handler, fset, fname, n)
|
||||||
|
})
|
||||||
|
|
||||||
switch n2 := n.(type) {
|
return nil
|
||||||
case *ast.CallExpr:
|
}
|
||||||
if len(n2.Args) == 0 {
|
|
||||||
return true
|
func HandleGoNode(handler llu.Handler, fset *token.FileSet, fname string, n ast.Node) bool {
|
||||||
|
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||||
|
|
||||||
|
switch n2 := n.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if len(n2.Args) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotUnexpectedInvoke *int
|
||||||
|
|
||||||
|
for _, argNum := range ltf {
|
||||||
|
if len(n2.Args) <= int(argNum) {
|
||||||
|
argc := len(n2.Args)
|
||||||
|
gotUnexpectedInvoke = &argc
|
||||||
|
} else {
|
||||||
|
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||||
}
|
}
|
||||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
}
|
||||||
if !ok {
|
|
||||||
|
if gotUnexpectedInvoke != nil {
|
||||||
|
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||||
|
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||||
|
} else if strings.Contains(fname, "models/asymkey/") {
|
||||||
|
lluAsymKey.HandleCompositeErrorReason(handler, fset, n2)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKeyWeak"); matchInsPrefix != nil {
|
||||||
|
results := n2.Type.Results.List
|
||||||
|
if len(results) != 1 {
|
||||||
|
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||||
if !ok {
|
// search for return stmts
|
||||||
return true
|
// TODO: what about nested functions?
|
||||||
}
|
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||||
|
for _, res := range ret.Results {
|
||||||
var gotUnexpectedInvoke *int
|
ast.Inspect(res, func(n ast.Node) bool {
|
||||||
|
if expr, ok := n.(ast.Expr); ok {
|
||||||
for _, argNum := range ltf {
|
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||||
if len(n2.Args) <= int(argNum) {
|
}
|
||||||
argc := len(n2.Args)
|
return true
|
||||||
gotUnexpectedInvoke = &argc
|
})
|
||||||
} else {
|
}
|
||||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey"); matchInsPrefix != nil {
|
||||||
|
results := n2.Type.Results.List
|
||||||
|
if len(results) != 1 {
|
||||||
|
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if gotUnexpectedInvoke != nil {
|
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
// search for return stmts
|
||||||
}
|
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||||
|
for _, res := range ret.Results {
|
||||||
case *ast.CompositeLit:
|
handler.HandleGoTrArgument(fset, res, *matchInsPrefix)
|
||||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
}
|
||||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
return false
|
||||||
} else if strings.Contains(fname, "models/asymkey/") {
|
} else if _, ok := n.(*ast.FuncDecl); ok {
|
||||||
lluAsymKey.HandleCompositeErrorReason(handler, fset, n2)
|
ast.Inspect(n, func(n2 ast.Node) bool {
|
||||||
}
|
return HandleGoNode(handler, fset, fname, n2)
|
||||||
|
})
|
||||||
case *ast.FuncDecl:
|
// don't search inside nested functions for return stmts
|
||||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
return false
|
||||||
if matchInsPrefix != nil {
|
|
||||||
results := n2.Type.Results.List
|
|
||||||
if len(results) != 1 {
|
|
||||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||||
// search for return stmts
|
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||||
// TODO: what about nested functions?
|
}
|
||||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
return true
|
||||||
for _, res := range ret.Results {
|
case *ast.GenDecl:
|
||||||
ast.Inspect(res, func(n ast.Node) bool {
|
switch n2.Tok {
|
||||||
if expr, ok := n.(ast.Expr); ok {
|
case token.CONST, token.VAR:
|
||||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||||
}
|
if matchInsPrefix == nil {
|
||||||
return true
|
return true
|
||||||
})
|
}
|
||||||
}
|
for _, spec := range n2.Specs {
|
||||||
|
// interpret all contained strings as message IDs
|
||||||
|
ast.Inspect(spec, func(n ast.Node) bool {
|
||||||
|
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||||
|
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
case token.TYPE:
|
||||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||||
}
|
// entries can have tags.
|
||||||
return true
|
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||||
case *ast.GenDecl:
|
// Problem: we don't know which structs are forms, actually.
|
||||||
switch n2.Tok {
|
|
||||||
case token.CONST, token.VAR:
|
|
||||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
|
||||||
if matchInsPrefix == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, spec := range n2.Specs {
|
|
||||||
// interpret all contained strings as message IDs
|
|
||||||
ast.Inspect(spec, func(n ast.Node) bool {
|
|
||||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
|
||||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
case token.TYPE:
|
for _, spec := range n2.Specs {
|
||||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
tspec := spec.(*ast.TypeSpec)
|
||||||
// entries can have tags.
|
structNode, ok := tspec.Type.(*ast.StructType)
|
||||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||||
// Problem: we don't know which structs are forms, actually.
|
(tspec.Doc != nil &&
|
||||||
|
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||||
for _, spec := range n2.Specs {
|
return c.Text == "// swagger:model"
|
||||||
tspec := spec.(*ast.TypeSpec)
|
}))) {
|
||||||
structNode, ok := tspec.Type.(*ast.StructType)
|
continue
|
||||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
}
|
||||||
(tspec.Doc != nil &&
|
for _, field := range structNode.Fields.List {
|
||||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
if field.Names == nil {
|
||||||
return c.Text == "// swagger:model"
|
|
||||||
}))) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, field := range structNode.Fields.List {
|
if len(field.Names) != 1 {
|
||||||
if field.Names == nil {
|
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
if len(field.Names) != 1 {
|
|
||||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
msgidPos := field.Names[0].NamePos
|
|
||||||
msgid := "form." + field.Names[0].Name
|
|
||||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
|
||||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
|
||||||
if err != nil {
|
|
||||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tag := reflect.StructTag(rawTag)
|
|
||||||
if tag.Get("form") == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tmp := tag.Get("locale")
|
|
||||||
if len(tmp) != 0 {
|
|
||||||
msgidPos = field.Tag.ValuePos
|
|
||||||
msgid = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
|
||||||
}
|
}
|
||||||
|
msgidPos := field.Names[0].NamePos
|
||||||
|
msgid := "form." + field.Names[0].Name
|
||||||
|
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||||
|
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||||
|
if err != nil {
|
||||||
|
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tag := reflect.StructTag(rawTag)
|
||||||
|
if tag.Get("form") == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmp := tag.Get("locale")
|
||||||
|
if len(tmp) != 0 {
|
||||||
|
msgidPos = field.Tag.ValuePos
|
||||||
|
msgid = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -44,12 +45,57 @@ type StringTrie interface {
|
||||||
|
|
||||||
type StringTrieMap map[string]StringTrie
|
type StringTrieMap map[string]StringTrie
|
||||||
|
|
||||||
|
func printfPatternToRegex(key string) (string, bool) {
|
||||||
|
parts := strings.Split(key, "%")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return key, false
|
||||||
|
}
|
||||||
|
var pattern strings.Builder
|
||||||
|
pattern.WriteString("^")
|
||||||
|
pattern.WriteString(parts[0])
|
||||||
|
skip := false
|
||||||
|
for _, part := range parts[1:] {
|
||||||
|
if skip {
|
||||||
|
skip = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(part) == 0 {
|
||||||
|
// "%%"
|
||||||
|
pattern.WriteString("%")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch part[0] {
|
||||||
|
case 'd':
|
||||||
|
pattern.WriteString("[0-9]+")
|
||||||
|
default:
|
||||||
|
pattern.WriteString("[A-Za-z0-9]*")
|
||||||
|
}
|
||||||
|
pattern.WriteString(part[1:])
|
||||||
|
}
|
||||||
|
pattern.WriteString("$")
|
||||||
|
return pattern.String(), true
|
||||||
|
}
|
||||||
|
|
||||||
func (m StringTrieMap) Matches(key []string) bool {
|
func (m StringTrieMap) Matches(key []string) bool {
|
||||||
if len(key) == 0 || m == nil {
|
if len(key) == 0 || m == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
value, ok := m[key[0]]
|
value, ok := m[key[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
for altKey, value := range m {
|
||||||
|
// TODO: cache mapping $printfFormatString -> $regexpCompileOutput
|
||||||
|
pattern, found := printfPatternToRegex(altKey)
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matched, err := regexp.MatchString(pattern, key[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unable to compile regexp '%s': %s", pattern, err.Error()))
|
||||||
|
}
|
||||||
|
if matched && (value == nil || value.Matches(key[1:])) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
|
@ -101,7 +147,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if linePrefix, found := strings.CutSuffix(line, "."); found {
|
if linePrefix, found := strings.CutSuffix(line, "."); found || strings.Contains(line, "%") {
|
||||||
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
||||||
} else {
|
} else {
|
||||||
if !chkMsgid(line) {
|
if !chkMsgid(line) {
|
||||||
|
|
@ -145,9 +191,14 @@ func Usage() {
|
||||||
|
|
||||||
fmt.Fprintf(outp, "\nSpecial Go doc comments:\n")
|
fmt.Fprintf(outp, "\nSpecial Go doc comments:\n")
|
||||||
for _, i := range []string{
|
for _, i := range []string{
|
||||||
|
"//llu:returnsTrKeyWeak",
|
||||||
|
"\tcan be used in front of functions to indicate",
|
||||||
|
"\tthat the function returns message IDs (allows nesting inside complicated function calls)",
|
||||||
|
"\tWARNING: this currently doesn't support nested functions properly",
|
||||||
|
"",
|
||||||
"//llu:returnsTrKey",
|
"//llu:returnsTrKey",
|
||||||
"\tcan be used in front of functions to indicate",
|
"\tcan be used in front of functions to indicate",
|
||||||
"\tthat the function returns message IDs",
|
"\tthat the function returns message IDs (doesn't allow nesting inside complicated function calls)",
|
||||||
"\tWARNING: this currently doesn't support nested functions properly",
|
"\tWARNING: this currently doesn't support nested functions properly",
|
||||||
"",
|
"",
|
||||||
"//llu:returnsTrKeySuffix prefix.",
|
"//llu:returnsTrKeySuffix prefix.",
|
||||||
|
|
@ -260,6 +311,10 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := llu.Handler{
|
handler := llu.Handler{
|
||||||
|
OnMsgidPattern: func(fset *token.FileSet, pos token.Pos, msgidPattern string) {
|
||||||
|
msgidPatternSplit := strings.Split(msgidPattern, ".")
|
||||||
|
allowedMaskedPrefixes.Insert(msgidPatternSplit)
|
||||||
|
},
|
||||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
||||||
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
||||||
if !truncated {
|
if !truncated {
|
||||||
|
|
@ -270,6 +325,10 @@ func main() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||||
|
if strings.Contains(msgid, "%") {
|
||||||
|
fmt.Printf("%s:\tunexpected msgid pattern: %s\n", fset.Position(pos).String(), msgid)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !msgids.Contains(msgid) {
|
if !msgids.Contains(msgid) {
|
||||||
if weak && allowWeakMissingMsgids {
|
if weak && allowWeakMissingMsgids {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,14 @@ func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.Basic
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
switch n := n.(type) {
|
||||||
handler.HandleGoTrBasicLit(fset, argLit, prefix)
|
case *ast.BasicLit:
|
||||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
handler.HandleGoTrBasicLit(fset, n, prefix)
|
||||||
if argBinExpr.Op != token.ADD {
|
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
if n.Op != token.ADD {
|
||||||
// pass
|
// pass
|
||||||
} else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
} else if argLit, ok := n.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||||
// extract string content
|
// extract string content
|
||||||
arg, err := strconv.Unquote(argLit.Value)
|
arg, err := strconv.Unquote(argLit.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -53,6 +55,39 @@ func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefi
|
||||||
}
|
}
|
||||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if selExpr, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||||
|
if xIdent, xok := selExpr.X.(*ast.Ident); !xok || xIdent.Name != "fmt" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selExpr.Sel.Name != "Sprintf" {
|
||||||
|
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function encountered: %s", selExpr.Sel.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(n.Args) == 0 {
|
||||||
|
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function invocation (no arguments) of '%s'", selExpr.Sel.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if argLit, ok := n.Args[0].(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||||
|
// extract string content
|
||||||
|
arg, err := strconv.Unquote(argLit.Value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(arg, " ") {
|
||||||
|
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf(
|
||||||
|
"formatting function invocation of '%s' with weird msgid format string: %s",
|
||||||
|
selExpr.Sel.Name,
|
||||||
|
arg,
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// found interesting strings
|
||||||
|
handler.OnMsgidPattern(fset, argLit.ValuePos, prefix+arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,13 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
||||||
|
|
||||||
case tmplParser.NodeField:
|
case tmplParser.NodeField:
|
||||||
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
||||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
if len(nodeField.Ident) != 2 || nodeField.Ident[0] != "locale" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resolvedPos := fset.PositionFor(token.Pos(nodeCommand.Pos), false)
|
||||||
|
if !strings.Contains(resolvedPos.Filename, "templates/mail/") {
|
||||||
|
handler.OnWarning(fset, token.Pos(nodeCommand.Pos), "encountered unexpected .locale usage")
|
||||||
|
}
|
||||||
funcname = nodeField.Ident[1]
|
funcname = nodeField.Ident[1]
|
||||||
|
|
||||||
case tmplParser.NodeVariable:
|
case tmplParser.NodeVariable:
|
||||||
|
|
@ -146,16 +150,12 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
||||||
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
||||||
} else {
|
} else {
|
||||||
if nodeIdent.Ident == "printf" {
|
if nodeIdent.Ident == "printf" {
|
||||||
parts := strings.SplitN(msgidPrefix, "%", 2)
|
// found interesting strings
|
||||||
if len(parts) != 2 {
|
if !(strings.HasSuffix(msgidPrefix, ".%s") && strings.Count(msgidPrefix, "%") == 1) {
|
||||||
handler.OnWarning(
|
handler.OnMsgidPattern(fset, stringPos, msgidPrefix)
|
||||||
fset,
|
|
||||||
stringPos,
|
|
||||||
fmt.Sprintf("unsupported invocation of locate function (format string doesn't match \"prefix%%smth\" pattern): %s", nodeString.String()),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msgidPrefix = parts[0]
|
msgidPrefix = strings.TrimSuffix(msgidPrefix, "%s")
|
||||||
}
|
}
|
||||||
|
|
||||||
msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix)
|
msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ func InitLocaleTrFunctions() map[string][]uint {
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool)
|
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool)
|
||||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||||
|
OnMsgidPattern func(fset *token.FileSet, pos token.Pos, msgidPattern string)
|
||||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||||
LocaleTrFunctions map[string][]uint
|
LocaleTrFunctions map[string][]uint
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ func subcmdRegenerate() *cli.Command {
|
||||||
Name: "regenerate",
|
Name: "regenerate",
|
||||||
Usage: "Regenerate specific files",
|
Usage: "Regenerate specific files",
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
microcmdRegenHooks,
|
|
||||||
microcmdRegenKeys,
|
microcmdRegenKeys,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,36 +7,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
asymkey_model "forgejo.org/models/asymkey"
|
asymkey_model "forgejo.org/models/asymkey"
|
||||||
"forgejo.org/modules/graceful"
|
|
||||||
repo_service "forgejo.org/services/repository"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var microcmdRegenKeys = &cli.Command{
|
||||||
microcmdRegenHooks = &cli.Command{
|
Name: "keys",
|
||||||
Name: "hooks",
|
Usage: "Regenerate authorized_keys file",
|
||||||
Usage: "Regenerate git-hooks",
|
Before: noDanglingArgs,
|
||||||
Before: noDanglingArgs,
|
Action: runRegenerateKeys,
|
||||||
Action: runRegenerateHooks,
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdRegenKeys = &cli.Command{
|
|
||||||
Name: "keys",
|
|
||||||
Usage: "Regenerate authorized_keys file",
|
|
||||||
Before: noDanglingArgs,
|
|
||||||
Action: runRegenerateKeys,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func runRegenerateHooks(ctx context.Context, c *cli.Command) error {
|
|
||||||
ctx, cancel := installSignals(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ func subcmdUser() *cli.Command {
|
||||||
microcmdUserChangePassword(),
|
microcmdUserChangePassword(),
|
||||||
microcmdUserDelete(),
|
microcmdUserDelete(),
|
||||||
microcmdUserGenerateAccessToken(),
|
microcmdUserGenerateAccessToken(),
|
||||||
|
microcmdUserCreateAuthorizedIntegration(),
|
||||||
microcmdUserMustChangePassword(),
|
microcmdUserMustChangePassword(),
|
||||||
microcmdUserResetMFA(),
|
microcmdUserResetMFA(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
269
cmd/admin_user_generate_authorized_integration.go
Normal file
269
cmd/admin_user_generate_authorized_integration.go
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/models/repo"
|
||||||
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/json"
|
||||||
|
"forgejo.org/services/authz"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func microcmdUserCreateAuthorizedIntegration() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "create-authorized-integration",
|
||||||
|
Description: `Creates an authorized integration. Authorized integrations allow Forgejo to
|
||||||
|
receive JWTs from external sources, validate their claims against
|
||||||
|
user-defined rules, and grant access to Forgejo's API on behalf of a user.
|
||||||
|
|
||||||
|
The issuer may be set to "urn:forgejo:authorized-integrations:actions"
|
||||||
|
to support JWTs from the local instance's Forgejo Actions, utilizing the
|
||||||
|
enable-openid-connect flag in a workflow.`,
|
||||||
|
|
||||||
|
// `--claim-in sub=v1,v2,v3` needs to be parsed as a single parameter so that we can comma-split the value into
|
||||||
|
// an array. To accomplish this, we disable urfave 's slice flag separator, which would cause this to be
|
||||||
|
// treated as "sub=v1", "v2=?", and "v3=?", resulting in an error of missing values.
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
|
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Username",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "Name of the authorized integration for later identification",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Usage: "Optional description for the authorized integration",
|
||||||
|
},
|
||||||
|
|
||||||
|
// JWT validation:
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "issuer",
|
||||||
|
Usage: `JWT issuer ('iss' claim), example: https://forgejo.example.org/api/actions`,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringMapFlag{
|
||||||
|
Name: "claim-eq",
|
||||||
|
Value: map[string]string{},
|
||||||
|
Usage: `Zero-or-more claim equality checks, formatted as claim=value, example: "actor=someuser"`,
|
||||||
|
},
|
||||||
|
&cli.StringMapFlag{
|
||||||
|
Name: "claim-in",
|
||||||
|
Value: map[string]string{},
|
||||||
|
Usage: `Zero-or-more claim equality in list checks, formatted as claim=value1,value2,... example: "actor=user1,user2"`,
|
||||||
|
},
|
||||||
|
&cli.StringMapFlag{
|
||||||
|
Name: "claim-glob",
|
||||||
|
Value: map[string]string{},
|
||||||
|
Usage: `Zero-or-more claim glob checks, formatted as claim=value, example: "sub=repo:forgejo/*:pull_request"`,
|
||||||
|
},
|
||||||
|
&cli.StringMapFlag{
|
||||||
|
Name: "claim-glob-in",
|
||||||
|
Value: map[string]string{},
|
||||||
|
Usage: `Zero-or-more claim glob in list checks, formatted as claim=va*ue1,va*ue2,... example: "sub=repo:*/*:pull_request,repo:*/*:refs:*"`,
|
||||||
|
},
|
||||||
|
// nested claim support omitted for now -- pretty complex for a CLI
|
||||||
|
|
||||||
|
// Permissions available on successful auth:
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "scope",
|
||||||
|
Value: []string{"all"},
|
||||||
|
Usage: `One-or-more scopes to apply to access token, examples: "all", "read:issue", "write:repository"`,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "repo",
|
||||||
|
Value: []string{"all"},
|
||||||
|
Usage: `Zero-or-more specific repositories that can be accessed, or "all" to allow access to all repositories, example: "owner1/repo1"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Before: noDanglingArgs,
|
||||||
|
Action: runCreateAuthorizedIntegration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCreateAuthorizedIntegration(ctx context.Context, c *cli.Command) error {
|
||||||
|
if !c.IsSet("username") {
|
||||||
|
return errors.New("you must provide a username to generate a token for")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &auth_model.AuthorizedIntegration{
|
||||||
|
UserID: user.ID,
|
||||||
|
Name: c.String("name"),
|
||||||
|
Description: c.String("description"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules []auth_model.ClaimRule
|
||||||
|
ai.Issuer = c.String("issuer")
|
||||||
|
for claim, value := range c.StringMap("claim-eq") {
|
||||||
|
rules = append(rules, auth_model.ClaimRule{
|
||||||
|
Claim: claim,
|
||||||
|
Comparison: auth_model.ClaimEqual,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for claim, value := range c.StringMap("claim-in") {
|
||||||
|
values := []string{}
|
||||||
|
for s := range strings.SplitSeq(value, ",") {
|
||||||
|
values = append(values, strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
rules = append(rules, auth_model.ClaimRule{
|
||||||
|
Claim: claim,
|
||||||
|
Comparison: auth_model.ClaimIn,
|
||||||
|
Values: values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for claim, value := range c.StringMap("claim-glob") {
|
||||||
|
rules = append(rules, auth_model.ClaimRule{
|
||||||
|
Claim: claim,
|
||||||
|
Comparison: auth_model.ClaimGlob,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for claim, value := range c.StringMap("claim-glob-in") {
|
||||||
|
values := []string{}
|
||||||
|
for s := range strings.SplitSeq(value, ",") {
|
||||||
|
values = append(values, strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
rules = append(rules, auth_model.ClaimRule{
|
||||||
|
Claim: claim,
|
||||||
|
Comparison: auth_model.ClaimGlobIn,
|
||||||
|
Values: values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ai.ClaimRules = &auth_model.ClaimRules{Rules: rules}
|
||||||
|
|
||||||
|
scopes := strings.Join(c.StringSlice("scope"), ",")
|
||||||
|
accessTokenScope, err := auth_model.AccessTokenScope(scopes).Normalize()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||||
|
}
|
||||||
|
ai.Scope = accessTokenScope
|
||||||
|
|
||||||
|
allRepos := false
|
||||||
|
repos := []*repo.Repository{}
|
||||||
|
for _, repoName := range c.StringSlice("repo") {
|
||||||
|
if repoName == "all" {
|
||||||
|
allRepos = true
|
||||||
|
} else {
|
||||||
|
split := strings.Split(repoName, "/")
|
||||||
|
if len(split) != 2 {
|
||||||
|
return fmt.Errorf("invalid repo name: %q", split)
|
||||||
|
}
|
||||||
|
owner := split[0]
|
||||||
|
name := split[1]
|
||||||
|
repo, err := repo.GetRepositoryByOwnerAndName(ctx, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repos = append(repos, repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ai.ResourceAllRepos = allRepos
|
||||||
|
|
||||||
|
rr := make([]*auth_model.AuthorizedIntegResourceRepo, len(repos))
|
||||||
|
for i := range repos {
|
||||||
|
rr[i] = &auth_model.AuthorizedIntegResourceRepo{RepoID: repos[i].ID}
|
||||||
|
}
|
||||||
|
if err := authz.ValidateAuthorizedIntegration(ai, rr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
if err := auth_model.InsertAuthorizedIntegration(ctx, ai); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !allRepos {
|
||||||
|
if err := auth_model.InsertAuthorizedIntegrationResourceRepos(ctx, ai.ID, rr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClaimRuleDescription struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Claim string `json:"claim"`
|
||||||
|
Comparison auth_model.ClaimComparison `json:"compare"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
Values []string `json:"values,omitempty"`
|
||||||
|
}
|
||||||
|
output := struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
Audience string `json:"audience"`
|
||||||
|
ClaimRules []ClaimRuleDescription `json:"claim_rules"`
|
||||||
|
}{
|
||||||
|
Message: "Authorized integration was successfully created.",
|
||||||
|
Name: ai.Name,
|
||||||
|
Description: ai.Description,
|
||||||
|
Issuer: ai.Issuer,
|
||||||
|
Audience: ai.Audience,
|
||||||
|
}
|
||||||
|
for _, cr := range ai.ClaimRules.Rules {
|
||||||
|
var description string
|
||||||
|
switch cr.Comparison {
|
||||||
|
case auth_model.ClaimEqual:
|
||||||
|
description = fmt.Sprintf("%q = %q", cr.Claim, cr.Value)
|
||||||
|
case auth_model.ClaimIn:
|
||||||
|
description = fmt.Sprintf("%q in %q", cr.Claim, cr.Values)
|
||||||
|
case auth_model.ClaimGlob:
|
||||||
|
description = fmt.Sprintf("%q matches %q", cr.Claim, cr.Value)
|
||||||
|
case auth_model.ClaimGlobIn:
|
||||||
|
description = fmt.Sprintf("%q matches in %q", cr.Claim, cr.Values)
|
||||||
|
}
|
||||||
|
output.ClaimRules = append(output.ClaimRules, ClaimRuleDescription{
|
||||||
|
Description: description,
|
||||||
|
Claim: cr.Claim,
|
||||||
|
Comparison: cr.Comparison,
|
||||||
|
Value: cr.Value,
|
||||||
|
Values: cr.Values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := json.Marshal(output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var indent bytes.Buffer
|
||||||
|
if err := json.Indent(&indent, raw, "", " "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stdout.Write(indent.Bytes())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -150,8 +150,8 @@ func runCert(ctx context.Context, c *cli.Command) error {
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := strings.Split(c.String("host"), ",")
|
hosts := strings.SplitSeq(c.String("host"), ",")
|
||||||
for _, h := range hosts {
|
for h := range hosts {
|
||||||
if ip := net.ParseIP(h); ip != nil {
|
if ip := net.ParseIP(h); ip != nil {
|
||||||
template.IPAddresses = append(template.IPAddresses, ip)
|
template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
42
cmd/dump.go
42
cmd/dump.go
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -83,11 +84,9 @@ func (o outputType) Join() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *outputType) Set(value string) error {
|
func (o *outputType) Set(value string) error {
|
||||||
for _, enum := range o.Enum {
|
if slices.Contains(o.Enum, value) {
|
||||||
if enum == value {
|
o.selected = value
|
||||||
o.selected = value
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("allowed values are %s", o.Join())
|
return fmt.Errorf("allowed values are %s", o.Join())
|
||||||
|
|
@ -113,7 +112,10 @@ func getArchiverByType(outType string) (archives.ArchiverAsync, error) {
|
||||||
var archiver archives.ArchiverAsync
|
var archiver archives.ArchiverAsync
|
||||||
switch outType {
|
switch outType {
|
||||||
case "zip":
|
case "zip":
|
||||||
archiver = archives.Zip{}
|
archiver = archives.Zip{
|
||||||
|
Compression: 8,
|
||||||
|
SelectiveCompression: false,
|
||||||
|
}
|
||||||
case "tar":
|
case "tar":
|
||||||
archiver = archives.Tar{}
|
archiver = archives.Tar{}
|
||||||
case "tar.sz":
|
case "tar.sz":
|
||||||
|
|
@ -250,8 +252,8 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
||||||
} else {
|
} else {
|
||||||
for _, suffix := range outputTypeEnum.Enum {
|
for _, suffix := range outputTypeEnum.Enum {
|
||||||
if strings.HasSuffix(fileName, "."+suffix) {
|
if before, ok := strings.CutSuffix(fileName, "."+suffix); ok {
|
||||||
fileName = strings.TrimSuffix(fileName, "."+suffix)
|
fileName = before
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -330,14 +332,12 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
|
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
|
||||||
|
|
||||||
if len(setting.CustomConf) > 0 {
|
if len(setting.CustomConf) > 0 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||||
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
|
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
|
||||||
fatal("Failed to include specified app.ini: %v", err)
|
fatal("Failed to include specified app.ini: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||||
|
|
@ -361,15 +361,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||||
log.Info("Skipping attachment data")
|
log.Info("Skipping attachment data")
|
||||||
} else {
|
} else {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||||
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
|
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fatal("Failed to dump attachments: %v", err)
|
fatal("Failed to dump attachments: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||||
|
|
@ -377,15 +375,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
} else if !setting.Packages.Enabled {
|
} else if !setting.Packages.Enabled {
|
||||||
log.Info("Package registry not enabled - skipping")
|
log.Info("Package registry not enabled - skipping")
|
||||||
} else {
|
} else {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||||
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
|
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fatal("Failed to dump packages: %v", err)
|
fatal("Failed to dump packages: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||||
|
|
@ -399,13 +395,11 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||||
}
|
}
|
||||||
if isExist {
|
if isExist {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||||
fatal("Failed to include log: %v", err)
|
fatal("Failed to include log: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,8 @@ func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
|
||||||
opts.PullRequests = true
|
opts.PullRequests = true
|
||||||
opts.ReleaseAssets = true
|
opts.ReleaseAssets = true
|
||||||
} else {
|
} else {
|
||||||
units := strings.Split(ctx.String("units"), ",")
|
units := strings.SplitSeq(ctx.String("units"), ",")
|
||||||
for _, unit := range units {
|
for unit := range units {
|
||||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||||
case "":
|
case "":
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -62,7 +63,12 @@ func runTestApp(app *cli.Command, args ...string) (runResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCliCmd(t *testing.T) {
|
func TestCliCmd(t *testing.T) {
|
||||||
defaultWorkPath := filepath.Dir(setting.AppPath)
|
path, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultWorkPath := filepath.Dir(path)
|
||||||
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
|
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
|
||||||
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -290,10 +290,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
||||||
Op: lfsVerb,
|
Op: lfsVerb,
|
||||||
UserID: results.UserID,
|
UserID: results.UserID,
|
||||||
}
|
}
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
|
|
||||||
// Sign and get the complete encoded token as a string using the secret
|
// Sign and get the complete encoded token as a string using the secret
|
||||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
tokenString, err := setting.LFS.SigningKey.JWT(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,8 +164,6 @@ func serveInstall(_ context.Context, ctx *cli.Command) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveInstalled(_ context.Context, ctx *cli.Command) error {
|
func serveInstalled(_ context.Context, ctx *cli.Command) error {
|
||||||
setting.InitCfgProvider(setting.CustomConf)
|
|
||||||
setting.LoadCommonSettings()
|
|
||||||
setting.MustInstalled()
|
setting.MustInstalled()
|
||||||
|
|
||||||
showWebStartupMessage("Prepare to run web server")
|
showWebStartupMessage("Prepare to run web server")
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ After=network.target
|
||||||
# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that
|
# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that
|
||||||
# LimitNOFILE=524288:524288
|
# LimitNOFILE=524288:524288
|
||||||
RestartSec=2s
|
RestartSec=2s
|
||||||
Type=simple
|
Type=notify
|
||||||
User=git
|
User=git
|
||||||
Group=git
|
Group=git
|
||||||
WorkingDirectory=/var/lib/forgejo/
|
WorkingDirectory=/var/lib/forgejo/
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,9 @@ RUN_USER = ; git
|
||||||
;LFS_START_SERVER = false
|
;LFS_START_SERVER = false
|
||||||
;;
|
;;
|
||||||
;;
|
;;
|
||||||
|
;; see JWT_* under [oauth2]
|
||||||
|
;LFS_JWT_SIGNING_ALGORITHM = HS256
|
||||||
|
;LFS_JWT_SIGNING_PRIVATE_KEY_FILE = jwt/lfs_private.pem
|
||||||
;; LFS authentication secret, change this yourself
|
;; LFS authentication secret, change this yourself
|
||||||
;LFS_JWT_SECRET =
|
;LFS_JWT_SECRET =
|
||||||
;;
|
;;
|
||||||
|
|
@ -457,7 +460,7 @@ INTERNAL_TOKEN =
|
||||||
;GLOBAL_TWO_FACTOR_REQUIREMENT = none
|
;GLOBAL_TWO_FACTOR_REQUIREMENT = none
|
||||||
;;
|
;;
|
||||||
;; Name of cookie used to store authentication information.
|
;; Name of cookie used to store authentication information.
|
||||||
;COOKIE_REMEMBER_NAME = gitea_incredible
|
;COOKIE_REMEMBER_NAME = persistent
|
||||||
;;
|
;;
|
||||||
;; Reverse proxy authentication header name of user name, email, and full name
|
;; Reverse proxy authentication header name of user name, email, and full name
|
||||||
;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
|
;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
|
||||||
|
|
@ -544,6 +547,7 @@ ENABLED = true
|
||||||
;; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH.
|
;; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH.
|
||||||
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to RS256, RS384, RS512, ES256, ES384 or ES512.
|
;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to RS256, RS384, RS512, ES256, ES384 or ES512.
|
||||||
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
|
||||||
|
;; XXX jwt/ is a misnomer, it should rather be oauth2/, because we use many JWTs
|
||||||
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem
|
||||||
;;
|
;;
|
||||||
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret
|
||||||
|
|
@ -1895,7 +1899,7 @@ LEVEL = Info
|
||||||
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
|
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
|
||||||
;;
|
;;
|
||||||
;; Session cookie name
|
;; Session cookie name
|
||||||
;COOKIE_NAME = i_like_gitea
|
;COOKIE_NAME = session
|
||||||
;;
|
;;
|
||||||
;; 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 =
|
||||||
|
|
@ -2790,7 +2794,7 @@ LEVEL = Info
|
||||||
;; server and database workload due to more complex database queries and more frequent server task querying; this
|
;; server and database workload due to more complex database queries and more frequent server task querying; this
|
||||||
;; feature can be disabled to reduce performance impact
|
;; feature can be disabled to reduce performance impact
|
||||||
;CONCURRENCY_GROUP_QUEUE_ENABLED = true
|
;CONCURRENCY_GROUP_QUEUE_ENABLED = true
|
||||||
;; Algorithm used to sign ID tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA.
|
;; Algorithm used to sign ID tokens. Valid values: RS256, RS384, RS512, ES256, ES384, ES512, EdDSA.
|
||||||
;; RS256 will ensure compatibility with all relying parties.
|
;; RS256 will ensure compatibility with all relying parties.
|
||||||
;; If a different algorithm is chosen, verify that relying parties of interest support the signing algorithm.
|
;; If a different algorithm is chosen, verify that relying parties of interest support the signing algorithm.
|
||||||
;ID_TOKEN_SIGNING_ALGORITHM = RS256
|
;ID_TOKEN_SIGNING_ALGORITHM = RS256
|
||||||
|
|
@ -2819,3 +2823,30 @@ LEVEL = Info
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; storage type
|
;; storage type
|
||||||
;STORAGE_TYPE = local
|
;STORAGE_TYPE = local
|
||||||
|
|
||||||
|
;; Authorized integrations are a capability for users to define external systems which can generate JWTs that Forgejo
|
||||||
|
;; will trust in order to perform API access on behalf of that user. While validating a JWT from an external system,
|
||||||
|
;; Forgejo makes outgoing HTTP requests to the JWT issuer.
|
||||||
|
; [authorized_integration]
|
||||||
|
;; Timeout for HTTP requests to remote servers. Default is 10 seconds.
|
||||||
|
;REQUEST_TIMEOUT = 10s
|
||||||
|
;
|
||||||
|
;; Allowed domains for authorized integrations. Default is blank which means all domains will be allowed (except local
|
||||||
|
;; networks, see ALLOW_LOCALNETWORKS).
|
||||||
|
;; Multiple domains can be separated by commas.
|
||||||
|
;; Wildcards are supported: "github.com, *.github.com"
|
||||||
|
;ALLOWED_DOMAINS =
|
||||||
|
;
|
||||||
|
;; Blocklist for authorized integrations, default is blank.
|
||||||
|
;; Multiple domains can be separated by commas.
|
||||||
|
;; Wildcards are supported: "github.com, *.github.com"
|
||||||
|
;BLOCKED_DOMAINS =
|
||||||
|
;
|
||||||
|
;; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291.
|
||||||
|
;; Default is false.
|
||||||
|
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
|
||||||
|
;ALLOW_LOCALNETWORKS = false
|
||||||
|
;
|
||||||
|
;; Remote requests are cached after being received for the cache time-to-live (TTL). Default is 10 minutes.
|
||||||
|
;; Caching uses the configured adapter in the [cache] config section.
|
||||||
|
;CACHE_TTL = 10m
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
# And place the original in /usr/lib/gitea with working files in /data/gitea
|
# And place the original in /usr/lib/gitea with working files in /data/gitea
|
||||||
GITEA="/app/gitea/gitea"
|
GITEA="/app/gitea/gitea"
|
||||||
WORK_DIR="/var/lib/gitea"
|
WORK_DIR="/var/lib/gitea"
|
||||||
APP_INI="/etc/gitea/app.ini"
|
APP_INI="/var/lib/gitea/custom/conf/app.ini"
|
||||||
|
|
||||||
APP_INI_SET=""
|
APP_INI_SET=""
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1762977756,
|
"lastModified": 1777954456,
|
||||||
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
|
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
133
go.mod
133
go.mod
|
|
@ -1,8 +1,8 @@
|
||||||
module forgejo.org
|
module forgejo.org
|
||||||
|
|
||||||
go 1.25.0
|
go 1.26.0
|
||||||
|
|
||||||
toolchain go1.26.1
|
toolchain go1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.forgejo.org/f3/gof3/v3 v3.11.15
|
code.forgejo.org/f3/gof3/v3 v3.11.15
|
||||||
|
|
@ -11,27 +11,27 @@ require (
|
||||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||||
code.forgejo.org/forgejo/reply v1.0.2
|
code.forgejo.org/forgejo/reply v1.0.2
|
||||||
code.forgejo.org/forgejo/runner/v12 v12.7.3
|
code.forgejo.org/forgejo/runner/v12 v12.10.1
|
||||||
code.forgejo.org/go-chi/binding v1.0.1
|
code.forgejo.org/go-chi/binding v1.0.1
|
||||||
code.forgejo.org/go-chi/cache v1.0.1
|
code.forgejo.org/go-chi/cache v1.0.1
|
||||||
code.forgejo.org/go-chi/captcha v1.0.2
|
code.forgejo.org/go-chi/captcha v1.0.2
|
||||||
code.forgejo.org/go-chi/session v1.0.3
|
code.forgejo.org/go-chi/session v1.0.4
|
||||||
code.gitea.io/sdk/gitea v0.21.0
|
code.gitea.io/sdk/gitea v0.21.0
|
||||||
code.superseriousbusiness.org/exif-terminator v0.11.1
|
code.superseriousbusiness.org/exif-terminator v0.11.2
|
||||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||||
connectrpc.com/connect v1.19.1
|
connectrpc.com/connect v1.19.2
|
||||||
github.com/42wim/httpsig v1.2.3
|
github.com/42wim/httpsig v1.2.3
|
||||||
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||||
github.com/ProtonMail/go-crypto v1.4.1
|
github.com/ProtonMail/go-crypto v1.4.1
|
||||||
github.com/PuerkitoBio/goquery v1.11.0
|
github.com/PuerkitoBio/goquery v1.12.0
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
|
||||||
github.com/alecthomas/chroma/v2 v2.23.1
|
github.com/alecthomas/chroma/v2 v2.23.1
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||||
github.com/blevesearch/bleve/v2 v2.5.7
|
github.com/blevesearch/bleve/v2 v2.6.0
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||||
github.com/caddyserver/certmagic v0.24.0
|
github.com/caddyserver/certmagic v0.25.3
|
||||||
github.com/chi-middleware/proxy v1.1.1
|
github.com/chi-middleware/proxy v1.1.1
|
||||||
github.com/djherbis/buffer v1.2.0
|
github.com/djherbis/buffer v1.2.0
|
||||||
github.com/djherbis/nio/v3 v3.0.1
|
github.com/djherbis/nio/v3 v3.0.1
|
||||||
|
|
@ -41,45 +41,46 @@ require (
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
|
||||||
github.com/emersion/go-imap v1.2.1
|
github.com/emersion/go-imap v1.2.1
|
||||||
github.com/felixge/fgprof v0.9.5
|
github.com/felixge/fgprof v0.9.5
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
|
github.com/gdgvda/cron v0.7.0
|
||||||
github.com/gliderlabs/ssh v0.3.8
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc
|
||||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77
|
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77
|
||||||
github.com/go-chi/chi/v5 v5.2.5
|
github.com/go-chi/chi/v5 v5.2.5
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-co-op/gocron v1.37.0
|
github.com/go-co-op/gocron v1.37.0
|
||||||
github.com/go-enry/go-enry/v2 v2.9.5
|
github.com/go-enry/go-enry/v2 v2.9.6
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12
|
github.com/go-ldap/ldap/v3 v3.4.12
|
||||||
github.com/go-openapi/spec v0.22.3
|
github.com/go-openapi/spec v0.22.3
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
github.com/go-sql-driver/mysql v1.10.0
|
||||||
github.com/go-webauthn/webauthn v0.16.1
|
github.com/go-webauthn/webauthn v0.16.5
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/google/go-github/v81 v81.0.0
|
github.com/google/go-github/v81 v81.0.0
|
||||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8
|
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/feeds v1.2.0
|
github.com/gorilla/feeds v1.2.0
|
||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/hashicorp/go-version v1.8.0
|
github.com/hashicorp/go-version v1.8.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/huandu/xstrings v1.5.0
|
github.com/huandu/xstrings v1.5.0
|
||||||
github.com/inbucket/html2text v0.9.0
|
github.com/inbucket/html2text v1.0.0
|
||||||
github.com/jackc/pgx/v5 v5.9.1
|
github.com/jackc/pgx/v5 v5.9.2
|
||||||
github.com/jhillyerd/enmime/v2 v2.2.0
|
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
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.18.4
|
github.com/klauspost/compress v1.18.6
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11
|
github.com/klauspost/cpuid/v2 v2.3.0
|
||||||
github.com/markbates/goth v1.82.0
|
github.com/markbates/goth v1.82.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.21
|
||||||
github.com/mattn/go-sqlite3 v1.14.37
|
github.com/mattn/go-sqlite3 v1.14.44
|
||||||
github.com/meilisearch/meilisearch-go v0.36.0
|
github.com/meilisearch/meilisearch-go v0.36.2
|
||||||
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/minio/minio-go/v7 v7.0.99
|
github.com/minio/minio-go/v7 v7.1.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/olivere/elastic/v7 v7.0.32
|
github.com/olivere/elastic/v7 v7.0.32
|
||||||
|
|
@ -87,28 +88,26 @@ require (
|
||||||
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.21.1
|
github.com/prometheus/client_golang v1.21.1
|
||||||
github.com/redis/go-redis/v9 v9.17.3
|
github.com/redis/go-redis/v9 v9.19.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||||
github.com/sergi/go-diff v1.4.0
|
github.com/sergi/go-diff v1.4.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.15
|
||||||
github.com/urfave/cli/v3 v3.7.0
|
github.com/urfave/cli/v3 v3.8.0
|
||||||
github.com/valyala/fastjson v1.6.10
|
github.com/valyala/fastjson v1.6.10
|
||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.7.17
|
github.com/yuin/goldmark v1.8.2
|
||||||
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 v0.143.2
|
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||||
go.uber.org/mock v0.6.0
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
go.yaml.in/yaml/v3 v3.0.4
|
||||||
golang.org/x/crypto v0.49.0
|
golang.org/x/crypto v0.51.0
|
||||||
golang.org/x/image v0.37.0
|
golang.org/x/image v0.40.0
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.54.0
|
||||||
golang.org/x/oauth2 v0.36.0
|
golang.org/x/oauth2 v0.36.0
|
||||||
golang.org/x/sync v0.20.0
|
golang.org/x/sync v0.20.0
|
||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.44.0
|
||||||
golang.org/x/text v0.35.0
|
golang.org/x/text v0.37.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
|
|
@ -120,46 +119,46 @@ require (
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
||||||
filippo.io/edwards25519 v1.1.1 // indirect
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.14.5 // 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.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
github.com/bits-and-blooms/bitset v1.24.2 // indirect
|
||||||
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
|
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
|
||||||
github.com/blevesearch/geo v0.2.4 // indirect
|
github.com/blevesearch/geo v0.2.5 // indirect
|
||||||
github.com/blevesearch/go-faiss v1.0.26 // indirect
|
github.com/blevesearch/go-faiss v1.1.0 // indirect
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
|
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
|
||||||
github.com/blevesearch/segment v0.9.1 // indirect
|
github.com/blevesearch/segment v0.9.1 // indirect
|
||||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
github.com/blevesearch/vellum v1.2.0 // indirect
|
||||||
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
|
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
|
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||||
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
|
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||||
|
github.com/blevesearch/zapx/v17 v17.1.2 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||||
github.com/bodgit/windows v1.0.1 // indirect
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.3 // 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/cloudflare/circl v1.6.3 // indirect
|
github.com/cloudflare/circl v1.6.3 // 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
||||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
|
|
@ -167,15 +166,12 @@ require (
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
github.com/go-enry/go-oniguruma v1.2.1 // indirect
|
||||||
github.com/go-errors/errors v1.4.2 // indirect
|
github.com/go-errors/errors v1.4.2 // indirect
|
||||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
|
||||||
github.com/go-git/go-billy/v5 v5.8.0 // indirect
|
|
||||||
github.com/go-git/go-git/v5 v5.17.0 // indirect
|
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
||||||
|
|
@ -187,11 +183,11 @@ require (
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/go-webauthn/x v0.2.2 // indirect
|
github.com/go-webauthn/x v0.2.3 // indirect
|
||||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
|
@ -204,19 +200,17 @@ require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.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
|
||||||
github.com/lib/pq v1.11.2 // indirect
|
github.com/libdns/libdns v1.1.1 // indirect
|
||||||
github.com/libdns/libdns v1.0.0 // indirect
|
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/markbates/going v1.0.3 // indirect
|
github.com/markbates/going v1.0.3 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.17 // indirect
|
github.com/mattn/go-runewidth v0.0.17 // indirect
|
||||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||||
github.com/miekg/dns v1.1.63 // 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
|
||||||
|
|
@ -231,6 +225,7 @@ require (
|
||||||
github.com/olekukonko/ll v0.0.9 // indirect
|
github.com/olekukonko/ll v0.0.9 // indirect
|
||||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
github.com/olekukonko/tablewriter v1.0.7 // 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.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
|
@ -240,28 +235,28 @@ require (
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/rhysd/actionlint v1.7.10 // indirect
|
github.com/rhysd/actionlint v1.7.10 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
github.com/sirupsen/logrus v1.9.4 // 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/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/tinylib/msgp v1.6.1 // indirect
|
github.com/tinylib/msgp v1.6.4 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/assert v1.3.0 // indirect
|
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // 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.11.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.27.0 // indirect
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/mod v0.35.0 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.44.0 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
@ -274,4 +269,4 @@ replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-2024121
|
||||||
|
|
||||||
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
|
||||||
|
|
||||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.8
|
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.12
|
||||||
|
|
|
||||||
276
go.sum
276
go.sum
|
|
@ -30,8 +30,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
|
||||||
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
||||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||||
code.forgejo.org/forgejo/runner/v12 v12.7.3 h1:+thSawVfLeAZaWB6sYeUPvLj4lxYjCIDt/ktvkfX5Rs=
|
code.forgejo.org/forgejo/runner/v12 v12.10.1 h1:qRKjWItVDc2lEMl3jGlKyFpqgX4xHeOdEyl/irO/Nk8=
|
||||||
code.forgejo.org/forgejo/runner/v12 v12.7.3/go.mod h1:OO+Vy9Dww6WNV7GG/6VUWo/0WwXY+ASGlINmAfEA9Ws=
|
code.forgejo.org/forgejo/runner/v12 v12.10.1/go.mod h1:A51GyZJlril5cIpVMvOn3NqE8upOE6ePjwC6s31kRHk=
|
||||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
||||||
|
|
@ -40,25 +40,27 @@ code.forgejo.org/go-chi/cache v1.0.1 h1:w6IsDcPbeEnEYZn7M2HJe3/3/Ehtcw/72VjcVK7+
|
||||||
code.forgejo.org/go-chi/cache v1.0.1/go.mod h1:K3aQSyRIN4xiuqV1kanfQ6O4ToDpzDpY3bNOyGjFe3U=
|
code.forgejo.org/go-chi/cache v1.0.1/go.mod h1:K3aQSyRIN4xiuqV1kanfQ6O4ToDpzDpY3bNOyGjFe3U=
|
||||||
code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkAaqZGQ=
|
code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkAaqZGQ=
|
||||||
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
||||||
code.forgejo.org/go-chi/session v1.0.3 h1:ByJ9c/UC0AU57hxiGl53TXh+NdBOBwK/bhZ9jyadEwE=
|
code.forgejo.org/go-chi/session v1.0.4 h1:WQ1NaVxcCpxYwCliEGypKclZnOCjh3p1fk8XciJc62U=
|
||||||
code.forgejo.org/go-chi/session v1.0.3/go.mod h1:xzGtFrV/agCJoZCUhFDlqAr1he6BrAdqlaprKOB1W90=
|
code.forgejo.org/go-chi/session v1.0.4/go.mod h1:+sSTiomM5C8AUPtxZyTENIbcTz22kcVottKO0lnmDRk=
|
||||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8 h1:dsSKm2nus0NhHsqYxeuB3Gldk6TtlusD1CBGV6V1SS0=
|
code.forgejo.org/xorm/xorm v1.3.9-forgejo.12 h1:EodlU2MGu/EeAdUpEOe+X4cU/qlnJol1ucJ0sHUCM1o=
|
||||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8/go.mod h1:A7sFd3BFmRp20h6drSsCXgQRQdF8Vz8HuCSrzFS3m90=
|
code.forgejo.org/xorm/xorm v1.3.9-forgejo.12/go.mod h1:ozQINrM8b7uYFMBB/w19nUTTLcda3RKTQ8HspocVKdg=
|
||||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||||
code.superseriousbusiness.org/exif-terminator v0.11.1 h1:qnujLH4/Yk/CFtFMmtjozbdV6Ry5G3Q/E/mLlWm/gQI=
|
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||||
code.superseriousbusiness.org/exif-terminator v0.11.1/go.mod h1:/Z+3DHSrefCzzN5ePkGjVYKFErRimoeUf694Gz8Pn/Y=
|
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||||
|
code.superseriousbusiness.org/exif-terminator v0.11.2 h1:nkTdaghZb6I0oGYFhTLSALhA2ShgkPnYAJryE5IE9+0=
|
||||||
|
code.superseriousbusiness.org/exif-terminator v0.11.2/go.mod h1:/Z+3DHSrefCzzN5ePkGjVYKFErRimoeUf694Gz8Pn/Y=
|
||||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
||||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
|
||||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
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=
|
||||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
||||||
|
|
@ -73,10 +75,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
||||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
github.com/RoaringBitmap/roaring/v2 v2.14.5 h1:ckd0o545JqDPeVJDgeFoaM21eBixUnlWfYgjE5VnyWw=
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
github.com/RoaringBitmap/roaring/v2 v2.14.5/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 h1:tgjwQrDH5m6jIYB7kac5IQZmfUzQNseac/e3H4VoCNE=
|
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 h1:tgjwQrDH5m6jIYB7kac5IQZmfUzQNseac/e3H4VoCNE=
|
||||||
|
|
@ -101,47 +103,48 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/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=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0=
|
||||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
|
github.com/blevesearch/bleve/v2 v2.6.0 h1:Cyd3dd4q5tCbOV8MnKUVRUDYMHOir9xn12NZzXVSEd4=
|
||||||
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
|
github.com/blevesearch/bleve/v2 v2.6.0/go.mod h1:gLmI8lWgHgrIYf7UpUX7JISI1CaqC6VScu46mHThuAY=
|
||||||
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
|
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
|
||||||
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
|
||||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
|
||||||
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
|
github.com/blevesearch/go-faiss v1.1.0 h1:xM7Jc0ZUCv5lssG9Ohj3Jv0SdTpxcUABU1dDt9XVsc4=
|
||||||
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
github.com/blevesearch/go-faiss v1.1.0/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
|
||||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
|
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 h1:GlMzW08hcsM3DnLUxhyF/1PcDal1qtvvIuytuph5djw=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
|
github.com/blevesearch/scorch_segment_api/v2 v2.4.7/go.mod h1://IJ7tG3QCf0cWW/aVSXqy77tc1AvLu3fcJLYEvOAFs=
|
||||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||||
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
|
github.com/blevesearch/vellum v1.2.0 h1:xkDiOEsHc2t3Cp0NsNZZ36pvc130sCzcGKOPMzXe+e0=
|
||||||
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
|
github.com/blevesearch/vellum v1.2.0/go.mod h1:uEcfBJz7mAOf0Kvq6qoEKQQkLODBF46SINYNkZNae4k=
|
||||||
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
|
github.com/blevesearch/zapx/v11 v11.4.3 h1:PTZOO5loKpHC/x/GzmPZNa9cw7GZIQxd5qRjwij9tHY=
|
||||||
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
github.com/blevesearch/zapx/v11 v11.4.3/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||||
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
|
github.com/blevesearch/zapx/v12 v12.4.3 h1:eElXvAaAX4m04t//CGBQAtHNPA+Q6A1hHZVrN3LSFYo=
|
||||||
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
github.com/blevesearch/zapx/v12 v12.4.3/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||||
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
|
github.com/blevesearch/zapx/v13 v13.4.3 h1:qsdhRhaSpVnqDFlRiH9vG5+KJ+dE7KAW9WyZz/KXAiE=
|
||||||
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
github.com/blevesearch/zapx/v13 v13.4.3/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||||
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
|
github.com/blevesearch/zapx/v14 v14.4.3 h1:GY4Hecx0C6UTmiNC2pKdeA2rOKiLR5/rwpU9WR51dgM=
|
||||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
github.com/blevesearch/zapx/v14 v14.4.3/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2lc/0/CU=
|
||||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||||
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
|
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
|
||||||
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
|
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||||
|
github.com/blevesearch/zapx/v17 v17.1.2 h1:avbOk2igaASNoiy0BE/jPgcxAnRI2PGeydeP4hg7Ikk=
|
||||||
|
github.com/blevesearch/zapx/v17 v17.1.2/go.mod h1:WQObxKrqUX7cd0G1GMvDfc/bmZzQvoy7APOPimx7DiI=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
|
|
@ -161,10 +164,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||||
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A=
|
||||||
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a 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=
|
||||||
|
|
@ -191,8 +194,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
|
||||||
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
||||||
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
|
github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ=
|
||||||
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
|
github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
|
||||||
|
|
@ -249,15 +250,16 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI=
|
github.com/gdgvda/cron v0.7.0 h1:LFPZUTbCb5ZpzYxavbQDDbjd6nwTwkiNUWyulOdlY2I=
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
github.com/gdgvda/cron v0.7.0/go.mod h1:caBF+mzTZGtQqFE05T1m6u9OmCASY3EK51XAICf3wio=
|
||||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc h1:yLe7YJhK+XNjNV4SqDxAjpWAgft+KU+XwKZS4AKEUV0=
|
||||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI=
|
github.com/go-ap/activitypub v0.0.0-20260208110334-902f6cf8c2cc/go.mod h1:jUs8eczo1EAT4ByRpZ4mQmNvjarw9eNf7Nm5udpMRhY=
|
||||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
|
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966 h1:tV+3kZgqFMKVUf+JPKBV400ISM8440+6y/SQCS0WZwQ=
|
||||||
|
github.com/go-ap/errors v0.0.0-20260208110149-e1b309365966/go.mod h1:zkp58Q5yXpCxZbh3d0GDvwqiYclfVuHEHjc9SZKAj6I=
|
||||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77 h1:yHAmoR6avNy84PlLmjHt1z9flAp2Qs2ens5QDE/CNWk=
|
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77 h1:yHAmoR6avNy84PlLmjHt1z9flAp2Qs2ens5QDE/CNWk=
|
||||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77/go.mod h1:4h93IBxgfnE/DEleMLgJ/XCeu/RtQ+MUh3ucANseeXA=
|
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77/go.mod h1:4h93IBxgfnE/DEleMLgJ/XCeu/RtQ+MUh3ucANseeXA=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
|
|
@ -269,8 +271,8 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||||
github.com/go-enry/go-enry/v2 v2.9.5 h1:HPhAQQHYwJgihL2PxBZiUMFWiROsGwOBdB6/D8zCUhY=
|
github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs=
|
||||||
github.com/go-enry/go-enry/v2 v2.9.5/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-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
|
@ -280,16 +282,12 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
|
||||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
|
||||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
|
||||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
|
||||||
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
|
|
||||||
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
||||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||||
|
|
@ -318,17 +316,17 @@ github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxE
|
||||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/go-webauthn/webauthn v0.16.1 h1:x5/SSki5/aIfogaRukqvbg/RXa3Sgxy/9vU7UfFPHKU=
|
github.com/go-webauthn/webauthn v0.16.5 h1:x+vADHlaiIjta23kGhtwyCIlB5mayKx6SBlpwQ5NF9A=
|
||||||
github.com/go-webauthn/webauthn v0.16.1/go.mod h1:RBS+rtQJMkE5VfMQ4diDA2VNrEL8OeUhp4Srz37FHbQ=
|
github.com/go-webauthn/webauthn v0.16.5/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
|
||||||
github.com/go-webauthn/x v0.2.2 h1:zIiipvMbr48CXi5RG0XdBJR94kd8I5LfzHPb/q+YYmk=
|
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
|
||||||
github.com/go-webauthn/x v0.2.2/go.mod h1:IpJ5qyWB9NRhLX3C7gIfjTU7RZLXEP6kzFkoVSE7Fz4=
|
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
|
||||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
|
@ -369,8 +367,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
|
|
@ -400,8 +398,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=
|
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
|
||||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
|
@ -441,18 +439,16 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
|
||||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
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 v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+qks=
|
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||||
github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE=
|
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
github.com/jcmturner/aescts/v2 v2.0.0 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=
|
||||||
|
|
@ -479,12 +475,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
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.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
|
@ -501,10 +497,12 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||||
github.com/libdns/libdns v1.0.0 h1:IvYaz07JNz6jUQ4h/fv2R4sVnRnm77J/aOuC9B+TQTA=
|
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||||
github.com/libdns/libdns v1.0.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||||
|
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||||
|
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
|
|
@ -514,32 +512,32 @@ github.com/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ
|
||||||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
||||||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
|
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||||
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI=
|
github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU=
|
||||||
github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78=
|
github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
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/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
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.0.99 h1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=
|
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
|
||||||
github.com/minio/minio-go/v7 v7.0.99/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
|
||||||
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
||||||
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
github.com/minio/minlz v1.0.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=
|
||||||
|
|
@ -608,8 +606,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
|
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||||
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rhysd/actionlint v1.7.10 h1:FL3XIEs72G4/++168vlv5FKOWMSWvWIQw1kBCadyOcM=
|
github.com/rhysd/actionlint v1.7.10 h1:FL3XIEs72G4/++168vlv5FKOWMSWvWIQw1kBCadyOcM=
|
||||||
|
|
@ -657,14 +655,13 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||||
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.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
|
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||||
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
|
||||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
|
@ -676,8 +673,8 @@ github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBz
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
|
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||||
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
|
|
@ -686,6 +683,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
github.com/zeebo/pcg v1.0.1 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/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI=
|
gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI=
|
||||||
gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM=
|
gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM=
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
|
|
@ -703,8 +702,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
|
@ -724,8 +723,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -734,12 +733,12 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
|
||||||
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
@ -761,8 +760,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.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.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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -793,8 +792,8 @@ 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
@ -851,8 +850,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.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.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
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=
|
||||||
|
|
@ -862,8 +861,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
@ -877,8 +876,8 @@ 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.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.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
|
|
@ -912,8 +911,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
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.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||||
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=
|
||||||
|
|
@ -974,8 +973,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 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=
|
||||||
|
|
@ -985,7 +982,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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.0/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=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
|
@ -995,14 +991,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||||
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.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package actions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
|
|
@ -88,6 +89,13 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||||
return artifact, nil
|
return artifact, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsV4 reports whether the artifact was uploaded via the v4 backend.
|
||||||
|
// The v4 backend stores the whole artifact as a single zip file;
|
||||||
|
// v1-v3 stores each file as a separate row.
|
||||||
|
func (a *ActionArtifact) IsV4() bool {
|
||||||
|
return a.ArtifactName+".zip" == a.ArtifactPath && a.ContentEncoding == "application/zip"
|
||||||
|
}
|
||||||
|
|
||||||
func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) {
|
func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) {
|
||||||
var art ActionArtifact
|
var art ActionArtifact
|
||||||
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art)
|
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art)
|
||||||
|
|
@ -150,11 +158,32 @@ type ActionArtifactMeta struct {
|
||||||
Status ArtifactStatus
|
Status ArtifactStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AggregatedArtifact is the aggregated view of a logical artifact
|
||||||
|
// (one or more rows sharing the same run_id + artifact_name), used by the
|
||||||
|
// public API to represent a single artifact to clients.
|
||||||
|
type AggregatedArtifact struct {
|
||||||
|
ID int64 `xorm:"id"`
|
||||||
|
RunID int64 `xorm:"run_id"`
|
||||||
|
RepoID int64 `xorm:"-"`
|
||||||
|
ArtifactName string `xorm:"artifact_name"`
|
||||||
|
FileSize int64 `xorm:"file_size"`
|
||||||
|
Status ArtifactStatus `xorm:"status"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created_unix"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated_unix"`
|
||||||
|
ExpiredUnix timeutil.TimeStamp `xorm:"expired_unix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIDownloadURL returns the download URL for this artifact under the given
|
||||||
|
// repository API URL prefix (e.g. "https://host/api/v1/repos/owner/name").
|
||||||
|
func (a *AggregatedArtifact) APIDownloadURL(repoAPIURL string) string {
|
||||||
|
return fmt.Sprintf("%s/actions/artifacts/%d/zip", repoAPIURL, a.ID)
|
||||||
|
}
|
||||||
|
|
||||||
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
|
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
|
||||||
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
|
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
|
||||||
arts := make([]*ActionArtifactMeta, 0, 10)
|
arts := make([]*ActionArtifactMeta, 0, 10)
|
||||||
return arts, db.GetEngine(ctx).Table("action_artifact").
|
return arts, db.GetEngine(ctx).Table("action_artifact").
|
||||||
Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
|
Where(builder.Eq{"run_id": runID}.And(builder.In("status", ArtifactStatusUploadConfirmed, ArtifactStatusExpired))).
|
||||||
GroupBy("artifact_name").
|
GroupBy("artifact_name").
|
||||||
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
|
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
|
||||||
Find(&arts)
|
Find(&arts)
|
||||||
|
|
@ -192,3 +221,94 @@ func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
|
||||||
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
|
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetArtifactsOfRunDeleted marks all artifacts of the given run as deleted.
|
||||||
|
func SetArtifactsOfRunDeleted(ctx context.Context, runID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).
|
||||||
|
Where("run_id=?", runID).
|
||||||
|
Cols("status").
|
||||||
|
Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregatedArtifactConds returns the common WHERE clause used by aggregated
|
||||||
|
// artifact queries: restrict to visible statuses and apply the caller's filters.
|
||||||
|
// The Status field on opts is ignored — visibility is fixed to UploadConfirmed/Expired.
|
||||||
|
func aggregatedArtifactConds(opts FindArtifactsOptions) builder.Cond {
|
||||||
|
opts.Status = 0
|
||||||
|
return opts.ToConds().And(builder.In("status", ArtifactStatusUploadConfirmed, ArtifactStatusExpired))
|
||||||
|
}
|
||||||
|
|
||||||
|
const aggregatedArtifactSelect = "min(id) as id, run_id, artifact_name, sum(file_size) as file_size, max(status) as status, min(created_unix) as created_unix, max(updated_unix) as updated_unix, max(expired_unix) as expired_unix"
|
||||||
|
|
||||||
|
// ListAggregatedArtifacts returns paginated aggregated artifacts.
|
||||||
|
// Each result represents one logical artifact: a (run_id, artifact_name) group,
|
||||||
|
// with ID = MIN(id), FileSize = SUM(file_size), Status = MAX(status), and
|
||||||
|
// timestamps aggregated accordingly. Status filter in opts is ignored; results
|
||||||
|
// are always restricted to UploadConfirmed and Expired statuses.
|
||||||
|
func ListAggregatedArtifacts(ctx context.Context, opts FindArtifactsOptions) ([]*AggregatedArtifact, int64, error) {
|
||||||
|
cond := aggregatedArtifactConds(opts)
|
||||||
|
|
||||||
|
var countKeys []struct {
|
||||||
|
ID int64 `xorm:"id"`
|
||||||
|
}
|
||||||
|
if err := db.GetEngine(ctx).Table("action_artifact").
|
||||||
|
Where(cond).
|
||||||
|
GroupBy("run_id, artifact_name").
|
||||||
|
Select("min(id) as id").
|
||||||
|
Find(&countKeys); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
total := int64(len(countKeys))
|
||||||
|
|
||||||
|
sess := db.GetEngine(ctx).Table("action_artifact").
|
||||||
|
Where(cond).
|
||||||
|
GroupBy("run_id, artifact_name").
|
||||||
|
Select(aggregatedArtifactSelect).
|
||||||
|
OrderBy("id DESC")
|
||||||
|
|
||||||
|
capacity := 10
|
||||||
|
if opts.PageSize > 0 {
|
||||||
|
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||||
|
capacity = opts.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
arts := make([]*AggregatedArtifact, 0, capacity)
|
||||||
|
return arts, total, sess.Find(&arts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAggregatedArtifactByID returns the aggregated artifact by its canonical ID
|
||||||
|
// (MIN(id) of the group), scoped to the given repository. Returns util.ErrNotExist
|
||||||
|
// when the ID does not exist, is not canonical for its group, or does not belong to repoID.
|
||||||
|
// The repoID scoping is performed in the query so callers don't need a follow-up check.
|
||||||
|
func GetAggregatedArtifactByID(ctx context.Context, repoID, artifactID int64) (*AggregatedArtifact, error) {
|
||||||
|
var art ActionArtifact
|
||||||
|
has, err := db.GetEngine(ctx).Where(builder.Eq{"id": artifactID, "repo_id": repoID}).Get(&art)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, util.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
cond := aggregatedArtifactConds(FindArtifactsOptions{
|
||||||
|
RunID: art.RunID,
|
||||||
|
ArtifactName: art.ArtifactName,
|
||||||
|
})
|
||||||
|
|
||||||
|
meta := new(AggregatedArtifact)
|
||||||
|
has, err = db.GetEngine(ctx).Table("action_artifact").
|
||||||
|
Where(cond).
|
||||||
|
GroupBy("run_id, artifact_name").
|
||||||
|
Select(aggregatedArtifactSelect).
|
||||||
|
Get(meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has || meta.ID != artifactID {
|
||||||
|
return nil, util.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.RepoID = art.RepoID
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const (
|
||||||
ErrorCodeIncompleteWithMissingOutput
|
ErrorCodeIncompleteWithMissingOutput
|
||||||
ErrorCodeIncompleteWithMissingMatrixDimension
|
ErrorCodeIncompleteWithMissingMatrixDimension
|
||||||
ErrorCodeIncompleteWithUnknownCause
|
ErrorCodeIncompleteWithUnknownCause
|
||||||
|
ErrorCodeUnknownJobInNeeds
|
||||||
)
|
)
|
||||||
|
|
||||||
func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string {
|
func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string {
|
||||||
|
|
@ -69,6 +70,8 @@ func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string
|
||||||
return lang.TrString("actions.workflow.incomplete_with_missing_matrix_dimension", run.PreExecutionErrorDetails...)
|
return lang.TrString("actions.workflow.incomplete_with_missing_matrix_dimension", run.PreExecutionErrorDetails...)
|
||||||
case ErrorCodeIncompleteWithUnknownCause:
|
case ErrorCodeIncompleteWithUnknownCause:
|
||||||
return lang.TrString("actions.workflow.incomplete_with_unknown_cause", run.PreExecutionErrorDetails...)
|
return lang.TrString("actions.workflow.incomplete_with_unknown_cause", run.PreExecutionErrorDetails...)
|
||||||
|
case ErrorCodeUnknownJobInNeeds:
|
||||||
|
return lang.TrString("actions.workflow.unknown_job_in_needs", run.PreExecutionErrorDetails...)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("<unsupported error: code=%v details=%#v", run.PreExecutionErrorCode, run.PreExecutionErrorDetails)
|
return fmt.Sprintf("<unsupported error: code=%v details=%#v", run.PreExecutionErrorCode, run.PreExecutionErrorDetails)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,9 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
|
||||||
|
|
||||||
if run.TriggerUser == nil {
|
if run.TriggerUser == nil {
|
||||||
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
|
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
|
||||||
if err != nil {
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
u = user_model.NewGhostUser()
|
||||||
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
run.TriggerUser = u
|
run.TriggerUser = u
|
||||||
|
|
@ -255,6 +257,33 @@ func (run *ActionRun) IsDispatchedRun() bool {
|
||||||
return run.TriggerEvent == "workflow_dispatch"
|
return run.TriggerEvent == "workflow_dispatch"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsValid indicates whether this ActionRun is valid and can be run.
|
||||||
|
func (run *ActionRun) IsValid() bool {
|
||||||
|
return run.PreExecutionErrorCode == 0 && run.PreExecutionError == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanBeRerun indicates whether this ActionRun can be rerun.
|
||||||
|
func (run *ActionRun) CanBeRerun() bool {
|
||||||
|
if !run.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return run.Status.IsDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (run *ActionRun) PrepareNextAttempt() error {
|
||||||
|
if run.Status != StatusUnknown && !run.Status.IsDone() {
|
||||||
|
return fmt.Errorf("cannot prepare next attempt because run %d is active: %s", run.ID, run.Status.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
run.PreviousDuration = run.Duration()
|
||||||
|
|
||||||
|
run.Status = StatusWaiting
|
||||||
|
run.Started = 0
|
||||||
|
run.Stopped = 0
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func actionsCountOpenCacheKey(repoID int64) string {
|
func actionsCountOpenCacheKey(repoID int64) string {
|
||||||
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
|
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +347,7 @@ func UpdateRunApprovalByID(ctx context.Context, id int64, approval ApprovalType,
|
||||||
func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, pullRequestPosterID int64) ([]*ActionRun, error) {
|
func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, pullRequestPosterID int64) ([]*ActionRun, error) {
|
||||||
var runs []*ActionRun
|
var runs []*ActionRun
|
||||||
// performance relies on indexes on repo_id and status
|
// performance relies on indexes on repo_id and status
|
||||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
|
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return runs, nil
|
return runs, nil
|
||||||
|
|
@ -327,7 +356,7 @@ func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, p
|
||||||
func GetRunsNotDoneByRepoIDAndPullRequestID(ctx context.Context, repoID, pullRequestID int64) ([]*ActionRun, error) {
|
func GetRunsNotDoneByRepoIDAndPullRequestID(ctx context.Context, repoID, pullRequestID int64) ([]*ActionRun, error) {
|
||||||
var runs []*ActionRun
|
var runs []*ActionRun
|
||||||
// performance relies on indexes on repo_id and status
|
// performance relies on indexes on repo_id and status
|
||||||
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
|
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return runs, nil
|
return runs, nil
|
||||||
|
|
@ -583,4 +612,11 @@ func ComputeRunStatus(ctx context.Context, runID int64) (run *ActionRun, columns
|
||||||
return run, columns, nil
|
return run, columns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteRun removes the given run. It is the caller's responsibility to handle the run's dependencies like artifacts or
|
||||||
|
// jobs. Nothing happens if the run does not exist.
|
||||||
|
func DeleteRun(ctx context.Context, runID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Delete(&ActionRun{ID: runID})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type ActionRunIndex db.ResourceIndex
|
type ActionRunIndex db.ResourceIndex
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,19 @@ func (job *ActionRunJob) PrepareNextAttempt(initialStatus Status) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanBeRerun answers whether this ActionRunJob can be rerun. Returns true if it is done and the Run it belongs to
|
||||||
|
// is valid. Returns false in all other cases.
|
||||||
|
func (job *ActionRunJob) CanBeRerun(ctx context.Context) (bool, error) {
|
||||||
|
if err := job.LoadRun(ctx); err != nil {
|
||||||
|
return false, fmt.Errorf("cannot load run %d of job %d: %w", job.RunID, job.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !job.Run.IsValid() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return job.Status.IsDone(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
||||||
var job ActionRunJob
|
var job ActionRunJob
|
||||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
|
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
|
||||||
|
|
@ -338,3 +351,24 @@ func (job *ActionRunJob) EnableOpenIDConnect() (bool, error) {
|
||||||
}
|
}
|
||||||
return jobWorkflow.EnableOpenIDConnect, nil
|
return jobWorkflow.EnableOpenIDConnect, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllNeedsExist checks whether this ActionRunJob's Needs can theoretically be met by comparing them with the supplied
|
||||||
|
// list of all job IDs that part of a particular workflow run. Returns the list of unknown job IDs found in Needs
|
||||||
|
// alongside an indicator whether the check was successful.
|
||||||
|
func (job *ActionRunJob) AllNeedsExist(allExistingJobIDs container.Set[string]) ([]string, bool) {
|
||||||
|
unknownJobIDs := []string{}
|
||||||
|
for _, need := range job.Needs {
|
||||||
|
if !allExistingJobIDs.Contains(need) {
|
||||||
|
unknownJobIDs = append(unknownJobIDs, need)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownJobIDs, len(unknownJobIDs) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteJob removes the given job. Removing all associated tasks is up to the caller. If the given job does not exist,
|
||||||
|
// nothing happens.
|
||||||
|
func DeleteJob(ctx context.Context, jobID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Delete(&ActionRunJob{ID: jobID})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,14 @@ func (jobs ActionJobList) GetRunIDs() []int64 {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (jobs ActionJobList) GetJobIDs() container.Set[string] {
|
||||||
|
jobIDs := container.SetOf[string]()
|
||||||
|
for _, job := range jobs {
|
||||||
|
jobIDs.Add(job.JobID)
|
||||||
|
}
|
||||||
|
return jobIDs
|
||||||
|
}
|
||||||
|
|
||||||
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
|
||||||
runIDs := jobs.GetRunIDs()
|
runIDs := jobs.GetRunIDs()
|
||||||
runs := make(map[int64]*ActionRun, len(runIDs))
|
runs := make(map[int64]*ActionRun, len(runIDs))
|
||||||
|
|
@ -66,7 +74,7 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
if opts.CommitSHA != "" {
|
if opts.CommitSHA != "" {
|
||||||
|
|
|
||||||
21
models/actions/run_job_list_test.go
Normal file
21
models/actions/run_job_list_test.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forgejo.org/modules/container"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionJobList_GetJobIDs(t *testing.T) {
|
||||||
|
jobs := ActionJobList{
|
||||||
|
&ActionRunJob{JobID: "job 1"},
|
||||||
|
&ActionRunJob{JobID: "job 2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, container.SetOf("job 2", "job 1"), jobs.GetJobIDs())
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
"forgejo.org/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
|
"forgejo.org/modules/container"
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
||||||
|
|
@ -369,3 +370,135 @@ func TestIsRequestedByRunner(t *testing.T) {
|
||||||
|
|
||||||
assert.False(t, emptyHandleJob.IsRequestedByRunner(&differentHandle))
|
assert.False(t, emptyHandleJob.IsRequestedByRunner(&differentHandle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllNeedsExist(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
job ActionRunJob
|
||||||
|
existingJobIDs container.Set[string]
|
||||||
|
expectedUnknownIDs []string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no needs",
|
||||||
|
job: ActionRunJob{Needs: nil},
|
||||||
|
existingJobIDs: container.Set[string]{},
|
||||||
|
expectedUnknownIDs: []string{},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty needs",
|
||||||
|
job: ActionRunJob{Needs: []string{}},
|
||||||
|
existingJobIDs: container.Set[string]{},
|
||||||
|
expectedUnknownIDs: []string{},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "satisfied needs",
|
||||||
|
job: ActionRunJob{Needs: []string{"job1", "job2"}},
|
||||||
|
existingJobIDs: container.SetOf("job2", "job1"),
|
||||||
|
expectedUnknownIDs: []string{},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsatisfied needs",
|
||||||
|
job: ActionRunJob{Needs: []string{"unknown", "job2"}},
|
||||||
|
existingJobIDs: container.SetOf("job2", "job1"),
|
||||||
|
expectedUnknownIDs: []string{"unknown"},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comparison is case-sensitive",
|
||||||
|
job: ActionRunJob{Needs: []string{"Job1", "job2"}},
|
||||||
|
existingJobIDs: container.SetOf("job2", "job1"),
|
||||||
|
expectedUnknownIDs: []string{"Job1"},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
unknownIDs, ok := testCase.job.AllNeedsExist(testCase.existingJobIDs)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.ok, ok)
|
||||||
|
assert.Equal(t, testCase.expectedUnknownIDs, unknownIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionRunJob_CanBeRerun(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
job ActionRunJob
|
||||||
|
canBeRerun bool
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "job with unknown status",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusUnknown},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSuccess},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusFailure},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancelled job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusCancelled},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skipped job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSkipped},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "waiting job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusWaiting},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blocked job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusBlocked},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ActionRun is nil",
|
||||||
|
job: ActionRunJob{ID: 12, Run: nil, Status: StatusSuccess},
|
||||||
|
expectedError: "cannot load run 0 of job 12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with busy run but completed job",
|
||||||
|
job: ActionRunJob{Run: &ActionRun{Status: StatusRunning}, Status: StatusSuccess},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with run that cannot be run",
|
||||||
|
job: ActionRunJob{
|
||||||
|
Run: &ActionRun{Status: StatusRunning, PreExecutionErrorCode: ErrorCodeEventDetectionError},
|
||||||
|
Status: StatusSuccess,
|
||||||
|
},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
result, err := testCase.job.CanBeRerun(t.Context())
|
||||||
|
|
||||||
|
if testCase.expectedError == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.ErrorContains(t, err, testCase.expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.canBeRerun, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
if opts.WorkflowID != "" {
|
if opts.WorkflowID != "" {
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRunBefore(t *testing.T) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetConcurrencyGroup(t *testing.T) {
|
func TestSetConcurrencyGroup(t *testing.T) {
|
||||||
run := ActionRun{}
|
run := ActionRun{}
|
||||||
run.SetConcurrencyGroup("abc123")
|
run.SetConcurrencyGroup("abc123")
|
||||||
|
|
@ -96,6 +93,86 @@ func TestIsManualRun(t *testing.T) {
|
||||||
assert.False(t, pushRun.IsDispatchedRun())
|
assert.False(t, pushRun.IsDispatchedRun())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActionRun_IsValid(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
run ActionRun
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid run",
|
||||||
|
run: ActionRun{},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with pre-execution error",
|
||||||
|
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput},
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, testCase.isValid, testCase.run.IsValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionRun_CanBeRerun(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
run ActionRun
|
||||||
|
canBeRerun bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "run with unknown status",
|
||||||
|
run: ActionRun{Status: StatusUnknown},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful run",
|
||||||
|
run: ActionRun{Status: StatusSuccess},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed run",
|
||||||
|
run: ActionRun{Status: StatusFailure},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancelled run",
|
||||||
|
run: ActionRun{Status: StatusCancelled},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skipped run",
|
||||||
|
run: ActionRun{Status: StatusSkipped},
|
||||||
|
canBeRerun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "waiting run",
|
||||||
|
run: ActionRun{Status: StatusWaiting},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blocked run",
|
||||||
|
run: ActionRun{Status: StatusBlocked},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with pre-execution error",
|
||||||
|
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput, Status: StatusSuccess},
|
||||||
|
canBeRerun: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, testCase.canBeRerun, testCase.run.CanBeRerun())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRepoNumOpenActions(t *testing.T) {
|
func TestRepoNumOpenActions(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
err := cache.Init()
|
err := cache.Init()
|
||||||
|
|
@ -606,3 +683,12 @@ jobs:
|
||||||
assert.Zero(t, insertedJobs[1].TaskID)
|
assert.Zero(t, insertedJobs[1].TaskID)
|
||||||
assert.Equal(t, StatusWaiting, insertedJobs[1].Status)
|
assert.Equal(t, StatusWaiting, insertedJobs[1].Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActionRunLoadAttributes(t *testing.T) {
|
||||||
|
run := &ActionRun{
|
||||||
|
RepoID: 10,
|
||||||
|
TriggerUserID: 1000,
|
||||||
|
}
|
||||||
|
require.NoError(t, run.LoadAttributes(t.Context()))
|
||||||
|
assert.Equal(t, "ghost", run.TriggerUser.LowerName)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
|
||||||
|
|
||||||
// LoadAttributes loads the attributes of the runner
|
// LoadAttributes loads the attributes of the runner
|
||||||
func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
|
func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
|
||||||
|
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
|
||||||
if r.OwnerID > 0 {
|
if r.OwnerID > 0 {
|
||||||
var user user_model.User
|
var user user_model.User
|
||||||
has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
|
has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
|
||||||
|
|
@ -214,7 +215,7 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
||||||
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||||
}
|
}
|
||||||
cond = cond.And(c)
|
cond = cond.And(c)
|
||||||
} else if opts.OwnerID > 0 { // OwnerID is ignored if RepoID is set
|
} else if opts.OwnerID != 0 { // OwnerID is ignored if RepoID is set
|
||||||
c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
|
c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
if opts.WithVisible {
|
if opts.WithVisible {
|
||||||
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
|
||||||
|
|
@ -395,6 +396,13 @@ func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
|
||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteEphemeralRunner removes the ephemeral runner with the given ID. If the runner with the given ID is not an
|
||||||
|
// ephemeral runner, nothing happens.
|
||||||
|
func DeleteEphemeralRunner(ctx context.Context, id int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Where(builder.Eq{"id": id, "ephemeral": true}).Delete(&ActionRunner{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteOfflineRunners(ctx context.Context, olderThan timeutil.TimeStamp, globalOnly bool) error {
|
func DeleteOfflineRunners(ctx context.Context, olderThan timeutil.TimeStamp, globalOnly bool) error {
|
||||||
log.Info("Doing: DeleteOfflineRunners")
|
log.Info("Doing: DeleteOfflineRunners")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, runner := range runners {
|
for _, runner := range runners {
|
||||||
|
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
|
||||||
if runner.OwnerID > 0 && runner.Owner == nil {
|
if runner.OwnerID > 0 && runner.Owner == nil {
|
||||||
runner.Owner = users[runner.OwnerID]
|
runner.Owner = users[runner.OwnerID]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -479,3 +479,62 @@ func TestRunner_FindRunnerOptionsToConds(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteEphemeralRunner(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
persistentRunnerOne := &ActionRunner{
|
||||||
|
ID: 606526,
|
||||||
|
UUID: "d53a1222-ae7a-4430-97f8-8fcb6efd04c9",
|
||||||
|
Name: "persistent-runner-one",
|
||||||
|
OwnerID: 2,
|
||||||
|
RepoID: 0,
|
||||||
|
Ephemeral: false,
|
||||||
|
TokenHash: "J9YDsQL",
|
||||||
|
}
|
||||||
|
persistentRunnerTwo := &ActionRunner{
|
||||||
|
ID: 606527,
|
||||||
|
UUID: "3dc23067-b2fd-4daf-b428-dddad80d7f37",
|
||||||
|
Name: "persistent-runner-two",
|
||||||
|
OwnerID: 2,
|
||||||
|
RepoID: 0,
|
||||||
|
Ephemeral: false,
|
||||||
|
TokenHash: "jvIylZtHsS",
|
||||||
|
}
|
||||||
|
ephemeralRunnerOne := &ActionRunner{
|
||||||
|
ID: 606528,
|
||||||
|
UUID: "2d9bc0a1-7019-4ed3-ba67-6415415ac2a9",
|
||||||
|
Name: "ephemeral-runner-one",
|
||||||
|
OwnerID: 2,
|
||||||
|
RepoID: 0,
|
||||||
|
Ephemeral: true,
|
||||||
|
TokenHash: "t9C8L0kM3W",
|
||||||
|
}
|
||||||
|
ephemeralRunnerTwo := &ActionRunner{
|
||||||
|
ID: 606529,
|
||||||
|
UUID: "da7a03f8-ab39-4c54-9ec9-2bd312fe3be1",
|
||||||
|
Name: "ephemeral-runner-two",
|
||||||
|
OwnerID: 2,
|
||||||
|
RepoID: 0,
|
||||||
|
Ephemeral: true,
|
||||||
|
TokenHash: "g9oTOFM",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, CreateRunner(t.Context(), persistentRunnerOne))
|
||||||
|
require.NoError(t, CreateRunner(t.Context(), persistentRunnerTwo))
|
||||||
|
require.NoError(t, CreateRunner(t.Context(), ephemeralRunnerOne))
|
||||||
|
require.NoError(t, CreateRunner(t.Context(), ephemeralRunnerTwo))
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: persistentRunnerOne.ID})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: persistentRunnerTwo.ID})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: ephemeralRunnerOne.ID})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: ephemeralRunnerTwo.ID})
|
||||||
|
|
||||||
|
require.NoError(t, DeleteEphemeralRunner(t.Context(), persistentRunnerOne.ID))
|
||||||
|
require.NoError(t, DeleteEphemeralRunner(t.Context(), ephemeralRunnerOne.ID))
|
||||||
|
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: persistentRunnerOne.ID})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: persistentRunnerTwo.ID})
|
||||||
|
unittest.AssertNotExistsBean(t, &ActionRunner{ID: ephemeralRunnerOne.ID})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &ActionRunner{ID: ephemeralRunnerTwo.ID})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
repo_model "forgejo.org/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
|
|
@ -21,7 +20,7 @@ import (
|
||||||
type ActionSchedule struct {
|
type ActionSchedule struct {
|
||||||
ID int64
|
ID int64
|
||||||
Title string
|
Title string
|
||||||
Specs []string
|
Specs []*ActionScheduleSpec `xorm:"-"`
|
||||||
RepoID int64 `xorm:"index"`
|
RepoID int64 `xorm:"index"`
|
||||||
Repo *repo_model.Repository `xorm:"-"`
|
Repo *repo_model.Repository `xorm:"-"`
|
||||||
OwnerID int64 `xorm:"index"`
|
OwnerID int64 `xorm:"index"`
|
||||||
|
|
@ -73,25 +72,12 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through each schedule spec and create a new spec row
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for _, spec := range row.Specs {
|
for _, spec := range row.Specs {
|
||||||
specRow := &ActionScheduleSpec{
|
spec.ScheduleID = row.ID
|
||||||
RepoID: row.RepoID,
|
spec.RepoID = row.RepoID
|
||||||
ScheduleID: row.ID,
|
|
||||||
Spec: spec,
|
|
||||||
}
|
|
||||||
// Parse the spec and check for errors
|
|
||||||
schedule, err := specRow.Parse()
|
|
||||||
if err != nil {
|
|
||||||
continue // skip to the next spec if there's an error
|
|
||||||
}
|
|
||||||
|
|
||||||
specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix())
|
|
||||||
|
|
||||||
// Insert the new schedule spec row
|
// Insert the new schedule spec row
|
||||||
if err = db.Insert(ctx, specRow); err != nil {
|
if err = db.Insert(ctx, spec); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +116,7 @@ func (opts FindScheduleOptions) ToConds() builder.Cond {
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import (
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
repo_model "forgejo.org/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/gdgvda/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActionScheduleSpec represents a schedule spec of a workflow file
|
// ActionScheduleSpec represents a schedule spec of a workflow file
|
||||||
|
|
@ -27,36 +28,58 @@ type ActionScheduleSpec struct {
|
||||||
// started or this entry's schedule is unsatisfiable
|
// started or this entry's schedule is unsatisfiable
|
||||||
Next timeutil.TimeStamp `xorm:"index"`
|
Next timeutil.TimeStamp `xorm:"index"`
|
||||||
// Prev is the last time this job was run, or the zero time if never.
|
// Prev is the last time this job was run, or the zero time if never.
|
||||||
Prev timeutil.TimeStamp
|
Prev timeutil.TimeStamp
|
||||||
Spec string
|
Spec string
|
||||||
|
TimeZone optional.Option[string]
|
||||||
|
|
||||||
Created timeutil.TimeStamp `xorm:"created"`
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
Updated timeutil.TimeStamp `xorm:"updated"`
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewActionScheduleSpec(cron string, tz optional.Option[string], referenceTime time.Time) (*ActionScheduleSpec, error) {
|
||||||
|
spec := &ActionScheduleSpec{
|
||||||
|
Spec: cron,
|
||||||
|
TimeZone: tz,
|
||||||
|
}
|
||||||
|
cronSchedule, err := spec.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.Next = timeutil.TimeStamp(cronSchedule.Next(referenceTime).Unix())
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses the spec and returns a cron.Schedule
|
// Parse parses the spec and returns a cron.Schedule
|
||||||
// Unlike the default cron parser, Parse uses UTC timezone as the default if none is specified.
|
// Unlike the default cron parser, Parse uses UTC timezone as the default if none is specified.
|
||||||
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
|
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
|
||||||
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
parser, err := cron.NewDefaultParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
schedule, err := parser.Parse(s.Spec)
|
schedule, err := parser.Parse(s.Spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the spec has specified a timezone, use it
|
// If `timezone` is not defined in the workflow, but the spec includes a timezone, use it.
|
||||||
if strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=") {
|
if !s.TimeZone.Has() && (strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=")) {
|
||||||
return schedule, nil
|
return schedule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
specSchedule, ok := schedule.(*cron.SpecSchedule)
|
var location *time.Location
|
||||||
// If it's not a spec schedule, like "@every 5m", timezone is not relevant
|
if present, tz := s.TimeZone.Get(); present {
|
||||||
if !ok {
|
location, err = time.LoadLocation(tz)
|
||||||
return schedule, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// UTC is the default time zone.
|
||||||
|
location = time.UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the timezone to UTC
|
return schedule.WithLocation(location), nil
|
||||||
specSchedule.Location = time.UTC
|
|
||||||
return specSchedule, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,50 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestActionScheduleSpec_NewActionScheduleSpec(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
refTime time.Time
|
||||||
|
cronPattern string
|
||||||
|
timeZone string
|
||||||
|
want string
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "without timezone",
|
||||||
|
refTime: time.Date(2026, 4, 6, 11, 56, 0, 0, time.UTC),
|
||||||
|
cronPattern: "58 14 * * *",
|
||||||
|
want: "2026-04-06T14:58:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with separate timezone",
|
||||||
|
refTime: time.Date(2026, 4, 6, 11, 56, 0, 0, time.UTC),
|
||||||
|
cronPattern: "58 14 * * *",
|
||||||
|
timeZone: "Europe/Tallinn", // +03 (EEST)
|
||||||
|
want: "2026-04-06T11:58:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
s, err := NewActionScheduleSpec(test.cronPattern, optional.FromNonDefault(test.timeZone), test.refTime)
|
||||||
|
test.wantErr(t, err)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
assert.Equal(t, test.want, s.Next.AsTime().UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestActionScheduleSpec_Parse(t *testing.T) {
|
func TestActionScheduleSpec_Parse(t *testing.T) {
|
||||||
// Mock the local timezone is not UTC
|
// Mock the local timezone is not UTC
|
||||||
local := time.Local
|
local := time.Local
|
||||||
|
|
@ -21,50 +61,105 @@ func TestActionScheduleSpec_Parse(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
time.Local = tz
|
time.Local = tz
|
||||||
|
|
||||||
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
spec string
|
refTime time.Time
|
||||||
want string
|
spec string
|
||||||
wantErr assert.ErrorAssertionFunc
|
timeZone string
|
||||||
|
want string
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "regular",
|
name: "regular",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
spec: "0 10 * * *",
|
spec: "0 10 * * *",
|
||||||
want: "2024-07-31T10:00:00Z",
|
want: "2024-07-31T10:00:00Z",
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid",
|
name: "invalid",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
spec: "0 10 * *",
|
spec: "0 10 * *",
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: assert.Error,
|
wantErr: assert.Error,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with timezone",
|
name: "with TZ in cron schedule",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
spec: "TZ=America/New_York 0 10 * * *",
|
spec: "TZ=America/New_York 0 10 * * *",
|
||||||
want: "2024-07-31T14:00:00Z",
|
want: "2024-07-31T14:00:00Z",
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "timezone irrelevant",
|
name: "with CRON_TZ in cron schedule",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
|
spec: "CRON_TZ=America/New_York 0 10 * * *",
|
||||||
|
want: "2024-07-31T14:00:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with separate time zone",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
|
spec: "0 10 * * *",
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
want: "2024-07-31T14:00:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "separate time zone takes precedence over inlined time zone",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
|
spec: "CRON_TZ=Europe/Berlin 0 10 * * *",
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
want: "2024-07-31T14:00:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "time zone irrelevant",
|
||||||
|
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
|
||||||
spec: "@every 5m",
|
spec: "@every 5m",
|
||||||
want: "2024-07-31T07:52:55Z",
|
want: "2024-07-31T07:52:55Z",
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// The various cron implementations handle the DST jump forwards differently. The most popular approaches
|
||||||
|
// are (a) scheduling all jobs at 3 o'clock that were supposed to run between 2 and 3 o'clock, or (b)
|
||||||
|
// skipping the execution on that day because any time between 2 and 3 o'clock never happened. Forgejo uses
|
||||||
|
// option B because the code it inherited already did that and was exposed to users.
|
||||||
|
name: "skips execution during DST jump forwards",
|
||||||
|
refTime: time.Date(2025, 3, 30, 0, 55, 0, 0, time.UTC), // 01:55 local time
|
||||||
|
spec: "10 2 * * *", // The clock jumps at 2 o'clock to 3 o'clock.
|
||||||
|
timeZone: "Europe/Berlin",
|
||||||
|
want: "2025-03-31T00:10:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "executes a first time before DST jump backwards",
|
||||||
|
refTime: time.Date(2025, 10, 26, 0, 5, 0, 0, time.UTC), // 02:05 local time
|
||||||
|
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
|
||||||
|
timeZone: "Europe/Berlin",
|
||||||
|
want: "2025-10-26T00:10:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "executes a second time after DST jump backwards",
|
||||||
|
refTime: time.Date(2025, 10, 26, 1, 5, 0, 0, time.UTC), // 02:05 local time
|
||||||
|
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
|
||||||
|
timeZone: "Europe/Berlin",
|
||||||
|
want: "2025-10-26T01:10:00Z",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
s := &ActionScheduleSpec{
|
s := &ActionScheduleSpec{
|
||||||
Spec: tt.spec,
|
Spec: tt.spec,
|
||||||
|
TimeZone: optional.FromNonDefault(tt.timeZone),
|
||||||
}
|
}
|
||||||
got, err := s.Parse()
|
got, err := s.Parse()
|
||||||
tt.wantErr(t, err)
|
tt.wantErr(t, err)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
assert.Equal(t, tt.want, got.Next(now).UTC().Format(time.RFC3339))
|
assert.Equal(t, tt.want, got.Next(tt.refTime).UTC().Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
102
models/actions/schedule_test.go
Normal file
102
models/actions/schedule_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/models/repo"
|
||||||
|
"forgejo.org/models/unittest"
|
||||||
|
"forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
"forgejo.org/modules/webhook"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScheduleCreateScheduleTask(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 2})
|
||||||
|
repo62 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 62, Name: "test_workflows", OwnerID: user2.ID})
|
||||||
|
|
||||||
|
content := `
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
schedule:
|
||||||
|
- cron: "2 13 * * *"
|
||||||
|
- cron: "03 13 * * *"
|
||||||
|
timezone: Europe/Paris
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: debian
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "OK"
|
||||||
|
`
|
||||||
|
|
||||||
|
referenceTime := time.Date(2026, 3, 27, 17, 41, 21, 0, time.UTC)
|
||||||
|
|
||||||
|
specWithoutTZ, err := NewActionScheduleSpec("2 13 * * *", optional.None[string](), referenceTime)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
specWithTZ, err := NewActionScheduleSpec("3 13 * * *", optional.Some("Europe/Paris"), referenceTime)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
schedule := &ActionSchedule{
|
||||||
|
Title: ".forgejo/workflows/test.yaml",
|
||||||
|
Specs: []*ActionScheduleSpec{specWithoutTZ, specWithTZ},
|
||||||
|
RepoID: repo62.ID,
|
||||||
|
OwnerID: user2.ID,
|
||||||
|
WorkflowID: "test.yaml",
|
||||||
|
WorkflowDirectory: ".forgejo/workflows",
|
||||||
|
TriggerUserID: -2,
|
||||||
|
Ref: "main",
|
||||||
|
CommitSHA: "6af834a5bc97c1a337eb3a21d26903c5cdceca0c",
|
||||||
|
Event: webhook.HookEventPush,
|
||||||
|
EventPayload: "{\"action\":\"schedule\"}",
|
||||||
|
Content: []byte(content),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CreateScheduleTask(t.Context(), []*ActionSchedule{schedule})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
schedules, err := db.Find[ActionSchedule](t.Context(), FindScheduleOptions{OwnerID: user2.ID, RepoID: repo62.ID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, schedules, 1)
|
||||||
|
|
||||||
|
assert.NotZero(t, schedules[0].ID)
|
||||||
|
assert.Equal(t, ".forgejo/workflows/test.yaml", schedules[0].Title)
|
||||||
|
assert.Equal(t, "test.yaml", schedules[0].WorkflowID)
|
||||||
|
assert.Equal(t, ".forgejo/workflows", schedules[0].WorkflowDirectory)
|
||||||
|
assert.Equal(t, int64(-2), schedules[0].TriggerUserID)
|
||||||
|
assert.Equal(t, "main", schedules[0].Ref)
|
||||||
|
assert.Equal(t, "6af834a5bc97c1a337eb3a21d26903c5cdceca0c", schedules[0].CommitSHA)
|
||||||
|
assert.Equal(t, webhook.HookEventPush, schedules[0].Event)
|
||||||
|
assert.JSONEq(t, "{\"action\":\"schedule\"}", schedules[0].EventPayload)
|
||||||
|
assert.Equal(t, []byte(content), schedules[0].Content)
|
||||||
|
|
||||||
|
specs, total, err := FindSpecs(t.Context(), FindSpecOptions{RepoID: repo62.ID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), total)
|
||||||
|
|
||||||
|
assert.NotZero(t, specs[0].ID)
|
||||||
|
assert.Equal(t, schedules[0].ID, specs[0].ScheduleID)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1774699380), specs[0].Next)
|
||||||
|
assert.Equal(t, "3 13 * * *", specs[0].Spec)
|
||||||
|
assert.Equal(t, optional.Some("Europe/Paris"), specs[0].TimeZone)
|
||||||
|
assert.Zero(t, specs[0].Prev)
|
||||||
|
|
||||||
|
assert.NotZero(t, specs[1].ID)
|
||||||
|
assert.Equal(t, schedules[0].ID, specs[1].ScheduleID)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1774702920), specs[1].Next)
|
||||||
|
assert.Equal(t, "2 13 * * *", specs[1].Spec)
|
||||||
|
assert.Equal(t, optional.None[string](), specs[1].TimeZone)
|
||||||
|
assert.Zero(t, specs[1].Prev)
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
"forgejo.org/modules/translation"
|
"forgejo.org/modules/translation"
|
||||||
|
|
||||||
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
|
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
|
||||||
|
|
@ -107,12 +109,7 @@ func (s Status) IsBlocked() bool {
|
||||||
|
|
||||||
// In returns whether s is one of the given statuses
|
// In returns whether s is one of the given statuses
|
||||||
func (s Status) In(statuses ...Status) bool {
|
func (s Status) In(statuses ...Status) bool {
|
||||||
for _, v := range statuses {
|
return slices.Contains(statuses, s)
|
||||||
if s == v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Status) AsResult() runnerv1.Result {
|
func (s Status) AsResult() runnerv1.Result {
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,15 @@ func HasTaskForRunner(ctx context.Context, runnerID int64) (bool, error) {
|
||||||
return db.GetEngine(ctx).Where("runner_id = ?", runnerID).Exist(&ActionTask{})
|
return db.GetEngine(ctx).Where("runner_id = ?", runnerID).Exist(&ActionTask{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTasksOfJob(ctx context.Context, jobID int64) ([]*ActionTask, error) {
|
||||||
|
var tasks []*ActionTask
|
||||||
|
err := db.GetEngine(ctx).Where("job_id=?", jobID).Find(&tasks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot fetch tasks of job %d: %w", jobID, err)
|
||||||
|
}
|
||||||
|
return tasks, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetTaskByJobAttempt(ctx context.Context, jobID, attempt int64) (*ActionTask, error) {
|
func GetTaskByJobAttempt(ctx context.Context, jobID, attempt int64) (*ActionTask, error) {
|
||||||
var task ActionTask
|
var task ActionTask
|
||||||
has, err := db.GetEngine(ctx).Where("job_id=?", jobID).Where("attempt=?", attempt).Get(&task)
|
has, err := db.GetEngine(ctx).Where("job_id=?", jobID).Where("attempt=?", attempt).Get(&task)
|
||||||
|
|
@ -451,9 +460,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder tasks are created when the status/content of an [ActionRunJob] is resolved by Forgejo without dispatch to
|
// Placeholder tasks are created when the status/content of an [ActionRunJob] is resolved by Forgejo without dispatch to
|
||||||
// a runner, specifically in the case of a workflow call's outer job. It is the responsibility of the caller to
|
// a runner, specifically in the case of a workflow call's outer job.
|
||||||
// increment the job's Attempt field before invoking this method, and to update that field in the database, so that
|
|
||||||
// reruns can function for placeholder tasks and provide updated outputs.
|
|
||||||
func CreatePlaceholderTask(ctx context.Context, job *ActionRunJob, outputs map[string]string) (*ActionTask, error) {
|
func CreatePlaceholderTask(ctx context.Context, job *ActionRunJob, outputs map[string]string) (*ActionTask, error) {
|
||||||
actionTask := &ActionTask{
|
actionTask := &ActionTask{
|
||||||
JobID: job.ID,
|
JobID: job.ID,
|
||||||
|
|
@ -499,6 +506,27 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteTask removes the given task including all its steps and outputs. Removing logs and ephemeral runners is the
|
||||||
|
// caller's responsibility.
|
||||||
|
func DeleteTask(ctx context.Context, taskID int64) error {
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
_, err = db.GetEngine(ctx).Delete(&ActionTaskStep{TaskID: taskID})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to delete steps of task %d: %w", taskID, err)
|
||||||
|
}
|
||||||
|
_, err = db.GetEngine(ctx).Delete(&ActionTaskOutput{TaskID: taskID})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to delete outputs of task %d: %w", taskID, err)
|
||||||
|
}
|
||||||
|
_, err = db.GetEngine(ctx).Delete(&ActionTask{ID: taskID})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to delete task %d: %w", taskID, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, limit int) ([]*ActionTask, error) {
|
func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, limit int) ([]*ActionTask, error) {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
if opts.CommitSHA != "" {
|
if opts.CommitSHA != "" {
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,7 @@ func (at ActionType) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (at ActionType) InActions(actions ...string) bool {
|
func (at ActionType) InActions(actions ...string) bool {
|
||||||
for _, action := range actions {
|
return slices.Contains(actions, at.String())
|
||||||
if action == at.String() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action represents user operation type and other information to
|
// Action represents user operation type and other information to
|
||||||
|
|
@ -817,3 +812,36 @@ func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Action) IsActionPrivate(ctx context.Context) (bool, error) {
|
||||||
|
if a.IsPrivate {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return true, repo_model.ErrRepoNotExist{}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := a.Repo
|
||||||
|
err := repo.LoadOwner(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.IsPrivate || repo.Owner.KeepActivityPrivate || repo.Owner.Visibility != structs.VisibleTypePublic {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a.LoadActUser(ctx)
|
||||||
|
if a.ActUser == nil {
|
||||||
|
return true, user_model.ErrUserNotExist{}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := a.ActUser
|
||||||
|
if user.KeepActivityPrivate || user.Visibility != structs.VisibleTypePublic {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -371,3 +371,28 @@ func TestGetIssueInfos(t *testing.T) {
|
||||||
assert.Equal(t, test.field3, issueInfos[2])
|
assert.Equal(t, test.field3, issueInfos[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsPrivate(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("models/activities/fixtures/TestIsPrivate")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
activityID int64
|
||||||
|
private bool
|
||||||
|
}{
|
||||||
|
{1, true}, // private repo
|
||||||
|
{3, false}, // public activities, public repo
|
||||||
|
{11, true}, // private activities
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
ctx := t.Context()
|
||||||
|
action, err := activities_model.GetActivityByID(ctx, test.activityID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
private, err := action.IsActionPrivate(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.private, private, "action ID: %d", test.activityID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
14
models/activities/error.go
Normal file
14
models/activities/error.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ErrActivityPrivate struct {
|
||||||
|
id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrActivityPrivate) Error() string {
|
||||||
|
return fmt.Sprintf("Activity with id %d is private", err.id)
|
||||||
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeed
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
|
||||||
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
|
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
|
||||||
count, err := sess.FindAndCount(&actions)
|
count, err := sess.Desc("`federated_user_activity`.created").FindAndCount(&actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
models/activities/fixtures/TestIsPrivate/action.yml
Normal file
7
models/activities/fixtures/TestIsPrivate/action.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
- id: 11
|
||||||
|
user_id: 44
|
||||||
|
op_type: 1 # create repo
|
||||||
|
act_user_id: 44 # private user activities
|
||||||
|
repo_id: 60 # public
|
||||||
|
is_private: false
|
||||||
|
created_unix: 1680454039
|
||||||
36
models/activities/fixtures/TestIsPrivate/user.yml
Normal file
36
models/activities/fixtures/TestIsPrivate/user.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
- id: 44
|
||||||
|
lower_name: user44
|
||||||
|
name: user44
|
||||||
|
full_name: user44
|
||||||
|
email: user44@example.com
|
||||||
|
keep_email_private: false
|
||||||
|
email_notifications_preference: enabled
|
||||||
|
passwd: ZogKvWdyEx:password
|
||||||
|
passwd_hash_algo: dummy
|
||||||
|
must_change_password: false
|
||||||
|
login_source: 0
|
||||||
|
login_name: user44
|
||||||
|
type: 0
|
||||||
|
salt: ZogKvWdyEx
|
||||||
|
max_repo_creation: -1
|
||||||
|
is_active: true
|
||||||
|
is_admin: false
|
||||||
|
is_restricted: false
|
||||||
|
allow_git_hook: false
|
||||||
|
allow_import_local: false
|
||||||
|
allow_create_organization: true
|
||||||
|
prohibit_login: false
|
||||||
|
avatar: ""
|
||||||
|
avatar_email: user44@example.com
|
||||||
|
use_custom_avatar: true
|
||||||
|
num_followers: 0
|
||||||
|
num_following: 0
|
||||||
|
num_stars: 0
|
||||||
|
num_repos: 0
|
||||||
|
num_teams: 0
|
||||||
|
num_members: 0
|
||||||
|
visibility: 0
|
||||||
|
repo_admin_change_team_access: false
|
||||||
|
theme: ""
|
||||||
|
keep_activity_private: true
|
||||||
|
created_unix: 1672578380
|
||||||
|
|
@ -210,34 +210,9 @@ 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, err := db.GetByIDs(ctx, "id", repoIDs, &repo_model.Repository{})
|
||||||
left := len(repoIDs)
|
if err != nil {
|
||||||
for left > 0 {
|
return nil, nil, err
|
||||||
limit := db.DefaultMaxInSize
|
|
||||||
if left < limit {
|
|
||||||
limit = left
|
|
||||||
}
|
|
||||||
rows, err := db.GetEngine(ctx).
|
|
||||||
In("id", repoIDs[:limit]).
|
|
||||||
Rows(new(repo_model.Repository))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var repo repo_model.Repository
|
|
||||||
err = rows.Scan(&repo)
|
|
||||||
if err != nil {
|
|
||||||
rows.Close()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repos[repo.ID] = &repo
|
|
||||||
}
|
|
||||||
_ = rows.Close()
|
|
||||||
|
|
||||||
left -= limit
|
|
||||||
repoIDs = repoIDs[limit:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failed := []int{}
|
failed := []int{}
|
||||||
|
|
@ -284,34 +259,9 @@ 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, err := db.GetByIDs(ctx, "id", issueIDs, &issues_model.Issue{})
|
||||||
left := len(issueIDs)
|
if err != nil {
|
||||||
for left > 0 {
|
return nil, err
|
||||||
limit := db.DefaultMaxInSize
|
|
||||||
if left < limit {
|
|
||||||
limit = left
|
|
||||||
}
|
|
||||||
rows, err := db.GetEngine(ctx).
|
|
||||||
In("id", issueIDs[:limit]).
|
|
||||||
Rows(new(issues_model.Issue))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var issue issues_model.Issue
|
|
||||||
err = rows.Scan(&issue)
|
|
||||||
if err != nil {
|
|
||||||
rows.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
issues[issue.ID] = &issue
|
|
||||||
}
|
|
||||||
_ = rows.Close()
|
|
||||||
|
|
||||||
left -= limit
|
|
||||||
issueIDs = issueIDs[limit:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
|
|
@ -379,34 +329,9 @@ 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, err := db.GetByIDs(ctx, "id", userIDs, &user_model.User{})
|
||||||
left := len(userIDs)
|
if err != nil {
|
||||||
for left > 0 {
|
return nil, err
|
||||||
limit := db.DefaultMaxInSize
|
|
||||||
if left < limit {
|
|
||||||
limit = left
|
|
||||||
}
|
|
||||||
rows, err := db.GetEngine(ctx).
|
|
||||||
In("id", userIDs[:limit]).
|
|
||||||
Rows(new(user_model.User))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var user user_model.User
|
|
||||||
err = rows.Scan(&user)
|
|
||||||
if err != nil {
|
|
||||||
rows.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users[user.ID] = &user
|
|
||||||
}
|
|
||||||
_ = rows.Close()
|
|
||||||
|
|
||||||
left -= limit
|
|
||||||
userIDs = userIDs[limit:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
|
|
@ -430,34 +355,9 @@ 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, err := db.GetByIDs(ctx, "id", commentIDs, &issues_model.Comment{})
|
||||||
left := len(commentIDs)
|
if err != nil {
|
||||||
for left > 0 {
|
return nil, err
|
||||||
limit := db.DefaultMaxInSize
|
|
||||||
if left < limit {
|
|
||||||
limit = left
|
|
||||||
}
|
|
||||||
rows, err := db.GetEngine(ctx).
|
|
||||||
In("id", commentIDs[:limit]).
|
|
||||||
Rows(new(issues_model.Comment))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var comment issues_model.Comment
|
|
||||||
err = rows.Scan(&comment)
|
|
||||||
if err != nil {
|
|
||||||
rows.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
comments[comment.ID] = &comment
|
|
||||||
}
|
|
||||||
_ = rows.Close()
|
|
||||||
|
|
||||||
left -= limit
|
|
||||||
commentIDs = commentIDs[limit:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
failures := []int{}
|
failures := []int{}
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository
|
||||||
return v[i].Commits > v[j].Commits
|
return v[i].Commits > v[j].Commits
|
||||||
})
|
})
|
||||||
|
|
||||||
cnt := count
|
cnt := min(count, len(v))
|
||||||
if cnt > len(v) {
|
|
||||||
cnt = len(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v[:cnt], nil
|
return v[:cnt], nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ func (opts FindGPGKeyOptions) ToConds() builder.Cond {
|
||||||
cond = cond.And(builder.Eq{"primary_key_id": ""})
|
cond = cond.And(builder.Eq{"primary_key_id": ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
if opts.KeyID != "" {
|
if opts.KeyID != "" {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"forgejo.org/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
"forgejo.org/modules/util"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -458,7 +457,7 @@ epiDVQ==
|
||||||
func TestTryGetKeyIDFromSignature(t *testing.T) {
|
func TestTryGetKeyIDFromSignature(t *testing.T) {
|
||||||
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
|
assert.Empty(t, tryGetKeyIDFromSignature(&packet.Signature{}))
|
||||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||||
IssuerKeyId: util.ToPointer(uint64(0x38D1A3EADDBEA9C)),
|
IssuerKeyId: new(uint64(0x38D1A3EADDBEA9C)),
|
||||||
}))
|
}))
|
||||||
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
assert.Equal(t, "038D1A3EADDBEA9C", tryGetKeyIDFromSignature(&packet.Signature{
|
||||||
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
IssuerFingerprint: []uint8{0xb, 0x23, 0x24, 0xc7, 0xe6, 0xfe, 0x4f, 0x3a, 0x6, 0x26, 0xc1, 0x21, 0x3, 0x8d, 0x1a, 0x3e, 0xad, 0xdb, 0xea, 0x9c},
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,32 @@ func HandleCompositeErrorReason(handler llu.Handler, fset *token.FileSet, n *ast
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields are normally named
|
// fields are normally named
|
||||||
|
var reason ast.Expr
|
||||||
|
verified := false
|
||||||
for _, i := range n.Elts {
|
for _, i := range n.Elts {
|
||||||
if kve, ok := i.(*ast.KeyValueExpr); ok {
|
if kve, ok := i.(*ast.KeyValueExpr); ok {
|
||||||
ident, ok = kve.Key.(*ast.Ident)
|
ident, ok = kve.Key.(*ast.Ident)
|
||||||
if ok && ident.Name == "Reason" {
|
if !ok {
|
||||||
handler.HandleGoTrArgument(fset, kve.Value, "")
|
continue
|
||||||
|
}
|
||||||
|
switch ident.Name {
|
||||||
|
case "Reason":
|
||||||
|
reason = kve.Value
|
||||||
|
case "Verified":
|
||||||
|
if valueIdent, ok := kve.Value.(*ast.Ident); ok {
|
||||||
|
switch valueIdent.Name {
|
||||||
|
case "true":
|
||||||
|
verified = true
|
||||||
|
case "false":
|
||||||
|
verified = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handler.OnWarning(fset, i.Pos(), "unable to parse ObjectVerification field assignment")
|
handler.OnWarning(fset, i.Pos(), "unable to parse ObjectVerification field assignment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !verified && reason != nil {
|
||||||
|
handler.HandleGoTrArgument(fset, reason, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ type FindPublicKeyOptions struct {
|
||||||
|
|
||||||
func (opts FindPublicKeyOptions) ToConds() builder.Cond {
|
func (opts FindPublicKeyOptions) ToConds() builder.Cond {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID != 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
if opts.Fingerprint != "" {
|
if opts.Fingerprint != "" {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
- integ_id: 1
|
||||||
|
repo_id: 1
|
||||||
|
created_unix: 1772158384
|
||||||
|
- integ_id: 1
|
||||||
|
repo_id: 2
|
||||||
|
created_unix: 1772158384
|
||||||
|
- integ_id: 1
|
||||||
|
repo_id: 3
|
||||||
|
created_unix: 1772158384
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
- id: 1
|
||||||
|
user_id: 2
|
||||||
|
scope: all
|
||||||
|
resource_all_repos: false
|
||||||
|
issuer: https://example.org/
|
||||||
|
audience: https://forgejo.example.org/-/user/integration/abcdef123
|
||||||
|
claim_rules: "{}"
|
||||||
|
created_unix: 1777153359
|
||||||
|
updated_unix: 1777153359
|
||||||
|
|
@ -24,6 +24,10 @@ func init() {
|
||||||
db.RegisterModel(new(AccessTokenResourceRepo))
|
db.RegisterModel(new(AccessTokenResourceRepo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (atr *AccessTokenResourceRepo) GetTargetRepoID() int64 {
|
||||||
|
return atr.RepoID
|
||||||
|
}
|
||||||
|
|
||||||
func GetRepositoriesAccessibleWithToken(ctx context.Context, accessTokenID int64) ([]*AccessTokenResourceRepo, error) {
|
func GetRepositoriesAccessibleWithToken(ctx context.Context, accessTokenID int64) ([]*AccessTokenResourceRepo, error) {
|
||||||
var resources []*AccessTokenResourceRepo
|
var resources []*AccessTokenResourceRepo
|
||||||
err := db.GetEngine(ctx).
|
err := db.GetEngine(ctx).
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forgejo.org/models/perm"
|
"forgejo.org/models/perm"
|
||||||
|
|
@ -204,12 +205,7 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
|
||||||
|
|
||||||
// ContainsCategory checks if a list of categories contains a specific category
|
// ContainsCategory checks if a list of categories contains a specific category
|
||||||
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
|
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
|
||||||
for _, c := range categories {
|
return slices.Contains(categories, category)
|
||||||
if c == category {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScopeLevelFromAccessMode converts permission access mode to scope level
|
// GetScopeLevelFromAccessMode converts permission access mode to scope level
|
||||||
|
|
|
||||||
189
models/auth/authorized_integration.go
Normal file
189
models/auth/authorized_integration.go
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
|
gouuid "github.com/google/uuid"
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Authorized Integration allow users to define external systems which can generate JSON Web Tokens (JWTs) that
|
||||||
|
// Forgejo will trust in order to perform API access on behalf of a user defined by the UserID field.
|
||||||
|
//
|
||||||
|
// When a JWT is received by Forgejo, the issuer (iss) and audience (aud) claims are used to lookup an authorized
|
||||||
|
// integration with an exact match. Together these fields serve as a unique key for the authorized issuer. Duplicates
|
||||||
|
// cannot be permitted because we would not know which user to authenticate the JWT as.
|
||||||
|
type AuthorizedIntegration struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
|
||||||
|
UserID int64 `xorm:"NOT NULL REFERENCES(user, id)"`
|
||||||
|
Scope AccessTokenScope `xorm:"NOT NULL"`
|
||||||
|
ResourceAllRepos bool `xorm:"NOT NULL"` // flag for whether AuthorizedIntegrationResourceRepo instances will limit the resources this access token can access (false) or won't limit them (true).
|
||||||
|
|
||||||
|
Name string // short name for lists of authorized integrations
|
||||||
|
Description string `xorm:"LONGTEXT"` // long description, optional to document relevant details of the integration
|
||||||
|
|
||||||
|
// Exact-match `iss` claim of the JWT
|
||||||
|
Issuer string `xorm:"NOT NULL UNIQUE(s)"`
|
||||||
|
// Exact-match `aud` claim of the JWT
|
||||||
|
Audience string `xorm:"NOT NULL UNIQUE(s)"`
|
||||||
|
ClaimRules *ClaimRules `xorm:"NOT NULL JSON"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"NOT NULL created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"NOT NULL updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(AuthorizedIntegration))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An [AuthorizedIntegration] can validate the claims in a JWT against a set of rules defined by this structure.
|
||||||
|
//
|
||||||
|
// JWTs can contain any number of claims, which are represented as a JSON object. A small number of common claims are
|
||||||
|
// described in RFC7519 (sec 4.1) which defines JWTs, but most claims are entirely arbitrarily defined by the JWT
|
||||||
|
// issuer.
|
||||||
|
//
|
||||||
|
// For example, eg. a claim may be {"sub": "repo:coolguy/forgejo-runner-testrepo:pull_request"} indicating that an OIDC
|
||||||
|
// token was received from an Actions execution in a specific repo on a specific event.
|
||||||
|
//
|
||||||
|
// Validating the claims from a JWT issuer is a critical part of creating a secure [AuthorizedIssuer]. For example,
|
||||||
|
// assume that we receive a JWT from a public hosting platform like Codeberg. We will validate that it is a claim
|
||||||
|
// created by the correct Issuer, Codeberg -- but anyone can do that through Forgejo Actions. We will validate that it
|
||||||
|
// has the correct audience -- but that's an *input* to Forgejo Actions, so anyone can create a claim on Codeberg with
|
||||||
|
// an arbitrary audience. The rest of the claims contain the critical information about who ran a Forgejo Action, on
|
||||||
|
// which repository, and in response to which events, and those must be validated to ensure that an authorized issuer is
|
||||||
|
// correctly authorized.
|
||||||
|
//
|
||||||
|
// Following that an example, a minimum claim rule that would be required for securely using Forgejo Actions would be
|
||||||
|
// something like:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "rules": [{
|
||||||
|
// "claim": "sub",
|
||||||
|
// "comparison": "eq",
|
||||||
|
// "value": "repo:forgejo/website:pull_request"
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This defines a single rule which says that the `sub` claim must be exactly equal to
|
||||||
|
// "repo:forgejo/website:pull_request". Forgejo Actions would generate this subject when an Action is running on the
|
||||||
|
// repo forgejo/website in response to the pull_request event.
|
||||||
|
//
|
||||||
|
// Some JWT claims are JSON objects. The [ClaimNested] comparison operator can be used to define rules that inspect the
|
||||||
|
// object within a claim. For example, AWS STS generates a claim "https://sts.amazonaws.com/": {...} with values inside
|
||||||
|
// an object, like "aws_account". A nested claim can inspect those values:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "rules":[{
|
||||||
|
// "claim": "https://sts.amazonaws.com/",
|
||||||
|
// "compare": "nest",
|
||||||
|
// "nested": {"rules":[
|
||||||
|
// {"claim": "aws_account", "compare": "eq", "value": "1234567890"},
|
||||||
|
// {"claim": "lambda_source_function_arn", "compare": "eq", "value": "arn:aws:lambda:ca-central-1:1234567890:function:forgejo-oidc-accepting-test"}
|
||||||
|
// ]}
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ]}
|
||||||
|
//
|
||||||
|
// This defines a rule that looks into the "https://sts..." claim and verifies the "aws_account" and
|
||||||
|
// "lambda_source_function_arn" keys match specific known values.
|
||||||
|
type ClaimRules struct {
|
||||||
|
Rules []ClaimRule `json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines a single rule that will check the value of one JWT claim.
|
||||||
|
type ClaimRule struct {
|
||||||
|
// The target claim, eg. "sub"
|
||||||
|
Claim string `json:"claim"`
|
||||||
|
// Comparison rule to use on this claim
|
||||||
|
Comparison ClaimComparison `json:"compare"`
|
||||||
|
|
||||||
|
// For Comparison of ClaimEqual or ClaimGlob, the specific value or glob to match against
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
|
||||||
|
// For Comparison of ClaimIn or ClaimGlobIn, an array of values to match against
|
||||||
|
Values []string `json:"values,omitempty"`
|
||||||
|
|
||||||
|
// For ClaimNested, the rules to apply to the nested object
|
||||||
|
Nested *ClaimRules `json:"nested,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClaimComparison string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClaimEqual ClaimComparison = "eq" // exactly equal claim
|
||||||
|
ClaimIn ClaimComparison = "in" // exactly equal any of the options in a list
|
||||||
|
ClaimGlob ClaimComparison = "glob" // glob match complete claim string
|
||||||
|
ClaimGlobIn ClaimComparison = "glob-in" // glob match any of the options in a list
|
||||||
|
ClaimNested ClaimComparison = "nest" // recurse into a claim that is an map[string]any with it's own data fields
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAuthorizedIntegration(ctx context.Context, issuer, audience string) (*AuthorizedIntegration, error) {
|
||||||
|
var ai AuthorizedIntegration
|
||||||
|
found, err := db.GetEngine(ctx).Where("issuer = ? AND audience = ?", issuer, audience).Get(&ai)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !found {
|
||||||
|
return nil, util.ErrNotExist
|
||||||
|
}
|
||||||
|
return &ai, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertAuthorizedIntegration(ctx context.Context, ai *AuthorizedIntegration) error {
|
||||||
|
if ai.Audience != "" {
|
||||||
|
return errors.New("audience cannot be provided, and must be generated by NewAuthorizedIntegration")
|
||||||
|
} else if err := ai.generateAudience(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := db.GetEngine(ctx).Insert(ai)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bump the UpdatedUnix field of this authorized integration to now, tracking when it was last used for authentication.
|
||||||
|
// To reduce database write workload, this is only tracked by one-minute intervals -- the UPDATE statement conditionally
|
||||||
|
// avoids writes.
|
||||||
|
func (ai *AuthorizedIntegration) UpdateLastUsed(ctx context.Context) error {
|
||||||
|
newTime := timeutil.TimeStampNow()
|
||||||
|
cnt, err := db.GetEngine(ctx).
|
||||||
|
Table(&AuthorizedIntegration{}).
|
||||||
|
Where(builder.Eq{"id": ai.ID}).
|
||||||
|
Where(builder.Lt{"updated_unix": newTime.AddDuration(-1 * time.Minute)}).
|
||||||
|
NoAutoTime().
|
||||||
|
Update(map[string]any{"updated_unix": newTime})
|
||||||
|
if cnt == 1 {
|
||||||
|
ai.UpdatedUnix = newTime
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates the `aud` claim that the remote JWT generator must use to match this authorized integration. The `aud`
|
||||||
|
// claim is an arbitrary value in a JWT claim, but Forgejo is faced with a few hard and soft requirements:
|
||||||
|
//
|
||||||
|
// - Hard requirement: each authorized integration must have a unique `aud`, as it is used to find the DB record that
|
||||||
|
// authenticates a request.
|
||||||
|
// - If authentication is failing, being able to inspect the `aud` claim can be useful to identify the intent.
|
||||||
|
// - Inspection should have a stable meaning -- eg. if it included the username, and the user was renamed, the `aud`
|
||||||
|
// value which can't be changed would continue to reference the old username causing confusion when inspecting it.
|
||||||
|
// - Forgejo & GitHub Actions uses a URL $ACTIONS_ID_TOKEN_REQUEST_URL&audience=... to generate a JWT for the running
|
||||||
|
// action, so it should only consist of safe characters for URL encoding.
|
||||||
|
// - It should be relatively short, as it's encoded into the JWT and increases its size.
|
||||||
|
//
|
||||||
|
// Meeting these requirements decently well is a combination of the owner's ID, a guid, and a "u:" prefix that makes the
|
||||||
|
// fact that it's an `aud` claim value a little bit identifiable.
|
||||||
|
func (ai *AuthorizedIntegration) generateAudience() error {
|
||||||
|
if ai.UserID == 0 {
|
||||||
|
return errors.New("UserID must be initialized")
|
||||||
|
}
|
||||||
|
ai.Audience = fmt.Sprintf("u:%d:%s", ai.UserID, gouuid.New().String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
56
models/auth/authorized_integration_resource_repo.go
Normal file
56
models/auth/authorized_integration_resource_repo.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents a many-to-many join table which indicates specific repositories (RepoID) that can be accessed by an
|
||||||
|
// authorized integration (IntegID). An authorized integrations's ResourceAllRepos field must be false for records in
|
||||||
|
// this table to become active.
|
||||||
|
//
|
||||||
|
// Model name is shortend (from AuthorizedIntegrationResourceRepo) to accomodate recreate-tables + MySQL, where the
|
||||||
|
// "tmp_recreate_" + foreign key index name would exceed the max identifier length.
|
||||||
|
type AuthorizedIntegResourceRepo struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
IntegID int64 `xorm:"NOT NULL REFERENCES(authorized_integration, id)"` // field name shortened (AuthorizationIntegrationID) for max identifier length
|
||||||
|
RepoID int64 `xorm:"NOT NULL REFERENCES(repository, id)"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(AuthorizedIntegResourceRepo))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (air *AuthorizedIntegResourceRepo) GetTargetRepoID() int64 {
|
||||||
|
return air.RepoID
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepositoriesAccessibleWithIntegration(ctx context.Context, aiID int64) ([]*AuthorizedIntegResourceRepo, error) {
|
||||||
|
var resources []*AuthorizedIntegResourceRepo
|
||||||
|
err := db.GetEngine(ctx).
|
||||||
|
Where("integ_id = ?", aiID).
|
||||||
|
Find(&resources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertAuthorizedIntegrationResourceRepos(ctx context.Context, aiID int64, resources []*AuthorizedIntegResourceRepo) error {
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
for _, resourceRepo := range resources {
|
||||||
|
resourceRepo.IntegID = aiID
|
||||||
|
if err := db.Insert(ctx, resourceRepo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
91
models/auth/authorized_integration_resource_repo_test.go
Normal file
91
models/auth/authorized_integration_resource_repo_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package auth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
"forgejo.org/models/unittest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetRepositoriesAccessibleWithIntegration(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("models/auth/TestGetRepositoriesAccessibleWithIntegration")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
t.Run("No Resources", func(t *testing.T) {
|
||||||
|
resources, err := auth_model.GetRepositoriesAccessibleWithIntegration(t.Context(), 999)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, resources)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Has Resources", func(t *testing.T) {
|
||||||
|
resources, err := auth_model.GetRepositoriesAccessibleWithIntegration(t.Context(), 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resources, 3)
|
||||||
|
|
||||||
|
// Verify all expected repo IDs are present
|
||||||
|
repoIDs := make([]int64, len(resources))
|
||||||
|
for i, res := range resources {
|
||||||
|
repoIDs[i] = res.RepoID
|
||||||
|
}
|
||||||
|
assert.Contains(t, repoIDs, int64(1))
|
||||||
|
assert.Contains(t, repoIDs, int64(2))
|
||||||
|
assert.Contains(t, repoIDs, int64(3))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertAuthorizedIntegration(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
ai1 := makeAuthorizedIntegration(t)
|
||||||
|
ai2 := makeAuthorizedIntegration(t)
|
||||||
|
ai3 := makeAuthorizedIntegration(t)
|
||||||
|
|
||||||
|
t.Run("blank insert", func(t *testing.T) {
|
||||||
|
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai1.ID, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple insert", func(t *testing.T) {
|
||||||
|
resRepo1 := &auth_model.AuthorizedIntegResourceRepo{
|
||||||
|
IntegID: ai2.ID,
|
||||||
|
RepoID: 1,
|
||||||
|
}
|
||||||
|
resRepo3 := &auth_model.AuthorizedIntegResourceRepo{
|
||||||
|
IntegID: ai2.ID,
|
||||||
|
RepoID: 3,
|
||||||
|
}
|
||||||
|
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai2.ID,
|
||||||
|
[]*auth_model.AuthorizedIntegResourceRepo{resRepo1, resRepo3})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai2.ID}, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("in tx", func(t *testing.T) {
|
||||||
|
// Pre-condition: count is 0.
|
||||||
|
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai3.ID}, 0)
|
||||||
|
|
||||||
|
// Verify that InsertAuthorizedIntegrationResourceRepos performs inserts in a TX by having a second one with an invalid
|
||||||
|
// RepoID, causing a foreign key violation
|
||||||
|
resRepo1 := &auth_model.AuthorizedIntegResourceRepo{
|
||||||
|
IntegID: ai3.ID,
|
||||||
|
RepoID: 1,
|
||||||
|
}
|
||||||
|
resRepo3 := &auth_model.AuthorizedIntegResourceRepo{
|
||||||
|
IntegID: ai3.ID,
|
||||||
|
RepoID: 30000, // invalid
|
||||||
|
}
|
||||||
|
err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai3.ID,
|
||||||
|
[]*auth_model.AuthorizedIntegResourceRepo{resRepo1, resRepo3})
|
||||||
|
require.ErrorContains(t, err, "foreign key")
|
||||||
|
|
||||||
|
// Count remains 0; the first record was not inserted.
|
||||||
|
unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai3.ID}, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
106
models/auth/authorized_integration_test.go
Normal file
106
models/auth/authorized_integration_test.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package auth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
auth_model "forgejo.org/models/auth"
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/models/unittest"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
"forgejo.org/modules/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeAuthorizedIntegration(t *testing.T) *auth_model.AuthorizedIntegration {
|
||||||
|
t.Helper()
|
||||||
|
ai := &auth_model.AuthorizedIntegration{
|
||||||
|
UserID: 2,
|
||||||
|
Scope: auth_model.AccessTokenScopeAll,
|
||||||
|
ResourceAllRepos: true,
|
||||||
|
Issuer: "https://example.org/",
|
||||||
|
ClaimRules: &auth_model.ClaimRules{},
|
||||||
|
}
|
||||||
|
require.NoError(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai))
|
||||||
|
return ai
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAuthorizedIntegration(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ai := makeAuthorizedIntegration(t)
|
||||||
|
|
||||||
|
get, err := auth_model.GetAuthorizedIntegration(t.Context(), "abc", "123")
|
||||||
|
require.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
assert.Nil(t, get)
|
||||||
|
|
||||||
|
get, err = auth_model.GetAuthorizedIntegration(t.Context(), ai.Issuer, ai.Audience)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, get)
|
||||||
|
assert.Equal(t, ai.ID, get.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizedIntegrationUpdateLastUsed(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
ai := makeAuthorizedIntegration(t)
|
||||||
|
ai.UpdatedUnix = 0
|
||||||
|
cnt, err := db.GetEngine(t.Context()).ID(ai.ID).Cols("updated_unix").NoAutoTime().Update(ai)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, cnt)
|
||||||
|
|
||||||
|
timeutil.MockSet(time.Unix(1777130023, 0))
|
||||||
|
defer timeutil.MockUnset()
|
||||||
|
|
||||||
|
assert.EqualValues(t, 0, ai.UpdatedUnix)
|
||||||
|
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||||
|
assert.EqualValues(t, 1777130023, ai.UpdatedUnix) // object field updated
|
||||||
|
assert.EqualValues(t, 1777130023, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix)
|
||||||
|
|
||||||
|
// nearly immediate redo should have same timestamp due to the 1 minute deduplication:
|
||||||
|
timeutil.MockSet(time.Unix(1777130025, 0))
|
||||||
|
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||||
|
assert.EqualValues(t, 1777130023, ai.UpdatedUnix) // object field not updated
|
||||||
|
assert.EqualValues(t, 1777130023, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix) // database field not updated
|
||||||
|
|
||||||
|
// but if it's a little while later..
|
||||||
|
timeutil.MockSet(time.Unix(1777131139, 0))
|
||||||
|
require.NoError(t, ai.UpdateLastUsed(t.Context()))
|
||||||
|
assert.EqualValues(t, 1777131139, ai.UpdatedUnix) // object field updated
|
||||||
|
assert.EqualValues(t, 1777131139, unittest.AssertExistsAndLoadBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID}).UpdatedUnix) // database field updated
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAuthorizedIntegration(t *testing.T) {
|
||||||
|
ai := &auth_model.AuthorizedIntegration{
|
||||||
|
UserID: 2,
|
||||||
|
Scope: auth_model.AccessTokenScopeAll,
|
||||||
|
ResourceAllRepos: true,
|
||||||
|
Issuer: "https://example.org/",
|
||||||
|
ClaimRules: &auth_model.ClaimRules{},
|
||||||
|
}
|
||||||
|
require.NoError(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai))
|
||||||
|
assert.Contains(t, ai.Audience, "u:2:")
|
||||||
|
|
||||||
|
ai = &auth_model.AuthorizedIntegration{
|
||||||
|
UserID: 2,
|
||||||
|
Scope: auth_model.AccessTokenScopeAll,
|
||||||
|
ResourceAllRepos: true,
|
||||||
|
Issuer: "https://example.org/",
|
||||||
|
Audience: "I made my own audience",
|
||||||
|
ClaimRules: &auth_model.ClaimRules{},
|
||||||
|
}
|
||||||
|
require.ErrorContains(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai), "audience cannot be provided")
|
||||||
|
|
||||||
|
ai = &auth_model.AuthorizedIntegration{
|
||||||
|
// Forgot to set UserID
|
||||||
|
Scope: auth_model.AccessTokenScopeAll,
|
||||||
|
ResourceAllRepos: true,
|
||||||
|
Issuer: "https://example.org/",
|
||||||
|
ClaimRules: &auth_model.ClaimRules{},
|
||||||
|
}
|
||||||
|
require.ErrorContains(t, auth_model.InsertAuthorizedIntegration(t.Context(), ai), "UserID must be initialized")
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -151,9 +152,9 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
|
||||||
contains := func(s string) bool {
|
contains := func(s string) bool {
|
||||||
s = strings.TrimSuffix(strings.ToLower(s), "/")
|
s = strings.TrimSuffix(util.ToUpperASCII(s), "/")
|
||||||
for _, u := range app.RedirectURIs {
|
for _, u := range app.RedirectURIs {
|
||||||
if strings.TrimSuffix(strings.ToLower(u), "/") == s {
|
if strings.TrimSuffix(util.ToUpperASCII(u), "/") == s {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -408,26 +409,41 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL
|
||||||
return redirect, err
|
return redirect, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate deletes the auth code from the database to invalidate this code
|
// Invalidate deletes the auth code from the database to invalidate this code.
|
||||||
|
// It returns an error if the code was already invalidated (i.e., no rows were deleted),
|
||||||
|
// which prevents authorization code replay attacks.
|
||||||
func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error {
|
func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error {
|
||||||
_, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
|
affected, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return fmt.Errorf("authorization code already used or does not exist")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
|
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE
|
||||||
|
// implementation. If a code challenge was set during authorization, a valid verifier MUST be provided.
|
||||||
func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
|
func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
|
||||||
|
// If no PKCE was used during authorization, no verifier is needed.
|
||||||
|
if code.CodeChallengeMethod == "" && code.CodeChallenge == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// A challenge was set but no verifier provided: reject outright, no comparison or hashing is required.
|
||||||
|
if verifier == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
switch code.CodeChallengeMethod {
|
switch code.CodeChallengeMethod {
|
||||||
case "S256":
|
case "S256":
|
||||||
// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
|
// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
|
||||||
h := sha256.Sum256([]byte(verifier))
|
h := sha256.Sum256([]byte(verifier))
|
||||||
hashedVerifier := base64.RawURLEncoding.EncodeToString(h[:])
|
hashedVerifier := base64.RawURLEncoding.EncodeToString(h[:])
|
||||||
return hashedVerifier == code.CodeChallenge
|
return subtle.ConstantTimeCompare([]byte(hashedVerifier), []byte(code.CodeChallenge)) == 1
|
||||||
case "plain":
|
case "plain":
|
||||||
return verifier == code.CodeChallenge
|
return subtle.ConstantTimeCompare([]byte(verifier), []byte(code.CodeChallenge)) == 1
|
||||||
case "":
|
|
||||||
return true
|
|
||||||
default:
|
default:
|
||||||
// unsupported method -> return false
|
// unsupported or empty method with a non-empty challenge -> reject
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -490,22 +506,25 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncreaseCounter increases the counter and updates the grant
|
// IncreaseCounter increases the counter and updates the grant
|
||||||
|
// IncreaseCounter atomically increments the counter only if it still matches
|
||||||
|
// the value loaded into this grant. Returns an error if the counter was already
|
||||||
|
// changed by a concurrent request (refresh token replay).
|
||||||
func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
|
func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
|
||||||
_, err := db.GetEngine(ctx).ID(grant.ID).Incr("counter").Update(new(OAuth2Grant))
|
affected, err := db.GetEngine(ctx).Where("id = ? AND counter = ?", grant.ID, grant.Counter).
|
||||||
|
Incr("counter").Update(new(OAuth2Grant))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updatedGrant, err := GetOAuth2GrantByID(ctx, grant.ID)
|
if affected == 0 {
|
||||||
if err != nil {
|
return fmt.Errorf("grant counter changed unexpectedly (possible replay)")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
grant.Counter = updatedGrant.Counter
|
grant.Counter++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScopeContains returns true if the grant scope contains the specified scope
|
// ScopeContains returns true if the grant scope contains the specified scope
|
||||||
func (grant *OAuth2Grant) ScopeContains(scope string) bool {
|
func (grant *OAuth2Grant) ScopeContains(scope string) bool {
|
||||||
for _, currentScope := range strings.Split(grant.Scope, " ") {
|
for currentScope := range strings.SplitSeq(grant.Scope, " ") {
|
||||||
if scope == currentScope {
|
if scope == currentScope {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,13 @@ func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
|
||||||
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
|
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2Application_ContainsRedirect_Normalization(t *testing.T) {
|
||||||
|
app := &auth_model.OAuth2Application{RedirectURIs: []string{"https://website.com"}}
|
||||||
|
assert.True(t, app.ContainsRedirectURI("https://website.com"))
|
||||||
|
assert.True(t, app.ContainsRedirectURI("https://webSITE.com")) // ascii uppercase I
|
||||||
|
assert.False(t, app.ContainsRedirectURI("https://websİte.com")) // U+0130 as I, Latin Capital Letter I with Dot Above
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
|
func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
|
||||||
|
|
@ -147,9 +154,18 @@ func TestGetOAuth2GrantByID(t *testing.T) {
|
||||||
func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
|
func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
|
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
|
||||||
|
|
||||||
|
// First increment succeeds
|
||||||
require.NoError(t, grant.IncreaseCounter(db.DefaultContext))
|
require.NoError(t, grant.IncreaseCounter(db.DefaultContext))
|
||||||
assert.Equal(t, int64(2), grant.Counter)
|
assert.Equal(t, int64(2), grant.Counter)
|
||||||
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
|
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
|
||||||
|
|
||||||
|
// Simulate a stale grant (counter still 1): must fail (concurrent replay)
|
||||||
|
grant.Counter = 1
|
||||||
|
require.Error(t, grant.IncreaseCounter(db.DefaultContext), "stale counter must be rejected")
|
||||||
|
|
||||||
|
// Counter in DB should be unchanged
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2Grant_ScopeContains(t *testing.T) {
|
func TestOAuth2Grant_ScopeContains(t *testing.T) {
|
||||||
|
|
@ -232,12 +248,33 @@ func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.False(t, code.ValidateCodeChallenge("foiwgjioriogeiogjerger"))
|
assert.False(t, code.ValidateCodeChallenge("foiwgjioriogeiogjerger"))
|
||||||
|
|
||||||
// test no code challenge
|
// test no PKCE at all (no challenge set, no verifier needed)
|
||||||
|
code = &auth_model.OAuth2AuthorizationCode{
|
||||||
|
CodeChallengeMethod: "",
|
||||||
|
CodeChallenge: "",
|
||||||
|
}
|
||||||
|
assert.True(t, code.ValidateCodeChallenge(""))
|
||||||
|
|
||||||
|
// test PKCE required: challenge was set but verifier is empty
|
||||||
|
code = &auth_model.OAuth2AuthorizationCode{
|
||||||
|
CodeChallengeMethod: "S256",
|
||||||
|
CodeChallenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg",
|
||||||
|
}
|
||||||
|
assert.False(t, code.ValidateCodeChallenge(""), "PKCE required: S256 challenge set but empty verifier must be rejected")
|
||||||
|
|
||||||
|
code = &auth_model.OAuth2AuthorizationCode{
|
||||||
|
CodeChallengeMethod: "plain",
|
||||||
|
CodeChallenge: "test123",
|
||||||
|
}
|
||||||
|
assert.False(t, code.ValidateCodeChallenge(""), "PKCE required: plain challenge set but empty verifier must be rejected")
|
||||||
|
|
||||||
|
// test challenge stored but method empty (malformed: should reject)
|
||||||
code = &auth_model.OAuth2AuthorizationCode{
|
code = &auth_model.OAuth2AuthorizationCode{
|
||||||
CodeChallengeMethod: "",
|
CodeChallengeMethod: "",
|
||||||
CodeChallenge: "foierjiogerogerg",
|
CodeChallenge: "foierjiogerogerg",
|
||||||
}
|
}
|
||||||
assert.True(t, code.ValidateCodeChallenge(""))
|
assert.False(t, code.ValidateCodeChallenge(""), "challenge present with empty method must be rejected")
|
||||||
|
assert.False(t, code.ValidateCodeChallenge("foierjiogerogerg"), "challenge present with empty method must be rejected even with matching verifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) {
|
func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) {
|
||||||
|
|
@ -262,6 +299,20 @@ func TestOAuth2AuthorizationCode_Invalidate(t *testing.T) {
|
||||||
unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
|
unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2AuthorizationCode_Invalidate_DoubleUse(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
code := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
|
||||||
|
|
||||||
|
// First invalidation should succeed
|
||||||
|
require.NoError(t, code.Invalidate(db.DefaultContext))
|
||||||
|
unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
|
||||||
|
|
||||||
|
// Second invalidation of the same code must fail (replay prevention)
|
||||||
|
err := code.Invalidate(db.DefaultContext)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "authorization code already used")
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuth2AuthorizationCode_TableName(t *testing.T) {
|
func TestOAuth2AuthorizationCode_TableName(t *testing.T) {
|
||||||
assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName())
|
assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ var registeredConfigs = map[Type]func() Config{}
|
||||||
|
|
||||||
// RegisterTypeConfig register a config for a provided type
|
// RegisterTypeConfig register a config for a provided type
|
||||||
func RegisterTypeConfig(typ Type, exemplar Config) {
|
func RegisterTypeConfig(typ Type, exemplar Config) {
|
||||||
if reflect.TypeOf(exemplar).Kind() == reflect.Ptr {
|
if reflect.TypeOf(exemplar).Kind() == reflect.Pointer {
|
||||||
// Pointer:
|
// Pointer:
|
||||||
registeredConfigs[typ] = func() Config {
|
registeredConfigs[typ] = func() Config {
|
||||||
return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config)
|
return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
@ -268,6 +271,91 @@ func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err e
|
||||||
return &bean, true, nil
|
return &bean, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieves multiple objects with database queries similar to an xorm `.In(idField, idList)`. idField must be a unique
|
||||||
|
// field on the database table, as a map[id]obj is returned and the usage of a non-unique field would result in objects
|
||||||
|
// being overwritten in the map.
|
||||||
|
//
|
||||||
|
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
|
||||||
|
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
|
||||||
|
// limitations or query length limitations in the database engine.
|
||||||
|
func GetByIDs[Bean any, Id comparable](ctx context.Context, idField string, idList []Id, bean *Bean) (map[Id]*Bean, error) {
|
||||||
|
retval := make(map[Id]*Bean, len(idList))
|
||||||
|
if len(idList) == 0 {
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := TableInfo(bean)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var structFieldName string
|
||||||
|
for _, c := range table.Columns() {
|
||||||
|
if c.Name == idField {
|
||||||
|
structFieldName = c.FieldName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if structFieldName == "" {
|
||||||
|
return nil, fmt.Errorf("unable to identify struct field for id field %s", idField)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idChunk := range slices.Chunk(idList, DefaultMaxInSize) {
|
||||||
|
beans := make([]*Bean, 0, len(idChunk))
|
||||||
|
if err := GetEngine(ctx).In(idField, idChunk).Find(&beans); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, bean := range beans {
|
||||||
|
retval[extractFieldValue(bean, structFieldName).(Id)] = bean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves multiple objects with database queries similar to an xorm `.In(field, valueList)`. Similar to GetByIDs,
|
||||||
|
// except that a map[Id][]*Bean is returned as the field value is not assumed to be a unique value -- if there are
|
||||||
|
// multiple rows in the table for each value, all of them are returned.
|
||||||
|
//
|
||||||
|
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
|
||||||
|
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
|
||||||
|
// limitations or query length limitations in the database engine.
|
||||||
|
func GetByFieldIn[Bean any, Id comparable](ctx context.Context, field string, valueList []Id, bean *Bean) (map[Id][]*Bean, error) {
|
||||||
|
retval := make(map[Id][]*Bean, len(valueList))
|
||||||
|
if len(valueList) == 0 {
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := TableInfo(bean)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var structFieldName string
|
||||||
|
for _, c := range table.Columns() {
|
||||||
|
if c.Name == field {
|
||||||
|
structFieldName = c.FieldName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if structFieldName == "" {
|
||||||
|
return nil, fmt.Errorf("unable to identify struct field for field %s", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idChunk := range slices.Chunk(valueList, DefaultMaxInSize) {
|
||||||
|
beans := make([]*Bean, 0, len(idChunk))
|
||||||
|
if err := GetEngine(ctx).In(field, idChunk).Find(&beans); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, bean := range beans {
|
||||||
|
fieldValue := extractFieldValue(bean, structFieldName).(Id)
|
||||||
|
retval[fieldValue] = append(retval[fieldValue], bean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
|
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
|
||||||
if !cond.IsValid() {
|
if !cond.IsValid() {
|
||||||
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
|
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
|
||||||
|
|
@ -416,3 +504,68 @@ func inTransaction(ctx context.Context) (*xorm.Session, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RetryConfig struct {
|
||||||
|
ErrorIs []error
|
||||||
|
AttemptCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNestedRetryTxFailure = errors.New("(nested)")
|
||||||
|
|
||||||
|
type nestedRetryTxState int
|
||||||
|
|
||||||
|
var nestedRetryTx nestedRetryTxState
|
||||||
|
|
||||||
|
// Execute the given function in a transaction. RetryConfig will retry the function on an error, if it matches the
|
||||||
|
// ErrorIs parameter, up to the total of AttemptCount number of tries. RetryTx cannot be invoked when already within a
|
||||||
|
// transaction and will return an error immediately.
|
||||||
|
//
|
||||||
|
// ErrNestedRetryTxFailure is an error type that will occur when RetryTx is nested within each other, and indicates that
|
||||||
|
// an inner RetryTx encountered an error that matched its error list.
|
||||||
|
func RetryTx(ctx context.Context, config RetryConfig, f func(ctx context.Context) error) error {
|
||||||
|
matchError := func(err error) bool {
|
||||||
|
for _, possibleError := range config.ErrorIs {
|
||||||
|
if errors.Is(err, possibleError) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept `ErrNestedRetryTxFailure` as error to retry on, means that a nested
|
||||||
|
// RetryTx indicated to retry the whole transaction.
|
||||||
|
config.ErrorIs = append(config.ErrorIs, ErrNestedRetryTxFailure)
|
||||||
|
|
||||||
|
withinRetryTx, present := ctx.Value(nestedRetryTx).(bool)
|
||||||
|
if present && withinRetryTx {
|
||||||
|
// If a caller already started `RetryTx`, then we assume we don't have to actually perform retries here -- we
|
||||||
|
// can attempt the requested function once, and if an error is returned that matches the configured error list,
|
||||||
|
// we'll return that error + ErrNestedRetryTxFailure wrapping.
|
||||||
|
err := f(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if matchError(err) {
|
||||||
|
return fmt.Errorf("nested RetryTx; internal Tx failed with error that won't be retried: %w %w", err, ErrNestedRetryTxFailure)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if InTransaction(ctx) {
|
||||||
|
return errors.New("unsupported operation: attempted to use RetryTx while already within a transaction")
|
||||||
|
} else if config.AttemptCount == 0 {
|
||||||
|
return errors.New("unsupported operation: attempted to use RetryTx with 0 attempts")
|
||||||
|
}
|
||||||
|
|
||||||
|
innerCtx := context.WithValue(ctx, nestedRetryTx, true)
|
||||||
|
var lastError error
|
||||||
|
for range config.AttemptCount {
|
||||||
|
err := WithTx(innerCtx, f)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if !matchError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastError = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("retry tx failed after %d attempts; last error: %w", config.AttemptCount, lastError)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,3 +220,103 @@ func TestAfterTx(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRetryTx(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{AttemptCount: 1}, func(ctx context.Context) error {
|
||||||
|
assert.True(t, db.InTransaction(ctx))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fail constantly", func(t *testing.T) {
|
||||||
|
attemptCount := 0
|
||||||
|
testError := errors.New("hello")
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
ErrorIs: []error{testError},
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
attemptCount++
|
||||||
|
return testError
|
||||||
|
})
|
||||||
|
require.ErrorIs(t, err, testError)
|
||||||
|
require.ErrorContains(t, err, "2 attempts")
|
||||||
|
assert.Equal(t, 2, attemptCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fail w/ non retriable error", func(t *testing.T) {
|
||||||
|
attemptCount := 0
|
||||||
|
testError := errors.New("hello")
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
ErrorIs: []error{},
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
attemptCount++
|
||||||
|
return testError
|
||||||
|
})
|
||||||
|
require.ErrorIs(t, err, testError)
|
||||||
|
assert.Equal(t, 1, attemptCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("succeed on retry", func(t *testing.T) {
|
||||||
|
attemptCount := 0
|
||||||
|
testError := errors.New("hello")
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
ErrorIs: []error{testError},
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
attemptCount++
|
||||||
|
if attemptCount == 1 {
|
||||||
|
return testError
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, attemptCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nested", func(t *testing.T) {
|
||||||
|
attemptCount := 0
|
||||||
|
testError := errors.New("hello")
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
attemptCount++
|
||||||
|
return db.RetryTx(ctx, db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
ErrorIs: []error{testError},
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
if attemptCount == 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return testError
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, attemptCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inner RetryTx decides on error", func(t *testing.T) {
|
||||||
|
attemptCount := 0
|
||||||
|
testError := errors.New("hello")
|
||||||
|
err := db.RetryTx(t.Context(), db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
ErrorIs: []error{},
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
attemptCount++
|
||||||
|
return db.RetryTx(ctx, db.RetryConfig{
|
||||||
|
AttemptCount: 2,
|
||||||
|
}, func(ctx context.Context) error {
|
||||||
|
if attemptCount == 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return testError
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
require.ErrorIs(t, err, testError)
|
||||||
|
assert.Equal(t, 1, attemptCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx contex
|
||||||
|
|
||||||
func extractFieldValue(bean any, fieldName string) any {
|
func extractFieldValue(bean any, fieldName string) any {
|
||||||
v := reflect.ValueOf(bean)
|
v := reflect.ValueOf(bean)
|
||||||
if v.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Pointer {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
field := v.FieldByName(fieldName)
|
field := v.FieldByName(fieldName)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultMaxInSize represents default variables number on IN () in SQL
|
// DefaultMaxInSize represents default variables number on IN () in SQL
|
||||||
DefaultMaxInSize = 50
|
DefaultMaxInSize = 500
|
||||||
defaultFindSliceSize = 10
|
defaultFindSliceSize = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package db
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
|
@ -114,10 +115,8 @@ func IsUsableName(names, patterns []string, name string) error {
|
||||||
return ErrNameEmpty
|
return ErrNameEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range names {
|
if slices.Contains(names, name) {
|
||||||
if name == names[i] {
|
return ErrNameReserved{name}
|
||||||
return ErrNameReserved{name}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pat := range patterns {
|
for _, pat := range patterns {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,7 @@ func (f *file) readAt(fileMeta *DbfsMeta, offset int64, p []byte) (n int, err er
|
||||||
blobPos := int(offset % f.blockSize)
|
blobPos := int(offset % f.blockSize)
|
||||||
blobOffset := offset - int64(blobPos)
|
blobOffset := offset - int64(blobPos)
|
||||||
blobRemaining := int(f.blockSize) - blobPos
|
blobRemaining := int(f.blockSize) - blobPos
|
||||||
needRead := len(p)
|
needRead := min(len(p), blobRemaining)
|
||||||
if needRead > blobRemaining {
|
|
||||||
needRead = blobRemaining
|
|
||||||
}
|
|
||||||
if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize {
|
if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize {
|
||||||
needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos))
|
needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos))
|
||||||
}
|
}
|
||||||
|
|
@ -66,14 +63,8 @@ func (f *file) readAt(fileMeta *DbfsMeta, offset int64, p []byte) (n int, err er
|
||||||
blobData = nil
|
blobData = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
canCopy := len(blobData) - blobPos
|
canCopy := max(len(blobData)-blobPos, 0)
|
||||||
if canCopy <= 0 {
|
realRead := min(needRead, canCopy)
|
||||||
canCopy = 0
|
|
||||||
}
|
|
||||||
realRead := needRead
|
|
||||||
if realRead > canCopy {
|
|
||||||
realRead = canCopy
|
|
||||||
}
|
|
||||||
if realRead > 0 {
|
if realRead > 0 {
|
||||||
copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead])
|
copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead])
|
||||||
}
|
}
|
||||||
|
|
@ -113,10 +104,7 @@ func (f *file) Write(p []byte) (n int, err error) {
|
||||||
blobPos := int(f.offset % f.blockSize)
|
blobPos := int(f.offset % f.blockSize)
|
||||||
blobOffset := f.offset - int64(blobPos)
|
blobOffset := f.offset - int64(blobPos)
|
||||||
blobRemaining := int(f.blockSize) - blobPos
|
blobRemaining := int(f.blockSize) - blobPos
|
||||||
needWrite := len(p)
|
needWrite := min(len(p), blobRemaining)
|
||||||
if needWrite > blobRemaining {
|
|
||||||
needWrite = blobRemaining
|
|
||||||
}
|
|
||||||
buf := make([]byte, f.blockSize)
|
buf := make([]byte, f.blockSize)
|
||||||
readBytes, err := f.readAt(fileMeta, blobOffset, buf)
|
readBytes, err := f.readAt(fileMeta, blobOffset, buf)
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
|
|
||||||
|
|
@ -81,4 +81,4 @@
|
||||||
act_user_id: 40
|
act_user_id: 40
|
||||||
repo_id: 60 # public
|
repo_id: 60 # public
|
||||||
is_private: false
|
is_private: false
|
||||||
created_unix: 1577404800 # end of heatmap
|
created_unix: 1577404800 # end of heatmap
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,36 @@
|
||||||
created_unix: 1559593109
|
created_unix: 1559593109
|
||||||
updated_unix: 1565224552
|
updated_unix: 1565224552
|
||||||
login_source_id: 0
|
login_source_id: 0
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
owner_id: 5
|
||||||
|
name: user5
|
||||||
|
fingerprint: ""
|
||||||
|
content: "user5"
|
||||||
|
mode: 2
|
||||||
|
type: 3
|
||||||
|
created_unix: 1775805112
|
||||||
|
updated_unix: 1775805112
|
||||||
|
login_source_id: 0
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
owner_id: 9
|
||||||
|
name: user9@localhost
|
||||||
|
fingerprint: "SHA256:K3RfDvtQ/aYVzh6RfXGFxlffLLTRgksf9UQwTlwSM8M"
|
||||||
|
content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDN7KuFUnlztx/UM6PUTyiBAq5SeIqr+qSVFC6JzLQAh user9@localhost"
|
||||||
|
mode: 2
|
||||||
|
type: 1
|
||||||
|
created_unix: 1775805112
|
||||||
|
updated_unix: 1775805112
|
||||||
|
login_source_id: 0
|
||||||
|
-
|
||||||
|
id: 4
|
||||||
|
owner_id: 9
|
||||||
|
name: user9
|
||||||
|
fingerprint: ""
|
||||||
|
content: "user9"
|
||||||
|
mode: 2
|
||||||
|
type: 3
|
||||||
|
created_unix: 1775805112
|
||||||
|
updated_unix: 1775805112
|
||||||
|
login_source_id: 0
|
||||||
|
|
|
||||||
22
models/forgefed/error.go
Normal file
22
models/forgefed/error.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package forgefed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrFederationHostNotFound struct {
|
||||||
|
SearchKey string
|
||||||
|
SearchValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrFederationHostNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("ErrFederationHostNotFound: search key: %s, search value: %s", err.SearchKey, err.SearchValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrFederationHostNotFound(err error) bool {
|
||||||
|
_, ok := err.(ErrFederationHostNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,31 @@ func init() {
|
||||||
db.RegisterModel(new(FederationHost))
|
db.RegisterModel(new(FederationHost))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CountFederationHosts(ctx context.Context) (int64, error) {
|
||||||
|
return db.GetEngine(ctx).Count(FederationHost{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindFederationHosts(ctx context.Context, opts db.ListOptions) (hosts []*FederationHost, err error) {
|
||||||
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
if opts.PageSize > 0 {
|
||||||
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sess.Find(&hosts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range hosts {
|
||||||
|
if res, err := validation.IsValid(host); !res {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetFederationHost(ctx context.Context, ID int64) (*FederationHost, error) {
|
func GetFederationHost(ctx context.Context, ID int64) (*FederationHost, error) {
|
||||||
log.Trace("GetFederationHost: %v", ID)
|
log.Trace("GetFederationHost: %v", ID)
|
||||||
host := new(FederationHost)
|
host := new(FederationHost)
|
||||||
|
|
@ -32,13 +57,13 @@ func GetFederationHost(ctx context.Context, ID int64) (*FederationHost, error) {
|
||||||
return host, nil
|
return host, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFederationHostFromDB(ctx context.Context, searchKey, searchValue string) (*FederationHost, error) {
|
func findFederationHostFromDB(ctx context.Context, searchKey string, searchValue ...any) (*FederationHost, error) {
|
||||||
host := new(FederationHost)
|
host := new(FederationHost)
|
||||||
has, err := db.GetEngine(ctx).Where(searchKey, searchValue).Get(host)
|
has, err := db.GetEngine(ctx).Where(searchKey, searchValue...).Get(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, nil
|
return nil, ErrFederationHostNotFound{SearchKey: searchKey, SearchValue: fmt.Sprintf("%v", searchValue)}
|
||||||
}
|
}
|
||||||
if res, err := validation.IsValid(host); !res {
|
if res, err := validation.IsValid(host); !res {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -47,17 +72,7 @@ func findFederationHostFromDB(ctx context.Context, searchKey, searchValue string
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindFederationHostByFqdnAndPort(ctx context.Context, fqdn string, port uint16) (*FederationHost, error) {
|
func FindFederationHostByFqdnAndPort(ctx context.Context, fqdn string, port uint16) (*FederationHost, error) {
|
||||||
host := new(FederationHost)
|
return findFederationHostFromDB(ctx, "host_fqdn=? AND host_port=?", fqdn, port)
|
||||||
has, err := db.GetEngine(ctx).Where("host_fqdn=? AND host_port=?", fqdn, port).Get(host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !has {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if res, err := validation.IsValid(host); !res {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return host, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindFederationHostByKeyID(ctx context.Context, keyID string) (*FederationHost, error) {
|
func FindFederationHostByKeyID(ctx context.Context, keyID string) (*FederationHost, error) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ package forgejo_migrations
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
@ -59,6 +58,18 @@ type v14ActionsApprovalAndTrustTrusted struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func v14ActionsApprovalAndTrustPopulateTableActionUser(x *xorm.Engine) error {
|
func v14ActionsApprovalAndTrustPopulateTableActionUser(x *xorm.Engine) error {
|
||||||
|
type ActionUser struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(repository, id)"`
|
||||||
|
TrustedWithPullRequests bool
|
||||||
|
LastAccess timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
}
|
||||||
|
insertActionUser := func(ctx context.Context, user *ActionUser) error {
|
||||||
|
user.LastAccess = timeutil.TimeStampNow()
|
||||||
|
return db.Insert(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Users approved once were trusted before and are trusted now.
|
// Users approved once were trusted before and are trusted now.
|
||||||
//
|
//
|
||||||
|
|
@ -87,7 +98,7 @@ func v14ActionsApprovalAndTrustPopulateTableActionUser(x *xorm.Engine) error {
|
||||||
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
for _, trusted := range trustedList {
|
for _, trusted := range trustedList {
|
||||||
log.Debug("v14a_actions-approval-and-trust: repository %d trusts user %d", trusted.RepoID, trusted.UserID)
|
log.Debug("v14a_actions-approval-and-trust: repository %d trusts user %d", trusted.RepoID, trusted.UserID)
|
||||||
if err := actions_model.InsertActionUser(ctx, &actions_model.ActionUser{
|
if err := insertActionUser(ctx, &ActionUser{
|
||||||
RepoID: trusted.RepoID,
|
RepoID: trusted.RepoID,
|
||||||
UserID: trusted.UserID,
|
UserID: trusted.UserID,
|
||||||
TrustedWithPullRequests: true,
|
TrustedWithPullRequests: true,
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||||
repo_model "forgejo.org/models/repo"
|
|
||||||
user_model "forgejo.org/models/user"
|
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
webhook_module "forgejo.org/modules/webhook"
|
webhook_module "forgejo.org/modules/webhook"
|
||||||
|
|
||||||
|
|
@ -20,6 +17,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
|
func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
|
||||||
|
type ConcurrencyMode int
|
||||||
|
type Status int
|
||||||
|
|
||||||
type ActionUser struct {
|
type ActionUser struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
|
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
|
||||||
|
|
@ -32,21 +32,18 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
|
||||||
type ActionRun struct {
|
type ActionRun struct {
|
||||||
ID int64
|
ID int64
|
||||||
Title string
|
Title string
|
||||||
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
|
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
|
||||||
Repo *repo_model.Repository `xorm:"-"`
|
OwnerID int64 `xorm:"index"`
|
||||||
OwnerID int64 `xorm:"index"`
|
WorkflowID string `xorm:"index"` // the name of workflow file
|
||||||
WorkflowID string `xorm:"index"` // the name of workflow file
|
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
|
||||||
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
|
TriggerUserID int64 `xorm:"index"`
|
||||||
TriggerUserID int64 `xorm:"index"`
|
|
||||||
TriggerUser *user_model.User `xorm:"-"`
|
|
||||||
ScheduleID int64
|
ScheduleID int64
|
||||||
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
||||||
IsRefDeleted bool `xorm:"-"`
|
|
||||||
CommitSHA string
|
CommitSHA string
|
||||||
Event webhook_module.HookEventType // the webhook event that causes the workflow to run
|
Event webhook_module.HookEventType // the webhook event that causes the workflow to run
|
||||||
EventPayload string `xorm:"LONGTEXT"`
|
EventPayload string `xorm:"LONGTEXT"`
|
||||||
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
|
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
|
||||||
Status actions_model.Status `xorm:"index"`
|
Status Status `xorm:"index"`
|
||||||
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
|
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
|
||||||
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
|
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
|
||||||
Started timeutil.TimeStamp
|
Started timeutil.TimeStamp
|
||||||
|
|
@ -65,7 +62,7 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
|
||||||
ApprovedBy int64 `xorm:"index"`
|
ApprovedBy int64 `xorm:"index"`
|
||||||
|
|
||||||
ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
|
ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
|
||||||
ConcurrencyType actions_model.ConcurrencyMode
|
ConcurrencyType ConcurrencyMode
|
||||||
|
|
||||||
PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
|
PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
|
||||||
}
|
}
|
||||||
|
|
@ -83,10 +80,10 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
|
||||||
|
|
||||||
require.NoError(t, v14ActionsApprovalAndTrustPopulateTableActionUser(x))
|
require.NoError(t, v14ActionsApprovalAndTrustPopulateTableActionUser(x))
|
||||||
|
|
||||||
var users []*actions_model.ActionUser
|
var users []*ActionUser
|
||||||
require.NoError(t, db.GetEngine(t.Context()).Select("`repo_id`, `user_id`").OrderBy("`id`").Find(&users))
|
require.NoError(t, db.GetEngine(t.Context()).Select("`repo_id`, `user_id`").OrderBy("`id`").Find(&users))
|
||||||
// See models/gitea_migrations/fixtures/Test_v14ActionsApprovalAndTrustPopulateTableActionUser/action_run.yml
|
// See models/gitea_migrations/fixtures/Test_v14ActionsApprovalAndTrustPopulateTableActionUser/action_run.yml
|
||||||
assert.Equal(t, []*actions_model.ActionUser{
|
assert.Equal(t, []*ActionUser{
|
||||||
{
|
{
|
||||||
UserID: 3,
|
UserID: 3,
|
||||||
RepoID: 15,
|
RepoID: 15,
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,14 @@ package forgejo_migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
"forgejo.org/models/forgefed"
|
|
||||||
user_model "forgejo.org/models/user"
|
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
"forgejo.org/modules/validation"
|
"forgejo.org/modules/validation"
|
||||||
user_service "forgejo.org/services/user"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
@ -31,6 +30,42 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
|
type FederationHost struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
HostFqdn string `xorm:"host_fqdn UNIQUE(federation_host) INDEX VARCHAR(255) NOT NULL"`
|
||||||
|
HostPort uint16 `xorm:" UNIQUE(federation_host) INDEX NOT NULL DEFAULT 443"`
|
||||||
|
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
|
||||||
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
||||||
|
}
|
||||||
|
type FederatedUser struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"NOT NULL INDEX user_id"`
|
||||||
|
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||||
|
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
|
||||||
|
KeyID sql.NullString `xorm:"key_id UNIQUE"`
|
||||||
|
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
|
||||||
|
InboxPath string
|
||||||
|
NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID!
|
||||||
|
}
|
||||||
|
type User struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||||
|
Name string `xorm:"UNIQUE NOT NULL"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
deleteFederatedUser := func(ctx context.Context, userID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userLogString := func(u *User) string {
|
||||||
|
if u == nil {
|
||||||
|
return "<User nil>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<User %d:%s>", u.ID, u.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// Normally, the db.WithTx statement ensures that the database transaction (aka. all changes made
|
// Normally, the db.WithTx statement ensures that the database transaction (aka. all changes made
|
||||||
// by this migration) will only be committed if the SQL operations inside of the iteration
|
// by this migration) will only be committed if the SQL operations inside of the iteration
|
||||||
// (db.Iterate) don't return an error.
|
// (db.Iterate) don't return an error.
|
||||||
|
|
@ -45,9 +80,9 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
// migrations at a later point and has been kept as-is.
|
// migrations at a later point and has been kept as-is.
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
// The transaction is committed only if modifying all federated users is possible.
|
// The transaction is committed only if modifying all federated users is possible.
|
||||||
return db.Iterate(ctx, nil, func(ctx context.Context, federatedUser *user_model.FederatedUser) error {
|
return db.Iterate(ctx, nil, func(ctx context.Context, federatedUser *FederatedUser) error {
|
||||||
// localUser represents the "local" representation of an ActivityPub (federated) user
|
// localUser represents the "local" representation of an ActivityPub (federated) user
|
||||||
localUser := &user_model.User{}
|
localUser := &User{}
|
||||||
has, err := db.GetEngine(ctx).ID(federatedUser.UserID).Get(localUser)
|
has, err := db.GetEngine(ctx).ID(federatedUser.UserID).Get(localUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while getting local user (ID: %d), ignoring...: %e", federatedUser.UserID, err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while getting local user (ID: %d), ignoring...: %e", federatedUser.UserID, err)
|
||||||
|
|
@ -56,7 +91,7 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: User missing for federated user: %v", federatedUser)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: User missing for federated user: %v", federatedUser)
|
||||||
err := user_model.DeleteFederatedUser(ctx, federatedUser.UserID)
|
err := deleteFederatedUser(ctx, federatedUser.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", federatedUser, err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", federatedUser, err)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -68,24 +103,13 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
} else {
|
} else {
|
||||||
// Copied from models/forgefed/federationhost_repository.go (forgefed.GetFederationHost),
|
// Copied from models/forgefed/federationhost_repository.go (forgefed.GetFederationHost),
|
||||||
// minus some validation code for FederationHost which we do not otherwise manipulate here.
|
// minus some validation code for FederationHost which we do not otherwise manipulate here.
|
||||||
federationHost := new(forgefed.FederationHost)
|
federationHost := new(FederationHost)
|
||||||
has, err := db.GetEngine(ctx).ID(federatedUser.FederationHostID).Get(federationHost)
|
has, err := db.GetEngine(ctx).ID(federatedUser.FederationHostID).Get(federationHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while looking up federation host info (for %v), ignoring...: %e", federatedUser, err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while looking up federation host info (for %v), ignoring...: %e", federatedUser, err)
|
||||||
return nil
|
return nil
|
||||||
} else if !has {
|
} else if !has {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Federation host for federated user missing, deleting: %v", federatedUser)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Federation host for federated user %s is missing", federatedUser)
|
||||||
err := user_model.DeleteFederatedUser(ctx, federatedUser.UserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%v), ignoring...: %e", federatedUser, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = user_service.DeleteUser(ctx, localUser, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting user (%s), ignoring...: %v", localUser.LogString(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,10 +141,10 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
|
|
||||||
// Implicitly assumes that there won't be a lower name unique constraint violation.
|
// Implicitly assumes that there won't be a lower name unique constraint violation.
|
||||||
// Potentially a bit paranoid, but why not?
|
// Potentially a bit paranoid, but why not?
|
||||||
userThatShouldntExist := &user_model.User{}
|
userThatShouldntExist := &User{}
|
||||||
lowernameTaken, err := db.GetEngine(ctx).Where("lower_name = ?", strings.ToLower(newUsername)).Table("user").Get(userThatShouldntExist)
|
lowernameTaken, err := db.GetEngine(ctx).Where("lower_name = ?", strings.ToLower(newUsername)).Table("user").Get(userThatShouldntExist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred, skipping migration of %s: %e", localUser.LogString(), err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred, skipping migration of %s: %e", userLogString(localUser), err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,23 +152,23 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
|
||||||
log.Warn(
|
log.Warn(
|
||||||
"Migration[v14a_ap-change-fedi-handle-structure]: New username %s for %s already taken by %s, deleting the former...",
|
"Migration[v14a_ap-change-fedi-handle-structure]: New username %s for %s already taken by %s, deleting the former...",
|
||||||
newUsername,
|
newUsername,
|
||||||
localUser.LogString(),
|
userLogString(localUser),
|
||||||
userThatShouldntExist.LogString(),
|
userLogString(userThatShouldntExist),
|
||||||
)
|
)
|
||||||
err := user_model.DeleteFederatedUser(ctx, localUser.ID)
|
err := deleteFederatedUser(ctx, localUser.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", localUser.LogString(), err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", userLogString(localUser), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe to assume that the following operations should just work now.
|
// Safe to assume that the following operations should just work now.
|
||||||
log.Info("Migration[v14a_ap-change-fedi-handle-structure]: Updating username of %s to %s", localUser.LogString(), newUsername)
|
log.Info("Migration[v14a_ap-change-fedi-handle-structure]: Updating username of %s to %s", userLogString(localUser), newUsername)
|
||||||
if _, err := db.GetEngine(ctx).ID(localUser.ID).Cols("lower_name", "name").Update(&user_model.User{
|
if _, err := db.GetEngine(ctx).ID(localUser.ID).Cols("lower_name", "name").Update(&User{
|
||||||
LowerName: strings.ToLower(newUsername),
|
LowerName: strings.ToLower(newUsername),
|
||||||
Name: newUsername,
|
Name: newUsername,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred when updating federated user's username (%s), ignoring...: %e", localUser.LogString(), err)
|
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred when updating federated user's username (%s), ignoring...: %e", userLogString(localUser), err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
admin_model "forgejo.org/models/admin"
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
"forgejo.org/modules/json"
|
"forgejo.org/modules/json"
|
||||||
"forgejo.org/modules/keying"
|
"forgejo.org/modules/keying"
|
||||||
|
|
@ -17,6 +16,7 @@ import (
|
||||||
"forgejo.org/modules/secret"
|
"forgejo.org/modules/secret"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/structs"
|
"forgejo.org/modules/structs"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
@ -30,6 +30,19 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateTaskSecrets(x *xorm.Engine) error {
|
func migrateTaskSecrets(x *xorm.Engine) error {
|
||||||
|
type Task struct {
|
||||||
|
ID int64
|
||||||
|
DoerID int64 `xorm:"index"`
|
||||||
|
OwnerID int64 `xorm:"index"`
|
||||||
|
RepoID int64 `xorm:"index"`
|
||||||
|
PayloadContent string `xorm:"TEXT"`
|
||||||
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
|
}
|
||||||
|
taskUpdateCols := func(ctx context.Context, task *Task, cols ...string) error {
|
||||||
|
_, err := db.GetEngine(ctx).ID(task.ID).Cols(cols...).Update(task)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
|
@ -39,7 +52,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
|
||||||
messages := make([]string, 0, 100)
|
messages := make([]string, 0, 100)
|
||||||
ids := make([]int64, 0, 100)
|
ids := make([]int64, 0, 100)
|
||||||
|
|
||||||
err := db.Iterate(ctx, builder.Eq{"type": structs.TaskTypeMigrateRepo}, func(ctx context.Context, bean *admin_model.Task) error {
|
err := db.Iterate(ctx, builder.Eq{"type": structs.TaskTypeMigrateRepo}, func(ctx context.Context, bean *Task) error {
|
||||||
var opts migration.MigrateOptions
|
var opts migration.MigrateOptions
|
||||||
err := json.Unmarshal([]byte(bean.PayloadContent), &opts)
|
err := json.Unmarshal([]byte(bean.PayloadContent), &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -96,7 +109,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
bean.PayloadContent = string(bs)
|
bean.PayloadContent = string(bs)
|
||||||
|
|
||||||
return bean.UpdateCols(ctx, "payload_content")
|
return taskUpdateCols(ctx, bean, "payload_content")
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -106,7 +119,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
|
||||||
log.Error("v14a_migrate_task_secrets: %s", message)
|
log.Error("v14a_migrate_task_secrets: %s", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&admin_model.Task{})
|
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&Task{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
webhook_model "forgejo.org/models/webhook"
|
|
||||||
"forgejo.org/modules/keying"
|
"forgejo.org/modules/keying"
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
"forgejo.org/modules/secret"
|
"forgejo.org/modules/secret"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
|
|
@ -26,6 +26,16 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateWebhookSecrets(x *xorm.Engine) error {
|
func migrateWebhookSecrets(x *xorm.Engine) error {
|
||||||
|
type Webhook struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
|
||||||
|
OwnerID int64 `xorm:"INDEX"`
|
||||||
|
HeaderAuthorizationEncrypted []byte `xorm:"BLOB"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
|
@ -59,7 +69,7 @@ func migrateWebhookSecrets(x *xorm.Engine) error {
|
||||||
messages := make([]string, 0, 100)
|
messages := make([]string, 0, 100)
|
||||||
ids := make([]int64, 0, 100)
|
ids := make([]int64, 0, 100)
|
||||||
|
|
||||||
err := db.Iterate(ctx, nil, func(ctx context.Context, bean *webhook_model.Webhook) error {
|
err := db.Iterate(ctx, nil, func(ctx context.Context, bean *Webhook) error {
|
||||||
if len(bean.HeaderAuthorizationEncrypted) == 0 {
|
if len(bean.HeaderAuthorizationEncrypted) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +93,7 @@ func migrateWebhookSecrets(x *xorm.Engine) error {
|
||||||
log.Error("migration[v14a_migrate_webhook_authorization]: %s", message)
|
log.Error("migration[v14a_migrate_webhook_authorization]: %s", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&webhook_model.Webhook{})
|
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&Webhook{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||||
webhook_model "forgejo.org/models/webhook"
|
|
||||||
"forgejo.org/modules/keying"
|
"forgejo.org/modules/keying"
|
||||||
"forgejo.org/modules/timeutil"
|
"forgejo.org/modules/timeutil"
|
||||||
webhook_module "forgejo.org/modules/webhook"
|
webhook_module "forgejo.org/modules/webhook"
|
||||||
|
|
@ -17,6 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_MigrateWebhookSecrets(t *testing.T) {
|
func Test_MigrateWebhookSecrets(t *testing.T) {
|
||||||
|
type HookContentType int
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
RepoID int64 `xorm:"INDEX"`
|
RepoID int64 `xorm:"INDEX"`
|
||||||
|
|
@ -24,7 +24,7 @@ func Test_MigrateWebhookSecrets(t *testing.T) {
|
||||||
IsSystemWebhook bool
|
IsSystemWebhook bool
|
||||||
URL string `xorm:"url TEXT"`
|
URL string `xorm:"url TEXT"`
|
||||||
HTTPMethod string `xorm:"http_method"`
|
HTTPMethod string `xorm:"http_method"`
|
||||||
ContentType webhook_model.HookContentType
|
ContentType HookContentType
|
||||||
Secret string `xorm:"TEXT"`
|
Secret string `xorm:"TEXT"`
|
||||||
Events string `xorm:"TEXT"`
|
Events string `xorm:"TEXT"`
|
||||||
IsActive bool `xorm:"INDEX"`
|
IsActive bool `xorm:"INDEX"`
|
||||||
|
|
@ -45,7 +45,7 @@ func Test_MigrateWebhookSecrets(t *testing.T) {
|
||||||
IsSystemWebhook bool
|
IsSystemWebhook bool
|
||||||
URL string `xorm:"url TEXT"`
|
URL string `xorm:"url TEXT"`
|
||||||
HTTPMethod string `xorm:"http_method"`
|
HTTPMethod string `xorm:"http_method"`
|
||||||
ContentType webhook_model.HookContentType
|
ContentType HookContentType
|
||||||
Secret string `xorm:"TEXT"`
|
Secret string `xorm:"TEXT"`
|
||||||
Events string `xorm:"TEXT"`
|
Events string `xorm:"TEXT"`
|
||||||
IsActive bool `xorm:"INDEX"`
|
IsActive bool `xorm:"INDEX"`
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
package forgejo_migrations
|
package forgejo_migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
activities_model "forgejo.org/models/activities"
|
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
@ -18,9 +17,10 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func reworkNotification(x *xorm.Engine) error {
|
func reworkNotification(x *xorm.Engine) error {
|
||||||
|
type NotificationStatus uint8
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
UserID int64 `xorm:"NOT NULL INDEX(s)"`
|
UserID int64 `xorm:"NOT NULL INDEX(s)"`
|
||||||
Status activities_model.NotificationStatus `xorm:"SMALLINT NOT NULL INDEX(s)"`
|
Status NotificationStatus `xorm:"SMALLINT NOT NULL INDEX(s)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dropIndexIfExists(x, "notification", "IDX_notification_user_id"); err != nil {
|
if err := dropIndexIfExists(x, "notification", "IDX_notification_user_id"); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ package forgejo_migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
user_model "forgejo.org/models/user"
|
|
||||||
"forgejo.org/modules/log"
|
"forgejo.org/modules/log"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
@ -22,13 +23,45 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setProhibitLoginActivityPubUser(x *xorm.Engine) error {
|
func setProhibitLoginActivityPubUser(x *xorm.Engine) error {
|
||||||
|
type UserType int
|
||||||
|
const (
|
||||||
|
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||||
|
UserTypeOrganization // 1
|
||||||
|
UserTypeUserReserved // 2
|
||||||
|
UserTypeOrganizationReserved // 3
|
||||||
|
UserTypeBot // 4
|
||||||
|
UserTypeRemoteUser // 5
|
||||||
|
UserTypeActivityPubUser // 6
|
||||||
|
)
|
||||||
|
type User struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string `xorm:"UNIQUE NOT NULL"`
|
||||||
|
Passwd string `xorm:"NOT NULL"`
|
||||||
|
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
|
||||||
|
Type UserType
|
||||||
|
Salt string `xorm:"VARCHAR(32)"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
type FederatedUser struct {
|
||||||
|
UserID int64 `xorm:"NOT NULL INDEX user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
userLogString := func(u *User) string {
|
||||||
|
if u == nil {
|
||||||
|
return "<User nil>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<User %d:%s>", u.ID, u.Name)
|
||||||
|
}
|
||||||
|
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
return db.Iterate(ctx, builder.Eq{"type": 5}, func(ctx context.Context, user *user_model.User) error {
|
return db.Iterate(ctx, builder.Eq{"type": 5}, func(ctx context.Context, user *User) error {
|
||||||
log.Info("Checking if user %s is created from ActivityPub", user.LogString())
|
log.Info("Checking if user %s is created from ActivityPub", userLogString(user))
|
||||||
|
|
||||||
// Users created from f3 also have the RemoteUser user type. All
|
// Users created from f3 also have the RemoteUser user type. All
|
||||||
// FederatedUser should reference exactly one User.
|
// FederatedUser should reference exactly one User.
|
||||||
has, err := db.GetEngine(ctx).Table("federated_user").Get(&user_model.FederatedUser{UserID: user.ID})
|
has, err := db.GetEngine(ctx).Table("federated_user").Get(&FederatedUser{UserID: user.ID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -37,9 +70,9 @@ func setProhibitLoginActivityPubUser(x *xorm.Engine) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Updating user %s", user.LogString())
|
log.Info("Updating user %s", userLogString(user))
|
||||||
_, err = db.GetEngine(ctx).Table("user").ID(user.ID).Cols("type", "prohibit_login", "passwd", "salt", "passwd_hash_algo").Update(&user_model.User{
|
_, err = db.GetEngine(ctx).Table("user").ID(user.ID).Cols("type", "prohibit_login", "passwd", "salt", "passwd_hash_algo").Update(&User{
|
||||||
Type: user_model.UserTypeActivityPubUser,
|
Type: UserTypeActivityPubUser,
|
||||||
ProhibitLogin: true,
|
ProhibitLogin: true,
|
||||||
Passwd: "",
|
Passwd: "",
|
||||||
Salt: "",
|
Salt: "",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package forgejo_migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(&Migration{
|
||||||
|
Description: "replace remote_address with encrypted_remote_address in table mirror",
|
||||||
|
Upgrade: addMirrorRemoteAddressAuth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMirrorRemoteAddressAuth(x *xorm.Engine) error {
|
||||||
|
type Mirror struct {
|
||||||
|
EncryptedRemoteAddress []byte `xorm:"BLOB NULL"`
|
||||||
|
}
|
||||||
|
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(Mirror)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// No data migration is necessary or desired. `remote_address` contains sanitized URLs which don't have
|
||||||
|
// credentials, so they can't be migrated to `encrypted_remote_address`. Instead, as this data is accessed,
|
||||||
|
// `DecryptOrRecoverRemoteAddress` will recover the fully credentialed contents of the remote address from the git
|
||||||
|
// repo's `origin` remote address.
|
||||||
|
_, err := x.Exec("ALTER TABLE `mirror` DROP COLUMN `remote_address`")
|
||||||
|
return err
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue