mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 14:30:25 +00:00
Compare commits
161 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27c5d9ac0f | ||
|
|
69fc06217d | ||
|
|
df248fbccd | ||
|
|
9c9485d1df | ||
|
|
8634b38383 | ||
|
|
206224aff5 | ||
|
|
7902524927 | ||
|
|
a3870d7955 | ||
|
|
9cf4962d95 | ||
|
|
2c3f0c4250 | ||
|
|
ff2cbd6824 | ||
|
|
85e12097a4 | ||
|
|
72d35af26a | ||
|
|
eac5cb9a64 | ||
|
|
a2f9fb501f | ||
|
|
a1bd21d45e | ||
|
|
2e6fc8aaf0 | ||
|
|
62c73cdd8e | ||
|
|
c7b4e90106 | ||
|
|
021d8b198f | ||
|
|
71df8827a9 | ||
|
|
3c6b708c0f | ||
|
|
1a3cdde2ed | ||
|
|
37cbf7f519 | ||
|
|
3c91d267ec | ||
|
|
0f2e6034ce | ||
|
|
b566753e47 | ||
|
|
14957b42bc | ||
|
|
c502e2b1e3 | ||
|
|
d8cba03e16 | ||
|
|
e7a5e0a82b | ||
|
|
f602b5f5ed | ||
|
|
d52756b879 | ||
|
|
a72cffddfa | ||
|
|
beed05d2f7 | ||
|
|
9421d22215 | ||
|
|
4123ace6c4 | ||
|
|
211376cd9a | ||
|
|
164c02b010 | ||
|
|
1452c3ae70 | ||
|
|
197220c2ee | ||
|
|
d1d90aa9c1 | ||
|
|
fc57758c87 | ||
|
|
f4581e0f23 | ||
|
|
912ffa2dbd | ||
|
|
e5a4f83bea | ||
|
|
fa3073044a | ||
|
|
fe55c0e76c | ||
|
|
816a37f576 | ||
|
|
155acecb4b | ||
|
|
2b0ec87644 | ||
|
|
388436d500 | ||
|
|
ea4f733de8 | ||
|
|
d4bb6cde65 | ||
|
|
cd9ddac459 | ||
|
|
d9896e4de6 | ||
|
|
6ee6dc2eb0 | ||
|
|
230ccf5276 | ||
|
|
d0dd0928be | ||
|
|
aca1775dae | ||
|
|
456be57bb7 | ||
|
|
b72473d114 | ||
|
|
dbdbd0f5e6 | ||
|
|
f4f3a3e0f3 | ||
|
|
6b6e9da3cd | ||
|
|
41c3c5bb80 | ||
|
|
677de6419b | ||
|
|
8ef91fa1cc | ||
|
|
b3c7fbcce3 | ||
|
|
3515a9c365 | ||
|
|
e50f9ff165 | ||
|
|
131fc0db9c | ||
|
|
382ee8ce34 | ||
|
|
ce0e17989b | ||
|
|
84240ce3a2 | ||
|
|
a59481d3d9 | ||
|
|
3ad19e7335 | ||
|
|
032b0bbeda | ||
|
|
9347d06de5 | ||
|
|
7410ef5b9f | ||
|
|
8aff5ab18b | ||
|
|
68f39ad66b | ||
|
|
4bb2d68a10 | ||
|
|
4e1468a1b5 | ||
|
|
ddf0265b5c | ||
|
|
3aaef3b3dd | ||
|
|
097680a64d | ||
|
|
bdc7c65173 | ||
|
|
f9e2ccb108 | ||
|
|
39a5f838c7 | ||
|
|
da0df4b5d9 | ||
|
|
c3fe2a5e34 | ||
|
|
191b309486 | ||
|
|
4d7dfe38bf | ||
|
|
186664b881 | ||
|
|
1d1a62187a | ||
|
|
ec28d5885d | ||
|
|
f33e2d1efd | ||
|
|
964288a4a8 | ||
|
|
f3028a7768 | ||
|
|
16f98ebaec | ||
|
|
ca46a3f68b | ||
|
|
16ee36b023 | ||
|
|
e147d8d805 | ||
|
|
cd00d61b91 | ||
|
|
0f20b2e51a | ||
|
|
a3a52251ad | ||
|
|
46d0192f5c | ||
|
|
745134a89f | ||
|
|
04132b338d | ||
|
|
527c1a4fda | ||
|
|
48bb631f20 | ||
|
|
92ea9b7055 | ||
|
|
d912a9b21f | ||
|
|
b042992694 | ||
|
|
f962b32177 | ||
|
|
5c9a2e91f4 | ||
|
|
915c436d95 | ||
|
|
686f780673 | ||
|
|
659f1fc0c6 | ||
|
|
f87ec19130 | ||
|
|
47b9fdc590 | ||
|
|
90ab3b1940 | ||
|
|
0b7e6ff363 | ||
|
|
440f38913e | ||
|
|
3556875c51 | ||
|
|
f5603d2210 | ||
|
|
ca3166ddba | ||
|
|
1ca9cbb7c2 | ||
|
|
590795f592 | ||
|
|
168dfbb70b | ||
|
|
bade14ee69 | ||
|
|
8e083c9f3e | ||
|
|
342a54a142 | ||
|
|
a53aa04f5d | ||
|
|
40e4f6f354 | ||
|
|
abab629d90 | ||
|
|
626929eaa3 | ||
|
|
a604f85c60 | ||
|
|
ee42a69b3a | ||
|
|
766104acae | ||
|
|
180ebee6de | ||
|
|
400c17e290 | ||
|
|
15f891abd7 | ||
|
|
8514af643d | ||
|
|
763547f43f | ||
|
|
a89978a207 | ||
|
|
c6c51dcde6 | ||
|
|
462fe3819b | ||
|
|
8dff8ba7c2 | ||
|
|
5a131275c1 | ||
|
|
6907601529 | ||
|
|
fcb22b1a47 | ||
|
|
650252f851 | ||
|
|
cd0afc4f90 | ||
|
|
dd75d0957d | ||
|
|
83da3ae68c | ||
|
|
44102c47d4 | ||
|
|
fed7d64861 | ||
|
|
6df7514417 | ||
|
|
0861a01192 |
524 changed files with 18523 additions and 7268 deletions
|
|
@ -50,9 +50,6 @@ forgejo.org/models/organization
|
|||
forgejo.org/models/perm/access
|
||||
GetRepoWriters
|
||||
|
||||
forgejo.org/models/repo
|
||||
WatchRepoMode
|
||||
|
||||
forgejo.org/models/user
|
||||
IsErrUserWrongType
|
||||
IsErrExternalLoginUserAlreadyExist
|
||||
|
|
@ -238,6 +235,7 @@ forgejo.org/services/repository
|
|||
|
||||
forgejo.org/services/repository/files
|
||||
ContentType.String
|
||||
RepoFileOptionMode
|
||||
|
||||
forgejo.org/services/repository/gitgraph
|
||||
Parser.Reset
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
set -ex
|
||||
|
||||
# WARNING: Changes to the behaviour of this file should be backported to all active releases, as it is used in
|
||||
# `build-release.yml` from release branches.
|
||||
|
||||
end_to_end=$1
|
||||
end_to_end_pr=$2
|
||||
forgejo=$3
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ runs:
|
|||
apt-get update -qq
|
||||
apt-get -q install -y -qq curl ca-certificates
|
||||
|
||||
curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb
|
||||
curl -sS -o /tmp/git-man.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb
|
||||
curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb
|
||||
curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb
|
||||
|
||||
|
|
|
|||
|
|
@ -52,10 +52,9 @@ runs:
|
|||
id: cache-deps
|
||||
uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod') }}
|
||||
key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod', 'Makefile') }}
|
||||
restore-keys: |
|
||||
setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-
|
||||
setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-
|
||||
path: |
|
||||
${{ steps.go-environment.outputs.modcache }}
|
||||
${{ steps.go-environment.outputs.cache }}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- id: forgejo
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.6
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.7
|
||||
with:
|
||||
user: root
|
||||
password: admin1234
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ jobs:
|
|||
|
||||
- name: build container & release
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
@ -183,7 +183,7 @@ jobs:
|
|||
|
||||
- name: build rootless container
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
@ -206,7 +206,7 @@ jobs:
|
|||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
origin-token: ${{ secrets.CASCADE_ORIGIN_TOKEN }}
|
||||
origin-ref: refs/heads/forgejo
|
||||
origin-ref: ${{ github.ref }}
|
||||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: ${{ vars.CASCADE_DESTINATION_DOER }}/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.5.1
|
||||
with:
|
||||
from-forgejo: ${{ vars.FORGEJO }}
|
||||
to-forgejo: ${{ vars.FORGEJO }}
|
||||
|
|
@ -63,14 +63,14 @@ jobs:
|
|||
|
||||
- name: get trigger mirror issue
|
||||
id: mirror
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.3.0
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.5.0
|
||||
with:
|
||||
forgejo: https://code.forgejo.org
|
||||
repository: forgejo/forgejo
|
||||
labels: mirror-trigger
|
||||
|
||||
- name: trigger the mirror
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.3.0
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.5.0
|
||||
with:
|
||||
forgejo: https://code.forgejo.org
|
||||
repository: forgejo/forgejo
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
|
@ -75,7 +75,7 @@ jobs:
|
|||
matrix:
|
||||
version: ['10.6', '11.8']
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -87,10 +87,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- name: event info
|
||||
|
|
@ -31,10 +31,13 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
|
|
@ -60,7 +63,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
elasticsearch:
|
||||
|
|
@ -80,10 +83,6 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- name: test release-notes-assistant.sh
|
||||
run: |
|
||||
apt-get -q install -qq -y jq
|
||||
|
|
@ -150,7 +149,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks, test-unit]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
||||
strategy:
|
||||
|
|
@ -177,10 +176,6 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-remote-cacher test-check'
|
||||
|
|
@ -194,7 +189,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -210,10 +205,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
@ -225,7 +220,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
minio:
|
||||
|
|
@ -248,10 +243,10 @@ jobs:
|
|||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
||||
|
|
@ -265,15 +260,15 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||
|
|
@ -293,7 +288,7 @@ jobs:
|
|||
- test-remote-cacher
|
||||
- test-unit
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v5
|
||||
|
|
|
|||
60
assets/go-licenses.json
generated
60
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -73,12 +73,6 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
funcname = nodeVar.Ident[2]
|
||||
}
|
||||
|
||||
if funcname == "IterWithTr" {
|
||||
for i := 2; i < len(nodeCommand.Args); i += 2 {
|
||||
handler.handleTemplateMsgid(fset, nodeCommand.Args[i])
|
||||
}
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
ltf, ok := handler.LocaleTrFunctions[funcname]
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ func subcmdAuth() *cli.Command {
|
|||
microcmdAuthUpdateLdapBindDn(),
|
||||
microcmdAuthAddLdapSimpleAuth(),
|
||||
microcmdAuthUpdateLdapSimpleAuth(),
|
||||
microcmdAuthAddPAM(),
|
||||
microcmdAuthUpdatePAM(),
|
||||
microcmdAuthAddSMTP(),
|
||||
microcmdAuthUpdateSMTP(),
|
||||
microcmdAuthList(),
|
||||
|
|
|
|||
145
cmd/admin_auth_pam.go
Normal file
145
cmd/admin_auth_pam.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/services/auth/source/pam"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func pamCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Usage: "Application Name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-name",
|
||||
Value: "PLAIN",
|
||||
Usage: "PAM service name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "email-domain",
|
||||
Value: "",
|
||||
Usage: "PAM email domain",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Skip 2FA to log on.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "This Authentication Source is Activated.",
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthAddPAM() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-pam",
|
||||
Usage: "Add new PAM authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().addPAM,
|
||||
Flags: pamCLIFlags(),
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdatePAM() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-pam",
|
||||
Usage: "Update existing PAM authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().updatePAM,
|
||||
Flags: append(pamCLIFlags()[:1], append([]cli.Flag{idFlag()}, pamCLIFlags()[1:]...)...),
|
||||
}
|
||||
}
|
||||
|
||||
func parsePAMConfig(_ context.Context, c *cli.Command) *pam.Source {
|
||||
return &pam.Source{
|
||||
ServiceName: c.String("service-name"),
|
||||
EmailDomain: c.String("email-domain"),
|
||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authService) addPAM(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.IsSet("name") || len(c.String("name")) == 0 {
|
||||
return errors.New("name must be set")
|
||||
}
|
||||
if !c.IsSet("service-name") || len(c.String("service-name")) == 0 {
|
||||
return errors.New("service-name must be set")
|
||||
}
|
||||
active := true
|
||||
if c.IsSet("active") {
|
||||
active = c.Bool("active")
|
||||
}
|
||||
|
||||
config := parsePAMConfig(ctx, c)
|
||||
|
||||
return a.createAuthSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.PAM,
|
||||
Name: c.String("name"),
|
||||
IsActive: active,
|
||||
Cfg: config,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *authService) updatePAM(ctx context.Context, c *cli.Command) error {
|
||||
if !c.IsSet("id") {
|
||||
return errors.New("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := a.getAuthSource(ctx, c, auth_model.PAM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pamConfig := source.Cfg.(*pam.Source)
|
||||
|
||||
if c.IsSet("name") {
|
||||
source.Name = c.String("name")
|
||||
}
|
||||
|
||||
if c.IsSet("service-name") {
|
||||
pamConfig.ServiceName = c.String("service-name")
|
||||
}
|
||||
|
||||
if c.IsSet("email-domain") {
|
||||
pamConfig.EmailDomain = c.String("email-domain")
|
||||
}
|
||||
|
||||
if c.IsSet("skip-local-2fa") {
|
||||
pamConfig.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
||||
}
|
||||
|
||||
if c.IsSet("active") {
|
||||
source.IsActive = c.Bool("active")
|
||||
}
|
||||
|
||||
source.Cfg = pamConfig
|
||||
|
||||
return a.updateAuthSource(ctx, source)
|
||||
}
|
||||
293
cmd/admin_auth_pam_test.go
Normal file
293
cmd/admin_auth_pam_test.go
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/auth"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/services/auth/source/pam"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestPamService(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
source *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "Pam Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
EmailDomain: "",
|
||||
SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--service-name", "myservice",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "Pam Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
EmailDomain: "testdomain.org",
|
||||
SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--service-name", "myservice",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa", "false",
|
||||
"--active", "true",
|
||||
},
|
||||
errMsg: "name must be set",
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa", "false",
|
||||
"--active", "true",
|
||||
},
|
||||
errMsg: "service-name must be set",
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var createdAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
createdAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call updateAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n)
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthAddPAM().Flags
|
||||
app.Action = service.addPAM
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.source, createdAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePAM(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
id int64
|
||||
existingAuthSource *auth.Source
|
||||
authSource *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "23",
|
||||
"--name", "PAM Service",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
id: 23,
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "PAM Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--name", "pam service",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "pam service",
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--active=false",
|
||||
},
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: false,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 4
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 5
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--skip-local-2fa=false",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
SkipLocalTwoFA: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 6
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--email-domain", "testdomain.org",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
EmailDomain: "testdomain.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var updatedAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call createAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
updatedAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
if c.id != 0 {
|
||||
assert.Equal(t, c.id, id, "case %d: wrong id", n)
|
||||
}
|
||||
if c.existingAuthSource != nil {
|
||||
return c.existingAuthSource, nil
|
||||
}
|
||||
return &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthUpdatePAM().Flags
|
||||
app.Action = service.updatePAM
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.authSource, updatedAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
migrate_base "forgejo.org/models/gitea_migrations/base"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
|
@ -41,6 +42,7 @@ func cmdDoctor() *cli.Command {
|
|||
cmdRecreateTable(),
|
||||
cmdDoctorConvert(),
|
||||
cmdAvatarStripExif(),
|
||||
cmdCleanupCommitStatuses(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -115,6 +117,54 @@ func cmdAvatarStripExif() *cli.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func cmdCleanupCommitStatuses() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "cleanup-commit-status",
|
||||
Usage: "Cleanup extra records in commit_status table",
|
||||
Description: `Forgejo suffered from a bug which caused the creation of more entries in the
|
||||
"commit_status" table than necessary. This operation removes the redundant
|
||||
data caused by the bug. Removing this data is almost always safe.
|
||||
These redundant records can be accessed by users through the API, making it
|
||||
possible, but unlikely, that removing it could have an impact to
|
||||
integrating services (API: /repos/{owner}/{repo}/commits/{ref}/statuses).
|
||||
|
||||
It is safe to run while Forgejo is online.
|
||||
|
||||
On very large Forgejo instances, the performance of operation will improve
|
||||
if the buffer-size option is used with large values. Approximately 130 MB of
|
||||
memory is required for every 100,000 records in the buffer.
|
||||
|
||||
Bug reference: https://codeberg.org/forgejo/forgejo/issues/10671
|
||||
`,
|
||||
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)),
|
||||
Action: runCleanupCommitStatus,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"V"},
|
||||
Usage: "Show process details",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Report statistics from the operation but do not modify the database",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "buffer-size",
|
||||
Usage: "Record count per query while iterating records; larger values are typically faster but use more memory",
|
||||
// See IterateByKeyset's documentation for performance notes which led to the choice of the default
|
||||
// buffer size for this operation.
|
||||
Value: 100000,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "delete-chunk-size",
|
||||
Usage: "Number of records to delete per DELETE query",
|
||||
Value: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
||||
stdCtx, cancel := installSignals(stdCtx)
|
||||
defer cancel()
|
||||
|
|
@ -322,3 +372,19 @@ func runAvatarStripExif(ctx context.Context, c *cli.Command) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCleanupCommitStatus(ctx context.Context, cli *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bufferSize := cli.Int("buffer-size")
|
||||
deleteChunkSize := cli.Int("delete-chunk-size")
|
||||
dryRun := cli.Bool("dry-run")
|
||||
log.Debug("bufferSize = %d, deleteChunkSize = %d, dryRun = %v", bufferSize, deleteChunkSize, dryRun)
|
||||
|
||||
return git_model.CleanupCommitStatus(ctx, bufferSize, deleteChunkSize, dryRun)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
fields := bytes.Split(scanner.Bytes(), []byte(" "))
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
fields := bytes.Split(scanner.Bytes(), []byte(" "))
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
133
cmd/hook_test.go
133
cmd/hook_test.go
|
|
@ -14,6 +14,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/modules/private"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
|
|
@ -161,3 +164,133 @@ func TestDelayWriter(t *testing.T) {
|
|||
require.Empty(t, out)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunHookPrePostReceive(t *testing.T) {
|
||||
// Setup the environment.
|
||||
defer test.MockVariableValue(&setting.InternalToken, "Random")()
|
||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||
defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
|
||||
t.Setenv("SSH_ORIGINAL_COMMAND", "true")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputLine string
|
||||
oldCommitID string
|
||||
newCommitID string
|
||||
refFullName string
|
||||
}{
|
||||
{
|
||||
name: "base case",
|
||||
inputLine: "00000000000000000000 00000000000000000001 refs/head/main\n",
|
||||
oldCommitID: "00000000000000000000",
|
||||
newCommitID: "00000000000000000001",
|
||||
refFullName: "refs/head/main",
|
||||
},
|
||||
{
|
||||
name: "nbsp case",
|
||||
inputLine: "00000000000000000000 00000000000000000001 refs/head/ma\u00A0in\n",
|
||||
oldCommitID: "00000000000000000000",
|
||||
newCommitID: "00000000000000000001",
|
||||
refFullName: "refs/head/ma\u00A0in",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup the Stdin.
|
||||
f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write([]byte(tt.inputLine))
|
||||
require.NoError(t, err)
|
||||
_, err = f.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(os.Stdin, *f)()
|
||||
|
||||
// Setup the server that processes the hooks.
|
||||
var serverError error
|
||||
var hookOpts *private.HookOptions
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &hookOpts)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
|
||||
resp := &private.HookPostReceiveResult{}
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
return
|
||||
}
|
||||
|
||||
_, err = w.Write(bytes)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
||||
|
||||
t.Run("pre-receive", func(t *testing.T) {
|
||||
app := cli.Command{}
|
||||
app.Commands = []*cli.Command{subcmdHookPreReceive()}
|
||||
|
||||
finish := captureOutput(t, os.Stdout)
|
||||
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
require.Empty(t, out)
|
||||
|
||||
require.NoError(t, serverError)
|
||||
require.NotNil(t, hookOpts)
|
||||
|
||||
require.Len(t, hookOpts.OldCommitIDs, 1)
|
||||
assert.Equal(t, tt.oldCommitID, hookOpts.OldCommitIDs[0])
|
||||
require.Len(t, hookOpts.NewCommitIDs, 1)
|
||||
assert.Equal(t, tt.newCommitID, hookOpts.NewCommitIDs[0])
|
||||
require.Len(t, hookOpts.RefFullNames, 1)
|
||||
assert.Equal(t, git.RefName(tt.refFullName), hookOpts.RefFullNames[0])
|
||||
})
|
||||
|
||||
// seek stdin back to beginning
|
||||
_, err = f.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
// reset state from prev test
|
||||
serverError = nil
|
||||
hookOpts = nil
|
||||
|
||||
t.Run("post-receive", func(t *testing.T) {
|
||||
app := cli.Command{}
|
||||
app.Commands = []*cli.Command{subcmdHookPostReceive()}
|
||||
|
||||
finish := captureOutput(t, os.Stdout)
|
||||
err = app.Run(t.Context(), []string{"./forgejo", "post-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
require.Empty(t, out)
|
||||
|
||||
require.NoError(t, serverError)
|
||||
require.NotNil(t, hookOpts)
|
||||
|
||||
require.Len(t, hookOpts.OldCommitIDs, 1)
|
||||
assert.Equal(t, tt.oldCommitID, hookOpts.OldCommitIDs[0])
|
||||
require.Len(t, hookOpts.NewCommitIDs, 1)
|
||||
assert.Equal(t, tt.newCommitID, hookOpts.NewCommitIDs[0])
|
||||
require.Len(t, hookOpts.RefFullNames, 1)
|
||||
assert.Equal(t, git.RefName(tt.refFullName), hookOpts.RefFullNames[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ RUN_USER = ; git
|
|||
DB_TYPE = sqlite3
|
||||
;PATH= ; defaults to data/forgejo.db
|
||||
;SQLITE_TIMEOUT = ; Query timeout defaults to: 500
|
||||
;SQLITE_JOURNAL_MODE = ; defaults to sqlite database default (often DELETE), can be used to enable WAL mode. https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
;SQLITE_JOURNAL_MODE = WAL; defaults to sqlite database default (often DELETE), can be used to enable WAL mode. https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -528,11 +528,7 @@ export default tseslint.config(
|
|||
'no-this-before-super': [2],
|
||||
'no-throw-literal': [2],
|
||||
'no-undef-init': [2],
|
||||
|
||||
'no-undef': [2, {
|
||||
typeof: true,
|
||||
}],
|
||||
|
||||
'no-undef': [0],
|
||||
'no-undefined': [0],
|
||||
'no-underscore-dangle': [0],
|
||||
'no-unexpected-multiline': [2],
|
||||
|
|
|
|||
59
go.mod
59
go.mod
|
|
@ -2,22 +2,22 @@ module forgejo.org
|
|||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
toolchain go1.25.9
|
||||
|
||||
require (
|
||||
code.forgejo.org/f3/gof3/v3 v3.11.1
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
code.forgejo.org/forgejo/actions-proto v0.5.3
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0
|
||||
code.forgejo.org/forgejo/levelqueue v1.0.0
|
||||
code.forgejo.org/forgejo/reply v1.0.2
|
||||
code.forgejo.org/forgejo/runner/v12 v12.2.0
|
||||
code.forgejo.org/forgejo/runner/v12 v12.6.4
|
||||
code.forgejo.org/go-chi/binding v1.0.1
|
||||
code.forgejo.org/go-chi/cache v1.0.1
|
||||
code.forgejo.org/go-chi/captcha v1.0.2
|
||||
code.forgejo.org/go-chi/session v1.0.2
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.19.1
|
||||
|
|
@ -45,7 +45,7 @@ require (
|
|||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||
github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/chi/v5 v5.2.4
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.2
|
||||
|
|
@ -58,7 +58,7 @@ require (
|
|||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/go-github/v74 v74.0.0
|
||||
github.com/google/go-github/v81 v81.0.0
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
|
|
@ -71,11 +71,11 @@ require (
|
|||
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.2
|
||||
github.com/klauspost/compress v1.18.3
|
||||
github.com/klauspost/cpuid/v2 v2.2.11
|
||||
github.com/markbates/goth v1.80.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.32
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
github.com/meilisearch/meilisearch-go v0.34.0
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
|
|
@ -102,13 +102,13 @@ require (
|
|||
gitlab.com/gitlab-org/api/client-go v0.143.2
|
||||
go.uber.org/mock v0.6.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/image v0.33.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/image v0.38.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/text v0.32.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.35.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
|
@ -120,10 +120,8 @@ require (
|
|||
require (
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
|
|
@ -158,8 +156,7 @@ require (
|
|||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
|
|
@ -169,7 +166,6 @@ require (
|
|||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // 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/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||
|
|
@ -178,8 +174,8 @@ require (
|
|||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.8.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.17.1 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.3 // indirect
|
||||
|
|
@ -195,7 +191,6 @@ require (
|
|||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
|
|
@ -211,9 +206,8 @@ require (
|
|||
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/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/lib/pq v1.11.2 // indirect
|
||||
github.com/libdns/libdns v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
|
|
@ -239,23 +233,20 @@ require (
|
|||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rhysd/actionlint v1.7.8 // indirect
|
||||
github.com/rhysd/actionlint v1.7.10 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/zeebo/assert v1.3.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
|
|
@ -263,11 +254,11 @@ require (
|
|||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
@ -282,4 +273,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 xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.4
|
||||
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.8
|
||||
|
|
|
|||
149
go.sum
149
go.sum
|
|
@ -20,8 +20,8 @@ code.forgejo.org/f3/gof3/v3 v3.11.1 h1:c0vE8XvqpbXuSv8gzttn96k5T2FQi0u9bYnux46qS
|
|||
code.forgejo.org/f3/gof3/v3 v3.11.1/go.mod h1:1p2UKrqZiwxKneQF2DKrMnc403YIgR/lfcfvadZtmDs=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
|
||||
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
|
||||
code.forgejo.org/forgejo/actions-proto v0.5.3 h1:dDProRNB4CDvEl9gfo8jkiVfGdiW7fXAt5TM9Irka28=
|
||||
code.forgejo.org/forgejo/actions-proto v0.5.3/go.mod h1:33iTdur/jVa/wAQP+BuciRTK9WZcVaxy0BNEnSWWFDM=
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0 h1:dw1Dogk9A4V/yrLVkhe9dSZPsqNAIkI1kCXPSqG3tZA=
|
||||
code.forgejo.org/forgejo/actions-proto v0.6.0/go.mod h1:+444hHBs9/qDh5X/AedaTv0Egj3vd/EXP93vg9zFV2E=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
|
||||
code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk=
|
||||
code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA=
|
||||
|
|
@ -30,8 +30,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
|
|||
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.2.0 h1:CNRdZqXD32xZOdlQe154c+rIY6VcQ3avEyBqKcAy9SU=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.2.0/go.mod h1:m6i/RnHQObdagTZUUPR+Nb2Th3VBLOHzjZ6tVw7F0qs=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.6.4 h1:nhYj2wSj5BVKxcF0bRtMt/A9iGkxHPFJiIui+T/4mrc=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.6.4/go.mod h1:34ATLtcxtOgjAJiINaJvBoNJiKpL1hGn0kt+gk+zdyk=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
|
||||
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
|
||||
|
|
@ -42,12 +42,12 @@ code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkA
|
|||
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
|
||||
code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc=
|
||||
code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.4 h1:kyJHREXNEIuzpMwQoouTbUldPP6s/UlL3ZAcNlO4C5s=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.4/go.mod h1:5ouTxqMcalQUvlBpQynRpzu/44GwaMpkA1nU+encsDE=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8 h1:dsSKm2nus0NhHsqYxeuB3Gldk6TtlusD1CBGV6V1SS0=
|
||||
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8/go.mod h1:A7sFd3BFmRp20h6drSsCXgQRQdF8Vz8HuCSrzFS3m90=
|
||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0 h1:Hof0MCcsa+1fS17gf86fTTZ8AQnMY9h9kzcc+2C6mVg=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.0/go.mod h1:9sutT1axa/kSdlPLlRFjCNKmyo/KNx8eX3XZvWBlAEY=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1 h1:qnujLH4/Yk/CFtFMmtjozbdV6Ry5G3Q/E/mLlWm/gQI=
|
||||
code.superseriousbusiness.org/exif-terminator v0.11.1/go.mod h1:/Z+3DHSrefCzzN5ePkGjVYKFErRimoeUf694Gz8Pn/Y=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
|
||||
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
|
||||
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
|
||||
|
|
@ -56,11 +56,9 @@ codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtf
|
|||
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.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
||||
|
|
@ -73,9 +71,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
|||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
|
|
@ -102,8 +97,6 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
|
|||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
|
@ -189,11 +182,9 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
|||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
|
|
@ -241,8 +232,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 h1:CHwUbBVVyKWRX9kt5A/OtwhYUJB32DrFp9xzmjR6cac=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4/go.mod h1:JWRVKHdVW+dkv6F8p+xGCa6a+TyMrqsFbFkSs/aQkrQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
|
|
@ -250,8 +239,6 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
|
|||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
|
|
@ -276,8 +263,8 @@ github.com/go-ap/jsonld v0.0.0-20251216162253-e38fa664ea77/go.mod h1:4h93IBxgfnE
|
|||
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/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
|
||||
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
|
|
@ -295,12 +282,10 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
|||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
|
||||
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
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.1 h1:WnljyxIzSj9BRRUlnmAU35ohDsjRK0EKmL0evDqi5Jk=
|
||||
github.com/go-git/go-git/v5 v5.17.1/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
|
|
@ -369,8 +354,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
|
|
@ -401,8 +384,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
|
||||
github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak=
|
||||
github.com/google/go-github/v81 v81.0.0 h1:hTLugQRxSLD1Yei18fk4A5eYjOGLUBKAl/VCqOfFkZc=
|
||||
github.com/google/go-github/v81 v81.0.0/go.mod h1:upyjaybucIbBIuxgJS7YLOZGziyvvJ92WX6WEBNE3sM=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
|
||||
|
|
@ -492,12 +475,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
|
|
@ -516,8 +497,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/libdns/libdns v1.0.0 h1:IvYaz07JNz6jUQ4h/fv2R4sVnRnm77J/aOuC9B+TQTA=
|
||||
github.com/libdns/libdns v1.0.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
|
|
@ -535,8 +516,8 @@ github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkEN
|
|||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.34.0 h1:P+Ohdx4/PCxXaoI5wNi0LMwPkuiNrF/kGIzBrKYS4tw=
|
||||
github.com/meilisearch/meilisearch-go v0.34.0/go.mod h1:cUVJZ2zMqTvvwIMEEAdsWH+zrHsrLpAw6gm8Lt1MXK0=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
|
|
@ -572,8 +553,8 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG
|
|||
github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||
|
|
@ -608,8 +589,6 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
|||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
|
@ -631,8 +610,8 @@ github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4Vi
|
|||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.7.8 h1:3d+N9ourgAxVYG4z2IFxFIk/YiT6V+VnKASfXGwT60E=
|
||||
github.com/rhysd/actionlint v1.7.8/go.mod h1:3kiS6egcbXG+vQsJIhFxTz+UKaF1JprsE0SKrpCZKvU=
|
||||
github.com/rhysd/actionlint v1.7.10 h1:FL3XIEs72G4/++168vlv5FKOWMSWvWIQw1kBCadyOcM=
|
||||
github.com/rhysd/actionlint v1.7.10/go.mod h1:ZHX/hrmknlsJN73InPTKsKdXpAv9wVdrJy8h8HAwFHg=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
|
|
@ -651,11 +630,8 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCw
|
|||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
|
|
@ -667,7 +643,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
|
|
@ -692,8 +667,6 @@ github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpB
|
|||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
||||
|
|
@ -734,8 +707,8 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
|||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
@ -745,13 +718,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -760,12 +732,12 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
|||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
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.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
|
||||
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
@ -787,8 +759,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -810,7 +782,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
|
|
@ -820,8 +791,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.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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -842,8 +813,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -856,7 +827,6 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -865,13 +835,10 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -882,8 +849,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
@ -893,8 +860,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -908,8 +875,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.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.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
|
|
@ -943,8 +910,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -1026,14 +993,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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
|
|
|||
|
|
@ -455,6 +455,10 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error)
|
|||
return run, nil
|
||||
}
|
||||
|
||||
// Error returned when ActionRun's optimistic concurrency control has indicated that the record has been updated in the
|
||||
// database by another session since it was loaded in-memory in this session.
|
||||
var ErrActionRunOutOfDate = errors.New("run has changed")
|
||||
|
||||
// UpdateRun updates a run.
|
||||
// It requires the inputted run has Version set.
|
||||
// It will return error if the version is not matched (it means the run has been changed after loaded).
|
||||
|
|
@ -471,8 +475,9 @@ func UpdateRunWithoutNotification(ctx context.Context, run *ActionRun, cols ...s
|
|||
return err
|
||||
}
|
||||
if affected == 0 {
|
||||
return errors.New("run has changed")
|
||||
// It's impossible that the run is not found, since Gitea never deletes runs.
|
||||
// UPDATE has no conditions on it, and we never delete runs, so the only possible cause of this is
|
||||
// `xorm:"version"` tagged field indicated that the version has changed since the record was loaded.
|
||||
return ErrActionRunOutOfDate
|
||||
}
|
||||
|
||||
if run.Status != 0 || slices.Contains(cols, "status") {
|
||||
|
|
@ -495,4 +500,35 @@ func UpdateRunWithoutNotification(ctx context.Context, run *ActionRun, cols ...s
|
|||
return nil
|
||||
}
|
||||
|
||||
// Compute the Status, Started, and Stopped fields of an ActionRun based upon the current job state within the run.
|
||||
// Returned is the [ActionRun] with modifications if necessary, a slice of column names that have been updated, or an
|
||||
// error if the calculation failed. The caller is responsible for then invoking [actions_service.UpdateRun] for an
|
||||
// update with notifications, or [actions_model.UpdateRunWithoutNotification] if notifications are already handled.
|
||||
func ComputeRunStatus(ctx context.Context, runID int64) (run *ActionRun, columns []string, err error) {
|
||||
run, err = GetRunByID(ctx, runID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
jobs, err := GetRunJobsByRunID(ctx, runID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newStatus := AggregateJobStatus(jobs)
|
||||
if run.Status != newStatus {
|
||||
run.Status = newStatus
|
||||
columns = append(columns, "status")
|
||||
}
|
||||
if run.Started.IsZero() && run.Status.IsRunning() {
|
||||
run.Started = timeutil.TimeStampNow()
|
||||
columns = append(columns, "started")
|
||||
}
|
||||
if run.Stopped.IsZero() && run.Status.IsDone() {
|
||||
run.Stopped = timeutil.TimeStampNow()
|
||||
columns = append(columns, "stopped")
|
||||
}
|
||||
|
||||
return run, columns, nil
|
||||
}
|
||||
|
||||
type ActionRunIndex db.ResourceIndex
|
||||
|
|
|
|||
|
|
@ -137,6 +137,22 @@ func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*ActionRunJob, error
|
|||
return jobs, nil
|
||||
}
|
||||
|
||||
// Check if the ActionRun has any jobs other than those included in the jobs parameter.
|
||||
func RunHasOtherJobs(ctx context.Context, runID int64, jobs []*ActionRunJob) (bool, error) {
|
||||
jobIDs := make([]int64, len(jobs))
|
||||
for i, job := range jobs {
|
||||
jobIDs[i] = job.ID
|
||||
}
|
||||
otherJobs, err := db.GetEngine(ctx).
|
||||
Where("run_id = ?", runID).
|
||||
Where(builder.NotIn("id", jobIDs)).
|
||||
Count(&ActionRunJob{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return otherJobs > 0, nil
|
||||
}
|
||||
|
||||
// All calls to UpdateRunJobWithoutNotification that change run.Status for any run from a not done status to a done status must call the ActionRunNowDone notification channel.
|
||||
// Use the wrapper function UpdateRunJob instead.
|
||||
func UpdateRunJobWithoutNotification(ctx context.Context, job *ActionRunJob, cond builder.Cond, cols ...string) (int64, error) {
|
||||
|
|
@ -174,34 +190,18 @@ func UpdateRunJobWithoutNotification(ctx context.Context, job *ActionRunJob, con
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Other goroutines may aggregate the status of the run and update it too.
|
||||
// So we need load the run and its jobs before updating the run.
|
||||
run, err := GetRunByID(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
jobs, err := GetRunJobsByRunID(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
run.Status = AggregateJobStatus(jobs)
|
||||
if run.Started.IsZero() && run.Status.IsRunning() {
|
||||
run.Started = timeutil.TimeStampNow()
|
||||
}
|
||||
if run.Stopped.IsZero() && run.Status.IsDone() {
|
||||
run.Stopped = timeutil.TimeStampNow()
|
||||
}
|
||||
// As the caller has to ensure the ActionRunNowDone notification is sent we can ignore doing so here.
|
||||
if err := UpdateRunWithoutNotification(ctx, run, "status", "started", "stopped"); err != nil {
|
||||
return 0, fmt.Errorf("update run %d: %w", run.ID, err)
|
||||
}
|
||||
run, columns, err := ComputeRunStatus(ctx, job.RunID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("compute run status: %w", err)
|
||||
}
|
||||
if err := UpdateRunWithoutNotification(ctx, run, columns...); err != nil {
|
||||
return 0, fmt.Errorf("update run %d: %w", run.ID, err)
|
||||
}
|
||||
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
var AggregateJobStatus = func(jobs []*ActionRunJob) Status {
|
||||
allSuccessOrSkipped := len(jobs) != 0
|
||||
allSkipped := len(jobs) != 0
|
||||
var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
|
@ -48,14 +49,15 @@ func (jobs ActionJobList) LoadAttributes(ctx context.Context, withRepo bool) err
|
|||
|
||||
type FindRunJobOptions struct {
|
||||
db.ListOptions
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
Events []string // []webhook_module.HookEventType
|
||||
RunNumber int64
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
Events []string // []webhook_module.HookEventType
|
||||
RunNumber int64
|
||||
RunNeedsApproval optional.Option[bool]
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
|
|
@ -84,5 +86,12 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|||
if opts.RunNumber > 0 {
|
||||
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
|
||||
}
|
||||
if opts.RunNeedsApproval.Has() {
|
||||
cond = cond.And(builder.Exists(builder.Select("id").From("action_run", "outer_run").
|
||||
Where(builder.Eq{
|
||||
"outer_run.need_approval": opts.RunNeedsApproval.Value(),
|
||||
"outer_run.id": builder.Expr("run_id"),
|
||||
})))
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,3 +159,23 @@ func TestActionRunJob_IsIncompleteRunsOn(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunHasOtherJobs(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
jobs, err := GetRunJobsByRunID(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, jobs, 1)
|
||||
|
||||
has, err := RunHasOtherJobs(t.Context(), 791, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = RunHasOtherJobs(t.Context(), 791, []*ActionRunJob{})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = RunHasOtherJobs(t.Context(), 791, jobs)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
|
@ -65,15 +64,14 @@ func (runs RunList) LoadRepos(ctx context.Context) error {
|
|||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
ConcurrencyGroup string
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
WorkflowID string
|
||||
Ref string // the commit/tag/… that caused this workflow
|
||||
TriggerUserID int64
|
||||
TriggerEvent webhook_module.HookEventType
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status []Status
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
|
|
@ -102,9 +100,6 @@ func (opts FindRunOptions) ToConds() builder.Cond {
|
|||
if opts.TriggerEvent != "" {
|
||||
cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
|
||||
}
|
||||
if opts.ConcurrencyGroup != "" {
|
||||
cond = cond.And(builder.Eq{"concurrency_group": strings.ToLower(opts.ConcurrencyGroup)})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -272,3 +272,122 @@ jobs:
|
|||
// Expect job with an incomplete runs-on to be StatusBlocked:
|
||||
assert.Equal(t, StatusBlocked, job.Status)
|
||||
}
|
||||
|
||||
func TestComputeRunStatus(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("no changes", func(t *testing.T) {
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusSuccess, run.Status)
|
||||
assert.NotContains(t, columns, "status")
|
||||
assert.EqualValues(t, 1683636528, run.Started)
|
||||
assert.NotContains(t, columns, "started")
|
||||
assert.EqualValues(t, 1683636626, run.Stopped)
|
||||
assert.NotContains(t, columns, "stopped")
|
||||
})
|
||||
|
||||
t.Run("change status", func(t *testing.T) {
|
||||
job := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: 192})
|
||||
job.Status = StatusFailure
|
||||
affected, err := db.GetEngine(t.Context()).Cols("status").ID(job.ID).Update(job)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusFailure, run.Status)
|
||||
assert.Contains(t, columns, "status")
|
||||
assert.NotContains(t, columns, "started")
|
||||
assert.NotContains(t, columns, "stopped")
|
||||
})
|
||||
|
||||
t.Run("won't change started if not running", func(t *testing.T) {
|
||||
job := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: 192})
|
||||
job.Status = StatusBlocked
|
||||
affected, err := db.GetEngine(t.Context()).Cols("status").ID(job.ID).Update(job)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
preRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: 791})
|
||||
preRun.Started = 0
|
||||
affected, err = db.GetEngine(t.Context()).Cols("started").ID(preRun.ID).Update(preRun)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusBlocked, run.Status)
|
||||
assert.EqualValues(t, 0, run.Started)
|
||||
assert.Contains(t, columns, "status")
|
||||
assert.NotContains(t, columns, "started")
|
||||
assert.NotContains(t, columns, "stopped")
|
||||
})
|
||||
|
||||
t.Run("change started", func(t *testing.T) {
|
||||
// Need the job to be "Running" for started to appear to change
|
||||
job := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: 192})
|
||||
job.Status = StatusRunning
|
||||
affected, err := db.GetEngine(t.Context()).Cols("status").ID(job.ID).Update(job)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
preRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: 791})
|
||||
preRun.Started = 0
|
||||
affected, err = db.GetEngine(t.Context()).Cols("started").ID(preRun.ID).Update(preRun)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusRunning, run.Status)
|
||||
assert.NotEqualValues(t, 0, run.Started)
|
||||
assert.Contains(t, columns, "status")
|
||||
assert.Contains(t, columns, "started")
|
||||
assert.NotContains(t, columns, "stopped")
|
||||
})
|
||||
|
||||
t.Run("won't change stopped if not done", func(t *testing.T) {
|
||||
job := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: 192})
|
||||
job.Status = StatusRunning
|
||||
affected, err := db.GetEngine(t.Context()).Cols("status").ID(job.ID).Update(job)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
preRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: 791})
|
||||
preRun.Stopped = 0
|
||||
affected, err = db.GetEngine(t.Context()).Cols("stopped").ID(preRun.ID).Update(preRun)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusRunning, run.Status)
|
||||
assert.EqualValues(t, 0, run.Stopped)
|
||||
assert.Contains(t, columns, "status")
|
||||
assert.NotContains(t, columns, "stopped")
|
||||
})
|
||||
|
||||
t.Run("change stopped", func(t *testing.T) {
|
||||
// Need the job to be some version of Done for stopped to appear to change
|
||||
job := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: 192})
|
||||
job.Status = StatusSuccess
|
||||
affected, err := db.GetEngine(t.Context()).Cols("status").ID(job.ID).Update(job)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
preRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: 791})
|
||||
preRun.Stopped = 0
|
||||
affected, err = db.GetEngine(t.Context()).Cols("stopped").ID(preRun.ID).Update(preRun)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
run, columns, err := ComputeRunStatus(t.Context(), 791)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, StatusSuccess, run.Status)
|
||||
assert.NotEqualValues(t, 0, run.Stopped)
|
||||
assert.NotContains(t, columns, "status")
|
||||
assert.NotContains(t, columns, "started")
|
||||
assert.Contains(t, columns, "stopped")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,13 @@ func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys[0], err
|
||||
|
||||
for _, key := range keys {
|
||||
if key.PrimaryKey.KeyIdString() == k.KeyID {
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("key with %s id not found", k.KeyID)
|
||||
}
|
||||
|
||||
// parseSubGPGKey parse a sub Key
|
||||
|
|
|
|||
|
|
@ -8,12 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/auth"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/perm/access"
|
||||
_ "forgejo.org/modules/testimport"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/perm/access"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
|
|
@ -84,3 +86,123 @@ func extractFieldValue(bean any, fieldName string) any {
|
|||
field := v.FieldByName(fieldName)
|
||||
return field.Interface()
|
||||
}
|
||||
|
||||
// IterateByKeyset iterates all the records on a database (matching the provided condition) in the order of specified
|
||||
// order fields, and invokes the provided handler function for each record. It is safe to UPDATE or DELETE the record in
|
||||
// the handler function, as long as the order fields are not mutated on the record (which could cause records to be
|
||||
// missed or iterated multiple times).
|
||||
//
|
||||
// Assuming order fields a, b, and c, then database queries will be performed as "SELECT * FROM table WHERE (a, b, c) >
|
||||
// (last_a, last_b, last_c) ORDER BY a, b, c LIMIT buffer_size" repeatedly until the query returns no records (except
|
||||
// the first query will have no WHERE clause).
|
||||
//
|
||||
// Critical requirements for proper usage:
|
||||
//
|
||||
// - the order fields encompass at least one UNIQUE or PRIMARY KEY constraint of the table to ensure that records are
|
||||
// not duplicated -- for example, if the table has a unique index covering `(repo_id, index)`, then it would be safe to
|
||||
// use this function as long as both fields (in either order) are provided as order fields.
|
||||
//
|
||||
// - none of the order fields may have NULL values in them, as the `=` and `>` comparisons being performed by the
|
||||
// iterative queries will not operate on these records consistently as they do with other values.
|
||||
//
|
||||
// This implementation could be a much simpler streaming scan of the query results, except that doesn't permit making
|
||||
// any additional database queries or data modifications in the target function -- SQLite cannot write while holding a
|
||||
// read lock. Buffering pages of data in-memory avoids that issue.
|
||||
//
|
||||
// Performance:
|
||||
//
|
||||
// - High performance will result from an alignment of an index on the table with the order fields, in the same field
|
||||
// order, even if additional ordering fields could be provided after the index fields. In the absence of this index
|
||||
// alignment, it is reasonable to expect that every extra page of data accessed will require a query that will perform
|
||||
// an index scan (if available) or sequential scan of the target table. In testing on the `commit_status` table with
|
||||
// 455k records, a fully index-supported ordering allowed each query page to execute in 0.18ms, as opposed to 80ms
|
||||
// per-query without matching supporting index.
|
||||
//
|
||||
// - In the absence of a matching index, slower per-query performance can be compensated with a larger `batchSize`
|
||||
// parameter, which controls how many records to fetch at once and therefore reduces the number of queries required.
|
||||
// This requires more memory. Similar `commit_status` table testing showed these stats for iteration time and memory
|
||||
// usage for different buffer sizes; specifics will vary depending on the target table:
|
||||
// - buffer size = 1,000,000 - iterates in 2.8 seconds, consumes 363 MB of RAM
|
||||
// - buffer size = 100,000 - iterates in 3.5 seconds, consume 130 MB of RAM
|
||||
// - buffer size = 10,000 - iterates in 7.1 seconds, consumes 59 MB of RAM
|
||||
// - buffer size = 1,000 - iterates in 33.9 seconds, consumes 42 MB of RAM
|
||||
func IterateByKeyset[Bean any](ctx context.Context, cond builder.Cond, orderFields []string, batchSize int, f func(ctx context.Context, bean *Bean) error) error {
|
||||
var dummy Bean
|
||||
|
||||
if len(orderFields) == 0 {
|
||||
return errors.New("orderFields must be provided")
|
||||
}
|
||||
|
||||
table, err := TableInfo(&dummy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch table info for bean %v: %w", dummy, err)
|
||||
}
|
||||
goFieldNames := make([]string, len(orderFields))
|
||||
for i, f := range orderFields {
|
||||
goFieldNames[i] = table.GetColumn(f).FieldName
|
||||
}
|
||||
sqlFieldNames := make([]string, len(orderFields))
|
||||
for i, f := range orderFields {
|
||||
// Support field names like "index" which need quoting in builder.Cond & OrderBy
|
||||
sqlFieldNames[i] = x.Dialect().Quoter().Quote(f)
|
||||
}
|
||||
|
||||
var lastKey []any
|
||||
|
||||
// For the order fields, generate clauses (a, b, c) and (?, ?, ?) which will be used in the WHERE clause when
|
||||
// reading additional pages of data.
|
||||
rowValue := strings.Builder{}
|
||||
rowParameterValue := strings.Builder{}
|
||||
rowValue.WriteString("(")
|
||||
rowParameterValue.WriteString("(")
|
||||
for i, f := range sqlFieldNames {
|
||||
rowValue.WriteString(f)
|
||||
rowParameterValue.WriteString("?")
|
||||
if i != len(sqlFieldNames)-1 {
|
||||
rowValue.WriteString(", ")
|
||||
rowParameterValue.WriteString(", ")
|
||||
}
|
||||
}
|
||||
rowValue.WriteString(")")
|
||||
rowParameterValue.WriteString(")")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
beans := make([]*Bean, 0, batchSize)
|
||||
|
||||
sess := GetEngine(ctx)
|
||||
for _, f := range sqlFieldNames {
|
||||
sess = sess.OrderBy(f)
|
||||
}
|
||||
if cond != nil {
|
||||
sess = sess.Where(cond)
|
||||
}
|
||||
if lastKey != nil {
|
||||
sess = sess.Where(
|
||||
builder.Expr(fmt.Sprintf("%s > %s", rowValue.String(), rowParameterValue.String()), lastKey...))
|
||||
}
|
||||
|
||||
if err := sess.Limit(batchSize).Find(&beans); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(beans) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, bean := range beans {
|
||||
if err := f(ctx, bean); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lastBean := beans[len(beans)-1]
|
||||
lastKey = make([]any, len(goFieldNames))
|
||||
for i := range goFieldNames {
|
||||
lastKey[i] = extractFieldValue(lastBean, goFieldNames[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/setting"
|
||||
|
|
@ -21,7 +22,6 @@ import (
|
|||
)
|
||||
|
||||
func TestIterate(t *testing.T) {
|
||||
db.SetLogSQL(t.Context(), true)
|
||||
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 50)()
|
||||
|
||||
t.Run("No Modifications", func(t *testing.T) {
|
||||
|
|
@ -115,3 +115,31 @@ func TestIterate(t *testing.T) {
|
|||
assert.Empty(t, remainingRepoIDs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIterateMultipleFields(t *testing.T) {
|
||||
for _, bufferSize := range []int{1, 2, 3, 10} { // 8 records in fixture
|
||||
t.Run(fmt.Sprintf("No Modifications bufferSize=%d", bufferSize), func(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// Fetch all the commit status IDs...
|
||||
var remainingIDs []int64
|
||||
err := db.GetEngine(t.Context()).Table(&git_model.CommitStatus{}).Cols("id").Find(&remainingIDs)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, remainingIDs)
|
||||
|
||||
// Ensure that every repo unit ID is found when doing iterate:
|
||||
err = db.IterateByKeyset(t.Context(),
|
||||
nil,
|
||||
[]string{"repo_id", "sha", "context", "index", "id"},
|
||||
bufferSize,
|
||||
func(ctx context.Context, commit_status *git_model.CommitStatus) error {
|
||||
remainingIDs = slices.DeleteFunc(remainingIDs, func(n int64) bool {
|
||||
return commit_status.ID == n
|
||||
})
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remainingIDs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/repo"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -533,3 +533,43 @@
|
|||
updated: 1683636626
|
||||
need_approval: false
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 895
|
||||
title: "job output"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "test.yaml"
|
||||
index: 191
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
is_fork_pull_request: false
|
||||
status: 2
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: false
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 896
|
||||
title: "job output"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "test.yaml"
|
||||
index: 192
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
is_fork_pull_request: false
|
||||
status: 2
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: false
|
||||
approved_by: 0
|
||||
|
|
|
|||
|
|
@ -69,6 +69,66 @@
|
|||
status: 5
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 197
|
||||
run_id: 895
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
name: job1 (1)
|
||||
attempt: 0
|
||||
job_id: job1
|
||||
task_id: 54
|
||||
status: 2 # failure
|
||||
runs_on: '["postmarketOS"]'
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 198
|
||||
run_id: 895
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
name: job1 (2)
|
||||
attempt: 0
|
||||
job_id: job1
|
||||
task_id: 55
|
||||
status: 6 # running
|
||||
runs_on: '["postmarketOS"]'
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 199
|
||||
run_id: 896
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
name: job1 (1)
|
||||
attempt: 0
|
||||
job_id: job1
|
||||
task_id: 56
|
||||
status: 2 # failure
|
||||
runs_on: '["postmarketOS"]'
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 200
|
||||
run_id: 896
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
name: job1 (2)
|
||||
attempt: 0
|
||||
job_id: job1
|
||||
task_id: 57
|
||||
status: 1 # success
|
||||
runs_on: '["postmarketOS"]'
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 292
|
||||
run_id: 891
|
||||
|
|
|
|||
|
|
@ -157,3 +157,83 @@
|
|||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: false
|
||||
-
|
||||
id: 54
|
||||
job_id: 197
|
||||
attempt: 0
|
||||
runner_id: 1
|
||||
status: 2 # failure
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784225
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: true
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: false
|
||||
-
|
||||
id: 55
|
||||
job_id: 198
|
||||
attempt: 0
|
||||
runner_id: 1
|
||||
status: 6 # running
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784226
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: true
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: false
|
||||
-
|
||||
id: 56
|
||||
job_id: 199
|
||||
attempt: 0
|
||||
runner_id: 1
|
||||
status: 2 # failure
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784227
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: true
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: false
|
||||
-
|
||||
id: 57
|
||||
job_id: 200
|
||||
attempt: 0
|
||||
runner_id: 1
|
||||
status: 1 # success
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: false
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784228
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: true
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: false
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 2
|
||||
type: 1
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 2
|
||||
type: 1
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
||||
|
|
@ -66,6 +66,18 @@
|
|||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 2
|
||||
type: 1
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
||||
-
|
||||
id: 7
|
||||
title: project on org3
|
||||
owner_id: 3
|
||||
repo_id: 0
|
||||
is_closed: false
|
||||
creator_id: 2
|
||||
board_type: 1
|
||||
type: 1
|
||||
created_unix: 1688973000
|
||||
updated_unix: 1688973000
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
email: user2@example.com
|
||||
keep_email_private: true
|
||||
keep_pronouns_private: true
|
||||
pronouns: he/him
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -4,28 +4,30 @@
|
|||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func syncDoctorForeignKey(x *xorm.Engine, beans []any) error {
|
||||
for _, bean := range beans {
|
||||
// Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncDoctorForeignKey
|
||||
// is used with partial bean definitions; so we disable that option
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
if err != nil {
|
||||
if errors.Is(err, xorm.ErrForeignKeyViolation) {
|
||||
tableName := x.TableName(bean)
|
||||
log.Error(
|
||||
"Foreign key creation on table %s failed. Run `forgejo doctor check --all` to identify the orphaned records preventing this foreign key from being created. Error was: %v",
|
||||
tableName, err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
// syncForeignKeyWithDelete will delete any records that match `cond`, and if present, log and warn to the
|
||||
// administrator; then it will perform an `xorm.Sync()` in order to create foreign keys on the table definition.
|
||||
func syncForeignKeyWithDelete(x *xorm.Engine, bean any, cond builder.Cond) error {
|
||||
rowsDeleted, err := x.Where(cond).Delete(bean)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure to delete inconsistent records before foreign key sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
if rowsDeleted > 0 {
|
||||
tableName := x.TableName(bean)
|
||||
log.Warn(
|
||||
"Foreign key creation on table %s required deleting %d records with inconsistent foreign key values.",
|
||||
tableName, rowsDeleted)
|
||||
}
|
||||
|
||||
// Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncForeignKeyWithDelete
|
||||
// is used with partial bean definitions; so we disable that option
|
||||
_, err = x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
|
|
@ -19,7 +20,11 @@ func addForeignKeysCollaboration(x *xorm.Engine) error {
|
|||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(repository, id)"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(user, id)"`
|
||||
}
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
return syncForeignKeyWithDelete(x,
|
||||
new(Collaboration),
|
||||
})
|
||||
builder.Or(
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM repository WHERE repository.id = collaboration.repo_id)"),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM `user` WHERE `user`.id = collaboration.user_id)"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_addForeignKeysCollaboration(t *testing.T) {
|
||||
type AccessMode int
|
||||
type Collaboration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(User), new(Repository), new(Collaboration))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, addForeignKeysCollaboration(x))
|
||||
|
||||
var remainingRecords []*Collaboration
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("collaboration").
|
||||
Select("`id`, `repo_id`, `user_id`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingRecords))
|
||||
assert.Equal(t,
|
||||
[]*Collaboration{
|
||||
{ID: 1, UserID: 1, RepoID: 1},
|
||||
},
|
||||
remainingRecords)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
|
|
@ -18,7 +19,8 @@ func addForeignKeysForgejoAuthToken(x *xorm.Engine) error {
|
|||
type ForgejoAuthToken struct {
|
||||
UID int64 `xorm:"INDEX REFERENCES(user, id)"`
|
||||
}
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
return syncForeignKeyWithDelete(x,
|
||||
new(ForgejoAuthToken),
|
||||
})
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM `user` WHERE `user`.id = forgejo_auth_token.uid)"),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_addForeignKeysForgejoAuthToken(t *testing.T) {
|
||||
type AuthorizationPurpose string
|
||||
type ForgejoAuthToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
LookupKey string `xorm:"INDEX UNIQUE"`
|
||||
HashedValidator string
|
||||
Purpose AuthorizationPurpose `xorm:"NOT NULL DEFAULT 'long_term_authorization'"`
|
||||
Expiry timeutil.TimeStamp
|
||||
}
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(User), new(ForgejoAuthToken))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, addForeignKeysForgejoAuthToken(x))
|
||||
|
||||
var remainingRecords []*ForgejoAuthToken
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("forgejo_auth_token").
|
||||
Select("`id`, `uid`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingRecords))
|
||||
assert.Equal(t,
|
||||
[]*ForgejoAuthToken{
|
||||
{ID: 1, UID: 1},
|
||||
},
|
||||
remainingRecords)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
|
|
@ -19,7 +20,11 @@ func addForeignKeysPullRequest1(x *xorm.Engine) error {
|
|||
IssueID int64 `xorm:"INDEX REFERENCES(issue, id)"`
|
||||
BaseRepoID int64 `xorm:"INDEX REFERENCES(repository, id)"`
|
||||
}
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
return syncForeignKeyWithDelete(x,
|
||||
new(PullRequest),
|
||||
})
|
||||
builder.Or(
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM issue WHERE issue.id = pull_request.issue_id)"),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM repository WHERE repository.id = pull_request.base_repo_id)"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_addForeignKeysPullRequest1(t *testing.T) {
|
||||
type PullRequestType int
|
||||
type PullRequestStatus int
|
||||
type PullRequestFlow int
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type PullRequestType
|
||||
Status PullRequestStatus
|
||||
ConflictedFiles []string `xorm:"TEXT JSON"`
|
||||
CommitsAhead int
|
||||
CommitsBehind int
|
||||
ChangedProtectedFiles []string `xorm:"TEXT JSON"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Index int64
|
||||
HeadRepoID int64 `xorm:"INDEX"`
|
||||
BaseRepoID int64 `xorm:"INDEX"`
|
||||
HeadBranch string
|
||||
BaseBranch string
|
||||
MergeBase string `xorm:"VARCHAR(64)"`
|
||||
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
|
||||
HasMerged bool `xorm:"INDEX"`
|
||||
MergedCommitID string `xorm:"VARCHAR(64)"`
|
||||
MergerID int64 `xorm:"INDEX"`
|
||||
MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"`
|
||||
Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Issue), new(Repository), new(PullRequest))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, addForeignKeysPullRequest1(x))
|
||||
|
||||
var remainingRecords []*PullRequest
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("pull_request").
|
||||
Select("`id`, `issue_id`, `base_repo_id`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingRecords))
|
||||
assert.Equal(t,
|
||||
[]*PullRequest{
|
||||
{ID: 1, BaseRepoID: 1, IssueID: 1},
|
||||
},
|
||||
remainingRecords)
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
|
|
@ -13,23 +12,24 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func syncDoctorForeignKey(x *xorm.Engine, beans []any) error {
|
||||
for _, bean := range beans {
|
||||
// Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncDoctorForeignKey
|
||||
// is used with partial bean definitions; so we disable that option
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
if err != nil {
|
||||
if errors.Is(err, xorm.ErrForeignKeyViolation) {
|
||||
tableName := x.TableName(bean)
|
||||
log.Error(
|
||||
"Foreign key creation on table %s failed. Run `forgejo doctor check --all` to identify the orphaned records preventing this foreign key from being created. Error was: %v",
|
||||
tableName, err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
// syncForeignKeyWithDelete will delete any records that match `cond`, and if present, log and warn to the
|
||||
// administrator; then it will perform an `xorm.Sync()` in order to create foreign keys on the table definition.
|
||||
func syncForeignKeyWithDelete(x *xorm.Engine, bean any, cond builder.Cond) error {
|
||||
rowsDeleted, err := x.Where(cond).Delete(bean)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure to delete inconsistent records before foreign key sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
if rowsDeleted > 0 {
|
||||
tableName := x.TableName(bean)
|
||||
log.Warn(
|
||||
"Foreign key creation on table %s required deleting %d records with inconsistent foreign key values.",
|
||||
tableName, rowsDeleted)
|
||||
}
|
||||
|
||||
// Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncForeignKeyWithDelete
|
||||
// is used with partial bean definitions; so we disable that option
|
||||
_, err = x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
return err
|
||||
}
|
||||
|
||||
func AddForeignKeysStopwatchTrackedTime(x *xorm.Engine) error {
|
||||
|
|
@ -50,6 +50,7 @@ func AddForeignKeysStopwatchTrackedTime(x *xorm.Engine) error {
|
|||
err := x.Table("tracked_time").
|
||||
Join("LEFT", "`user`", "`tracked_time`.user_id = `user`.id").
|
||||
Where(builder.IsNull{"`user`.id"}).
|
||||
Where(builder.NotNull{"tracked_time.user_id"}).
|
||||
Find(&trackedTime)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -63,8 +64,25 @@ func AddForeignKeysStopwatchTrackedTime(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
err = syncForeignKeyWithDelete(x,
|
||||
new(Stopwatch),
|
||||
builder.Or(
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM issue WHERE issue.id = stopwatch.issue_id)"),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM `user` WHERE `user`.id = stopwatch.user_id)"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syncForeignKeyWithDelete(x,
|
||||
new(TrackedTime),
|
||||
})
|
||||
builder.Or(
|
||||
builder.And(
|
||||
builder.Expr("user_id IS NOT NULL"),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM `user` WHERE `user`.id = tracked_time.user_id)"),
|
||||
),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM issue WHERE issue.id = tracked_time.issue_id)"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
75
models/forgejo_migrations_legacy/v41_test.go
Normal file
75
models/forgejo_migrations_legacy/v41_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AddForeignKeysStopwatchTrackedTime(t *testing.T) {
|
||||
type Stopwatch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
type TrackedTime struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
CreatedUnix int64 `xorm:"created"`
|
||||
Time int64 `xorm:"NOT NULL"`
|
||||
Deleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(User), new(Issue), new(Stopwatch), new(TrackedTime))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, AddForeignKeysStopwatchTrackedTime(x))
|
||||
|
||||
var remainingStopwatch []*Stopwatch
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("stopwatch").
|
||||
Select("`id`, `issue_id`, `user_id`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingStopwatch))
|
||||
assert.Equal(t,
|
||||
[]*Stopwatch{
|
||||
{1, 1, 1, 0},
|
||||
},
|
||||
remainingStopwatch,
|
||||
"stopwatch")
|
||||
|
||||
var remainingTrackedTime []*TrackedTime
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("tracked_time").
|
||||
Select("`id`, `issue_id`, `user_id`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingTrackedTime))
|
||||
assert.Equal(t,
|
||||
[]*TrackedTime{
|
||||
{ID: 1, IssueID: 1, UserID: 1},
|
||||
{ID: 4, IssueID: 1, UserID: 0},
|
||||
{ID: 5, IssueID: 1, UserID: 0},
|
||||
},
|
||||
remainingTrackedTime,
|
||||
"tracked_time")
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
|
|
@ -12,7 +13,11 @@ func AddForeignKeysAccess(x *xorm.Engine) error {
|
|||
UserID int64 `xorm:"UNIQUE(s) REFERENCES(user, id)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) REFERENCES(repository, id)"`
|
||||
}
|
||||
return syncDoctorForeignKey(x, []any{
|
||||
return syncForeignKeyWithDelete(x,
|
||||
new(Access),
|
||||
})
|
||||
builder.Or(
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM repository WHERE repository.id = access.repo_id)"),
|
||||
builder.Expr("NOT EXISTS (SELECT id FROM `user` WHERE `user`.id = access.user_id)"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
50
models/forgejo_migrations_legacy/v44_test.go
Normal file
50
models/forgejo_migrations_legacy/v44_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations_legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
migration_tests "forgejo.org/models/gitea_migrations/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_AddForeignKeysAccess(t *testing.T) {
|
||||
type AccessMode int
|
||||
type Access struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Mode AccessMode
|
||||
}
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
x, deferable := migration_tests.PrepareTestEnv(t, 0, new(User), new(Repository), new(Access))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, AddForeignKeysAccess(x))
|
||||
|
||||
var remainingRecords []*Access
|
||||
require.NoError(t,
|
||||
db.GetEngine(t.Context()).
|
||||
Table("access").
|
||||
Select("`id`, `user_id`, `repo_id`").
|
||||
OrderBy("`id`").
|
||||
Find(&remainingRecords))
|
||||
assert.Equal(t,
|
||||
[]*Access{
|
||||
{ID: 1, UserID: 1, RepoID: 1},
|
||||
},
|
||||
remainingRecords)
|
||||
}
|
||||
135
models/git/TestCleanupCommitStatus/commit_status.yml
Normal file
135
models/git/TestCleanupCommitStatus/commit_status.yml
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Fields that should, if changed, prevent deletion: repo_id, sha, context, state, description. The first test sets will
|
||||
# be varying each of these fields independently to confirm they're kept.
|
||||
|
||||
|
||||
# Vary description:
|
||||
-
|
||||
id: 10
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "01"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness
|
||||
-
|
||||
id: 11
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "01"
|
||||
description: "Almost woke up..."
|
||||
context: deploy/awesomeness
|
||||
|
||||
# Vary state:
|
||||
-
|
||||
id: 12
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "02"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness
|
||||
-
|
||||
id: 13
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "success"
|
||||
sha: "02"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness
|
||||
|
||||
# Vary context:
|
||||
-
|
||||
id: 14
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "03"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
-
|
||||
id: 15
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "03"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v2
|
||||
|
||||
# Vary sha:
|
||||
-
|
||||
id: 16
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "04"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
-
|
||||
id: 17
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "05"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
|
||||
# Vary Repo ID:
|
||||
-
|
||||
id: 18
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "06"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
-
|
||||
id: 19
|
||||
index: 2
|
||||
repo_id: 63
|
||||
state: "pending"
|
||||
sha: "06"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
|
||||
# That's all the varying cases, now here's the data that should be affected by the delete:
|
||||
-
|
||||
id: 20
|
||||
index: 1
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "07"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
- # Dupe 1
|
||||
id: 21
|
||||
index: 2
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "07"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
- # Dupe 2
|
||||
id: 22
|
||||
index: 3
|
||||
repo_id: 62
|
||||
state: "pending"
|
||||
sha: "07"
|
||||
description: "Waiting for wake up"
|
||||
context: deploy/awesomeness-v1
|
||||
- # Switched to "success", keep
|
||||
id: 23
|
||||
index: 4
|
||||
repo_id: 62
|
||||
state: "success"
|
||||
sha: "07"
|
||||
description: "Successful!"
|
||||
context: deploy/awesomeness-v1
|
||||
- # Dupe reporting success again
|
||||
id: 24
|
||||
index: 5
|
||||
repo_id: 62
|
||||
state: "success"
|
||||
sha: "07"
|
||||
description: "Successful!"
|
||||
context: deploy/awesomeness-v1
|
||||
|
|
@ -467,3 +467,68 @@ func ParseCommitsWithStatus(ctx context.Context, commits []*git.Commit, repo *re
|
|||
func hashCommitStatusContext(context string) string {
|
||||
return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
|
||||
}
|
||||
|
||||
func CleanupCommitStatus(ctx context.Context, bufferSize, deleteChunkSize int, dryRun bool) error {
|
||||
startTime := time.Now()
|
||||
|
||||
var lastCommitStatus CommitStatus
|
||||
deleteTargets := make([]int64, 0, deleteChunkSize)
|
||||
recordCount := 0
|
||||
deleteCount := 0
|
||||
|
||||
err := db.IterateByKeyset(ctx,
|
||||
nil,
|
||||
[]string{"repo_id", "sha", "context", "index", "id"},
|
||||
bufferSize,
|
||||
func(ctx context.Context, commitStatus *CommitStatus) error {
|
||||
if commitStatus.RepoID != lastCommitStatus.RepoID ||
|
||||
commitStatus.SHA != lastCommitStatus.SHA ||
|
||||
commitStatus.Context != lastCommitStatus.Context ||
|
||||
commitStatus.State != lastCommitStatus.State ||
|
||||
commitStatus.Description != lastCommitStatus.Description {
|
||||
// New context, or changed state/description; keep it, start looking for duplicates of it.
|
||||
lastCommitStatus = *commitStatus
|
||||
} else {
|
||||
// Same context as previous record, and same state -- this record shouldn't have been stored.
|
||||
deleteTargets = append(deleteTargets, commitStatus.ID)
|
||||
|
||||
if len(deleteTargets) == deleteChunkSize {
|
||||
// Flush delete chunk
|
||||
log.Debug("deleting chunk of %d records (dryRun=%v)", len(deleteTargets), dryRun)
|
||||
if !dryRun {
|
||||
if err := db.DeleteByIDs[CommitStatus](ctx, deleteTargets...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
deleteCount += len(deleteTargets)
|
||||
deleteTargets = make([]int64, 0, deleteChunkSize)
|
||||
}
|
||||
}
|
||||
recordCount++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(deleteTargets) > 0 {
|
||||
log.Debug("deleting final chunk of %d records (dryRun=%v)", len(deleteTargets), dryRun)
|
||||
if !dryRun {
|
||||
if err := db.DeleteByIDs[CommitStatus](ctx, deleteTargets...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
deleteCount += len(deleteTargets)
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
|
||||
if dryRun {
|
||||
log.Info("Reviewed %d records in commit_status, and would delete %d", recordCount, deleteCount)
|
||||
} else {
|
||||
log.Info("Reviewed %d records in commit_status, and deleted %d", recordCount, deleteCount)
|
||||
}
|
||||
log.Info("Cleanup commit status took %d milliseconds", duration.Milliseconds())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,3 +244,46 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
|
|||
assert.Equal(t, "compliance/lint-backend", contexts[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupCommitStatus(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/git/TestCleanupCommitStatus")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// No changes after a dry run:
|
||||
originalCount := unittest.GetCount(t, &git_model.CommitStatus{})
|
||||
err := git_model.CleanupCommitStatus(t.Context(), 100, 100, true)
|
||||
require.NoError(t, err)
|
||||
countAfterDryRun := unittest.GetCount(t, &git_model.CommitStatus{})
|
||||
assert.Equal(t, originalCount, countAfterDryRun)
|
||||
|
||||
// Perform actual cleanup
|
||||
err = git_model.CleanupCommitStatus(t.Context(), 100, 100, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Varying descriptions
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 10})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 11})
|
||||
|
||||
// Varying state
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 12})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 13})
|
||||
|
||||
// Varying context
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 14})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 15})
|
||||
|
||||
// Varying sha
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 16})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 17})
|
||||
|
||||
// Varying repo ID
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 18})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 19})
|
||||
|
||||
// Expected to remain or be removed from cleanup of fixture data:
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 20})
|
||||
unittest.AssertNotExistsBean(t, &git_model.CommitStatus{ID: 21})
|
||||
unittest.AssertNotExistsBean(t, &git_model.CommitStatus{ID: 22})
|
||||
unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatus{ID: 23})
|
||||
unittest.AssertNotExistsBean(t, &git_model.CommitStatus{ID: 24})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
|
|
@ -363,7 +362,7 @@ func renameTable(sess *xorm.Session, bean any, tableName, tempTableName string,
|
|||
|
||||
schema := sess.Engine().Dialect().URI().Schema
|
||||
sess.Engine().SetSchema("")
|
||||
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_id_seq' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil {
|
||||
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_schema = ? AND (sequence_name LIKE ? || '_id_seq' AND sequence_catalog = ?)", schema, tableName, setting.Database.Name).Find(&originalSequences); err != nil {
|
||||
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
|
||||
return err
|
||||
}
|
||||
|
|
@ -392,7 +391,7 @@ func renameTable(sess *xorm.Session, bean any, tableName, tempTableName string,
|
|||
|
||||
var indices []string
|
||||
sess.Engine().SetSchema("")
|
||||
if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
|
||||
if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? AND schemaname = ?", tableName, schema).Find(&indices); err != nil {
|
||||
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
|
||||
return err
|
||||
}
|
||||
|
|
@ -408,7 +407,7 @@ func renameTable(sess *xorm.Session, bean any, tableName, tempTableName string,
|
|||
|
||||
var sequences []string
|
||||
sess.Engine().SetSchema("")
|
||||
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_id_seq' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil {
|
||||
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_schema = ? AND sequence_name LIKE 'tmp_recreate__' || ? || '_id_seq' AND sequence_catalog = ?", schema, tableName, setting.Database.Name).Find(&sequences); err != nil {
|
||||
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
|
||||
return err
|
||||
}
|
||||
|
|
@ -469,74 +468,24 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(indexRes) != 1 {
|
||||
continue
|
||||
containsDroppedColumn := false
|
||||
for _, r := range indexRes {
|
||||
indexCol := string(r["name"])
|
||||
if slices.Contains(columnNames, indexCol) {
|
||||
containsDroppedColumn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
indexColumn := string(indexRes[0]["name"])
|
||||
for _, name := range columnNames {
|
||||
if name == indexColumn {
|
||||
_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if containsDroppedColumn {
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Here we need to get the columns from the original table
|
||||
sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
|
||||
res, err := sess.Query(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableSQL := string(res[0]["sql"])
|
||||
|
||||
// Get the string offset for column definitions: `CREATE TABLE ( column-definitions... )`
|
||||
columnDefinitionsIndex := strings.Index(tableSQL, "(")
|
||||
if columnDefinitionsIndex < 0 {
|
||||
return errors.New("couldn't find column definitions")
|
||||
}
|
||||
|
||||
// Separate out the column definitions
|
||||
tableSQL = tableSQL[columnDefinitionsIndex:]
|
||||
|
||||
// Remove the required columnNames
|
||||
for _, name := range columnNames {
|
||||
tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "")
|
||||
}
|
||||
|
||||
// Ensure the query is ended properly
|
||||
tableSQL = strings.TrimSpace(tableSQL)
|
||||
if tableSQL[len(tableSQL)-1] != ')' {
|
||||
if tableSQL[len(tableSQL)-1] == ',' {
|
||||
tableSQL = tableSQL[:len(tableSQL)-1]
|
||||
for _, col := range columnNames {
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN `%s`", tableName, col)); err != nil {
|
||||
return fmt.Errorf("drop table `%s` column %s encountered error: %w", tableName, col, err)
|
||||
}
|
||||
tableSQL += ")"
|
||||
}
|
||||
|
||||
// Find all the columns in the table
|
||||
columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
|
||||
|
||||
tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
|
||||
if _, err := sess.Exec(tableSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now restore the data
|
||||
columnsSeparated := strings.Join(columns, ",")
|
||||
insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
|
||||
if _, err := sess.Exec(insertSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now drop the old table
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename the table
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case setting.Database.Type.IsPostgreSQL():
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid repository foreign key
|
||||
-
|
||||
id: 2
|
||||
repo_id: 100
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to null repository foreign key
|
||||
-
|
||||
id: 3
|
||||
repo_id: null
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid user foreign key
|
||||
-
|
||||
id: 4
|
||||
repo_id: 1
|
||||
user_id: 100
|
||||
|
||||
# Expected to be deleted due to null user foreign key
|
||||
-
|
||||
id: 5
|
||||
repo_id: 1
|
||||
user_id: null
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
-
|
||||
id: 1
|
||||
issue_id: 1
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be deleted due to invalid issue foreign key
|
||||
-
|
||||
id: 2
|
||||
issue_id: 100
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be deleted due to null issue foreign key
|
||||
-
|
||||
id: 3
|
||||
issue_id: null
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be retained with null, due to invalid user foreign key
|
||||
-
|
||||
id: 4
|
||||
issue_id: 1
|
||||
user_id: 100
|
||||
time: 100
|
||||
|
||||
# Expected to be retained with null user foreign key
|
||||
-
|
||||
id: 5
|
||||
issue_id: 1
|
||||
user_id: null
|
||||
time: 100
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-
|
||||
id: 1
|
||||
issue_id: 1
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid issue foreign key
|
||||
-
|
||||
id: 2
|
||||
issue_id: 100
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to null issue foreign key
|
||||
-
|
||||
id: 3
|
||||
issue_id: null
|
||||
user_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid user foreign key
|
||||
-
|
||||
id: 4
|
||||
issue_id: 1
|
||||
user_id: 100
|
||||
|
||||
# Expected to be deleted due to null user foreign key
|
||||
-
|
||||
id: 5
|
||||
issue_id: 1
|
||||
user_id: null
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
-
|
||||
id: 1
|
||||
issue_id: 1
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be deleted due to invalid issue foreign key
|
||||
-
|
||||
id: 2
|
||||
issue_id: 100
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be deleted due to null issue foreign key
|
||||
-
|
||||
id: 3
|
||||
issue_id: null
|
||||
user_id: 1
|
||||
time: 100
|
||||
|
||||
# Expected to be retained with null, due to invalid user foreign key
|
||||
-
|
||||
id: 4
|
||||
issue_id: 1
|
||||
user_id: 100
|
||||
time: 100
|
||||
|
||||
# Expected to be retained with null user foreign key
|
||||
-
|
||||
id: 5
|
||||
issue_id: 1
|
||||
user_id: null
|
||||
time: 100
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-
|
||||
id: 1
|
||||
user_id: 1
|
||||
repo_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid user_id foreign key
|
||||
-
|
||||
id: 2
|
||||
user_id: 100
|
||||
repo_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid repo_id foreign key
|
||||
-
|
||||
id: 3
|
||||
user_id: 1
|
||||
repo_id: 100
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-
|
||||
id: 1
|
||||
uid: 1
|
||||
lookup_key: key-1
|
||||
|
||||
# Expected to be deleted due to invalid user foreign key
|
||||
-
|
||||
id: 2
|
||||
uid: 100
|
||||
lookup_key: key-2
|
||||
|
||||
# Expected to be deleted due to a null user foreign key
|
||||
-
|
||||
id: 3
|
||||
uid: null
|
||||
lookup_key: key-3
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-
|
||||
id: 1
|
||||
issue_id: 1
|
||||
base_repo_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid issue foreign key
|
||||
-
|
||||
id: 2
|
||||
issue_id: 100
|
||||
base_repo_id: 1
|
||||
|
||||
# Expected to be deleted due to null issue foreign key
|
||||
-
|
||||
id: 3
|
||||
issue_id: null
|
||||
base_repo_id: 1
|
||||
|
||||
# Expected to be deleted due to invalid repository foreign key
|
||||
-
|
||||
id: 4
|
||||
issue_id: 1
|
||||
base_repo_id: 100
|
||||
|
||||
# Expected to be deleted due to null repository foreign key
|
||||
-
|
||||
id: 5
|
||||
issue_id: 1
|
||||
base_repo_id: null
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
id: 1
|
||||
|
|
@ -611,9 +611,13 @@ func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
|
||||
if err := c.LoadIssue(ctx); err != nil {
|
||||
return fmt.Errorf("LoadIssue: %w", err)
|
||||
}
|
||||
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(ctx, c.Issue.RepoID, uuids, repo_model.FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, c.Issue.RepoID, err)
|
||||
}
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = c.IssueID
|
||||
|
|
@ -889,7 +893,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
// Check comment type.
|
||||
switch opts.Type {
|
||||
case CommentTypeCode:
|
||||
if err = updateAttachments(ctx, opts, comment); err != nil {
|
||||
if err := comment.UpdateAttachments(ctx, opts.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
if comment.ReviewID != 0 {
|
||||
|
|
@ -909,7 +913,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
}
|
||||
fallthrough
|
||||
case CommentTypeReview:
|
||||
if err = updateAttachments(ctx, opts, comment); err != nil {
|
||||
if err := comment.UpdateAttachments(ctx, opts.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
case CommentTypeReopen, CommentTypeClose:
|
||||
|
|
@ -921,23 +925,6 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
|
||||
}
|
||||
|
||||
func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
|
||||
}
|
||||
for i := range attachments {
|
||||
attachments[i].IssueID = opts.Issue.ID
|
||||
attachments[i].CommentID = comment.ID
|
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
}
|
||||
comment.Attachments = attachments
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
|
||||
var content string
|
||||
var commentType CommentType
|
||||
|
|
|
|||
|
|
@ -234,18 +234,18 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo *
|
|||
}
|
||||
|
||||
// UpdateIssueAttachments update attachments by UUIDs for the issue
|
||||
func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) {
|
||||
func UpdateIssueAttachments(ctx context.Context, issue *Issue, uuids []string) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(ctx, issue.RepoID, uuids, repo_model.FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, issue.RepoID, err)
|
||||
}
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = issueID
|
||||
attachments[i].IssueID = issue.ID
|
||||
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
|
|
@ -394,18 +394,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||
return err
|
||||
}
|
||||
|
||||
if len(opts.Attachments) > 0 {
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = opts.Issue.ID
|
||||
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
}
|
||||
if err := UpdateIssueAttachments(ctx, opts.Issue, opts.Attachments); err != nil {
|
||||
return fmt.Errorf("UpdateIssueAttachments: %w", err)
|
||||
}
|
||||
if err = opts.Issue.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@ import (
|
|||
issues_model "forgejo.org/models/issues"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/repo"
|
||||
_ "forgejo.org/models/user"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
|
|||
|
||||
// GetTrackedTimes returns all tracked times that fit to the given options.
|
||||
func GetTrackedTimes(ctx context.Context, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
|
||||
err = options.toSession(db.GetEngine(ctx)).Find(&trackedTimes)
|
||||
err = options.toSession(db.GetEngine(ctx)).Asc("tracked_time.id").Find(&trackedTimes)
|
||||
return trackedTimes, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ import (
|
|||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/system"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/organization"
|
||||
_ "forgejo.org/models/repo"
|
||||
_ "forgejo.org/models/user"
|
||||
_ "forgejo.org/modules/testimport"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,6 @@ import (
|
|||
"forgejo.org/modules/packages"
|
||||
packages_service "forgejo.org/services/packages"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,13 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/repo"
|
||||
_ "forgejo.org/models/user"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models/repo"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -309,6 +309,18 @@ func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, err
|
|||
return p, nil
|
||||
}
|
||||
|
||||
// GetProjectForUserByID returns the project by id that belongs to the specified user.
|
||||
func GetProjectForUserByID(ctx context.Context, uid, id int64) (*Project, error) {
|
||||
p := new(Project)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, uid).Get(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrProjectNotExist{ID: id}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// UpdateProject updates project properties
|
||||
func UpdateProject(ctx context.Context, p *Project) error {
|
||||
if !IsCardTypeValid(p.CardType) {
|
||||
|
|
@ -346,42 +358,26 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed
|
||||
func ChangeProjectStatusByRepoIDAndID(ctx context.Context, repoID, projectID int64, isClosed bool) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
p := new(Project)
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrProjectNotExist{ID: projectID, RepoID: repoID}
|
||||
}
|
||||
|
||||
if err := changeProjectStatus(ctx, p, isClosed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
||||
p.IsClosed = isClosed
|
||||
p.ClosedDateUnix = timeutil.TimeStampNow()
|
||||
count, err := db.GetEngine(ctx).ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
// ChangeProjectStatus changes the status of the specified project to the state
|
||||
// specified via the `isClosed` argument.
|
||||
func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
||||
if p.IsClosed == isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return updateRepositoryProjectCount(ctx, p.RepoID)
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
p.IsClosed = isClosed
|
||||
p.ClosedDateUnix = timeutil.TimeStampNow()
|
||||
count, err := db.GetEngine(ctx).ID(p.ID).Cols("is_closed", "closed_date_unix").Update(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return updateRepositoryProjectCount(ctx, p.RepoID)
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteProjectByID deletes a project from a repository. if it's not in a database
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package project
|
||||
|
|
@ -7,8 +8,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -48,42 +49,6 @@ func TestGetProjects(t *testing.T) {
|
|||
assert.Len(t, projects, 1)
|
||||
}
|
||||
|
||||
func TestProject(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
project := &Project{
|
||||
Type: TypeRepository,
|
||||
TemplateType: TemplateTypeBasicKanban,
|
||||
CardType: CardTypeTextOnly,
|
||||
Title: "New Project",
|
||||
RepoID: 1,
|
||||
CreatedUnix: timeutil.TimeStampNow(),
|
||||
CreatorID: 2,
|
||||
}
|
||||
|
||||
require.NoError(t, NewProject(db.DefaultContext, project))
|
||||
|
||||
_, err := GetProjectByID(db.DefaultContext, project.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update project
|
||||
project.Title = "Updated title"
|
||||
require.NoError(t, UpdateProject(db.DefaultContext, project))
|
||||
|
||||
projectFromDB, err := GetProjectByID(db.DefaultContext, project.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, project.Title, projectFromDB.Title)
|
||||
|
||||
require.NoError(t, ChangeProjectStatusByRepoIDAndID(db.DefaultContext, project.RepoID, project.ID, true))
|
||||
|
||||
// Retrieve from DB afresh to check if it is truly closed
|
||||
projectFromDB, err = GetProjectByID(db.DefaultContext, project.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, projectFromDB.IsClosed)
|
||||
}
|
||||
|
||||
func TestProjectsSort(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
|
@ -93,19 +58,19 @@ func TestProjectsSort(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
sortType: "default",
|
||||
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||
wants: []int64{1, 3, 2, 7, 6, 5, 4},
|
||||
},
|
||||
{
|
||||
sortType: "oldest",
|
||||
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||
wants: []int64{4, 5, 6, 7, 2, 3, 1},
|
||||
},
|
||||
{
|
||||
sortType: "recentupdate",
|
||||
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||
wants: []int64{1, 3, 2, 7, 6, 5, 4},
|
||||
},
|
||||
{
|
||||
sortType: "leastupdate",
|
||||
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||
wants: []int64{4, 5, 6, 7, 2, 3, 1},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -114,11 +79,82 @@ func TestProjectsSort(t *testing.T) {
|
|||
OrderBy: GetSearchOrderByBySortType(tt.sortType),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(6), count)
|
||||
if assert.Len(t, projects, 6) {
|
||||
assert.Equal(t, int64(7), count)
|
||||
if assert.Len(t, projects, 7) {
|
||||
for i := range projects {
|
||||
assert.Equal(t, tt.wants[i], projects[i].ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProjectForUserByID(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
found := func(t *testing.T, uid, id int64) {
|
||||
t.Helper()
|
||||
|
||||
p, err := GetProjectForUserByID(t.Context(), uid, id)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, p) {
|
||||
assert.Equal(t, id, p.ID)
|
||||
}
|
||||
}
|
||||
|
||||
notFound := func(t *testing.T, uid, id int64) {
|
||||
t.Helper()
|
||||
|
||||
p, err := GetProjectForUserByID(t.Context(), uid, id)
|
||||
require.ErrorIs(t, err, ErrProjectNotExist{ID: id})
|
||||
assert.Nil(t, p)
|
||||
}
|
||||
|
||||
found(t, 2, 4)
|
||||
found(t, 2, 5)
|
||||
found(t, 2, 6)
|
||||
found(t, 3, 7)
|
||||
notFound(t, 1, 4)
|
||||
notFound(t, 1, 5)
|
||||
notFound(t, 1, 6)
|
||||
notFound(t, 1, 7)
|
||||
}
|
||||
|
||||
func TestChangeProjectStatus(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("Unchanged", func(t *testing.T) {
|
||||
project := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
|
||||
require.NoError(t, ChangeProjectStatus(t.Context(), project, project.IsClosed))
|
||||
|
||||
projectAfter := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
assert.Equal(t, project.IsClosed, projectAfter.IsClosed)
|
||||
})
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
project := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
isClosed := !project.IsClosed
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: project.RepoID})
|
||||
|
||||
require.NoError(t, ChangeProjectStatus(t.Context(), project, isClosed))
|
||||
|
||||
projectAfter := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: project.RepoID})
|
||||
assert.Equal(t, isClosed, projectAfter.IsClosed)
|
||||
assert.Equal(t, repo.NumProjects, repoAfter.NumProjects)
|
||||
assert.Equal(t, repo.NumOpenProjects-1, repoAfter.NumOpenProjects)
|
||||
assert.Equal(t, repo.NumClosedProjects+1, repoAfter.NumClosedProjects)
|
||||
})
|
||||
|
||||
t.Run("Invalid ID", func(t *testing.T) {
|
||||
project := &Project{ID: 1001, RepoID: 1}
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: project.RepoID})
|
||||
|
||||
require.NoError(t, ChangeProjectStatus(t.Context(), project, true))
|
||||
|
||||
repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: project.RepoID})
|
||||
assert.Equal(t, repo.NumProjects, repoAfter.NumProjects)
|
||||
assert.Equal(t, repo.NumOpenProjects, repoAfter.NumOpenProjects)
|
||||
assert.Equal(t, repo.NumClosedProjects, repoAfter.NumClosedProjects)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
|
@ -16,17 +17,30 @@ import (
|
|||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Attachment represent a attachment of issue/comment/release.
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
// UUID is the public identifier of the attachment, and is used during HTTP
|
||||
// requests to refer to a specific attachment.
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
// UploaderID is always set and non-zero and refers to the user that has
|
||||
// uploaded this attachment.
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
// RepoID is always set and non-zero and refers to the repository where this
|
||||
// attachment was uploaded to.
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
// IssueID, ReleaseID and CommentID have multiple possible states:
|
||||
// - ReleaseID != 0 && IssueID == 0 && CommentID == 0: attached to release with id `ReleaseID`.
|
||||
// - ReleaseID == 0 && IssueID != 0 && CommentID == 0: attached to the issue with id `IssueID`.
|
||||
// - ReleaseID == 0 && IssueID != 0 && CommentID != 0: attached to comment with id `CommentID` that is in issue with id `IssueID`.
|
||||
// All other states should be considered invalid.
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
ReleaseID int64 `xorm:"INDEX"`
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Size int64 `xorm:"DEFAULT 0"`
|
||||
|
|
@ -73,6 +87,12 @@ func (a *Attachment) DownloadURL() string {
|
|||
return setting.AppURL + "attachments/" + url.PathEscape(a.UUID)
|
||||
}
|
||||
|
||||
// IsAttachedToResource returns true if this attachment is attached to a release,
|
||||
// issue or comment.
|
||||
func (a *Attachment) IsAttachedToResource() bool {
|
||||
return a.ReleaseID != 0 || a.IssueID != 0 || a.CommentID != 0
|
||||
}
|
||||
|
||||
// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
|
||||
type ErrAttachmentNotExist struct {
|
||||
ID int64
|
||||
|
|
@ -133,15 +153,37 @@ func GetAttachmentByUUID(ctx context.Context, uuid string) (*Attachment, error)
|
|||
return attach, nil
|
||||
}
|
||||
|
||||
// GetAttachmentsByUUIDs returns attachment by given UUID list.
|
||||
func GetAttachmentsByUUIDs(ctx context.Context, uuids []string) ([]*Attachment, error) {
|
||||
type FindAttachmentOptions struct {
|
||||
ReleaseID int64
|
||||
IssueID int64
|
||||
CommentID int64
|
||||
}
|
||||
|
||||
func (opts FindAttachmentOptions) ToConds() builder.Cond {
|
||||
return builder.Eq{"release_id": opts.ReleaseID, "issue_id": opts.IssueID, "comment_id": opts.CommentID}
|
||||
}
|
||||
|
||||
// FindRepoAttachmentsByUUID always returns attachment that has a UUID that is
|
||||
// in the given `uuids` argument and is attached to the repository.
|
||||
//
|
||||
// The values in `opts` are always as a condition even if they are zero, this
|
||||
// allows to search for attachments that are not yet attached to any resource by
|
||||
// specifying a empty struct.
|
||||
func FindRepoAttachmentsByUUID(ctx context.Context, repoID int64, uuids []string, opts FindAttachmentOptions) ([]*Attachment, error) {
|
||||
// Nothing to match anyway.
|
||||
if len(uuids) == 0 {
|
||||
return []*Attachment{}, nil
|
||||
}
|
||||
|
||||
// Silently drop invalid uuids.
|
||||
// At maximum nothing is filtered and we get all attachments via the UUID.
|
||||
attachments := make([]*Attachment, 0, len(uuids))
|
||||
return attachments, db.GetEngine(ctx).In("uuid", uuids).Find(&attachments)
|
||||
|
||||
err := db.GetEngine(ctx).
|
||||
Where("repo_id = ?", repoID).
|
||||
In("uuid", uuids).
|
||||
And(opts.ToConds()).
|
||||
Find(&attachments)
|
||||
return attachments, err
|
||||
}
|
||||
|
||||
// ExistAttachmentsByUUID returns true if attachment exists with the given UUID
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
|
@ -88,14 +91,67 @@ func TestUpdateAttachment(t *testing.T) {
|
|||
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"})
|
||||
}
|
||||
|
||||
func TestGetAttachmentsByUUIDs(t *testing.T) {
|
||||
func TestFindRepoAttachmentsByUUID(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("models/repo/fixtures/TestFindRepoAttachmentsByUUID")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
attachList, err := repo_model.GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, attachList, 2)
|
||||
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID)
|
||||
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", attachList[1].UUID)
|
||||
assert.Equal(t, int64(1), attachList[0].IssueID)
|
||||
assert.Equal(t, int64(5), attachList[1].IssueID)
|
||||
sort := func(x []*repo_model.Attachment) {
|
||||
slices.SortFunc(x, func(a, b *repo_model.Attachment) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Empty UUIDs", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{}, repo_model.FindAttachmentOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, attachments)
|
||||
})
|
||||
|
||||
t.Run("Wrong repository", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1002, []string{"31b6f65e-2745-4e87-b02c-e6bb9890d399", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed", "758e41f6-e3b7-4420-b34f-1920da0858aa"}, repo_model.FindAttachmentOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, attachments)
|
||||
})
|
||||
|
||||
t.Run("Not attached", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{"31b6f65e-2745-4e87-b02c-e6bb9890d399", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed", "758e41f6-e3b7-4420-b34f-1920da0858aa"}, repo_model.FindAttachmentOptions{})
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, attachments, 1) {
|
||||
assert.Equal(t, "31b6f65e-2745-4e87-b02c-e6bb9890d399", attachments[0].UUID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{"17bcdb6b-dd84-4da1-b37a-671165402d8d", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed", "774f276e-c85d-488e-b735-7bc07860c756"}, repo_model.FindAttachmentOptions{IssueID: 1001})
|
||||
require.NoError(t, err)
|
||||
sort(attachments)
|
||||
if assert.Len(t, attachments, 2) {
|
||||
assert.Equal(t, "17bcdb6b-dd84-4da1-b37a-671165402d8d", attachments[0].UUID)
|
||||
assert.Equal(t, "e19fd169-c2d1-4fd0-a6d5-9658fd4affed", attachments[1].UUID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Comment", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{"edf0d986-8a12-447a-a4bb-e9aefead251b", "774f276e-c85d-488e-b735-7bc07860c756", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed"}, repo_model.FindAttachmentOptions{IssueID: 1001, CommentID: 1001})
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, attachments, 1) {
|
||||
assert.Equal(t, "edf0d986-8a12-447a-a4bb-e9aefead251b", attachments[0].UUID)
|
||||
}
|
||||
|
||||
attachments, err = repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{"edf0d986-8a12-447a-a4bb-e9aefead251b", "774f276e-c85d-488e-b735-7bc07860c756", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed"}, repo_model.FindAttachmentOptions{IssueID: 1001, CommentID: 1002})
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, attachments, 1) {
|
||||
assert.Equal(t, "774f276e-c85d-488e-b735-7bc07860c756", attachments[0].UUID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Release", func(t *testing.T) {
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(t.Context(), 1001, []string{"d2570bab-c843-486f-b7b7-23e011c42815", "758e41f6-e3b7-4420-b34f-1920da0858aa", "e19fd169-c2d1-4fd0-a6d5-9658fd4affed"}, repo_model.FindAttachmentOptions{ReleaseID: 1001})
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, attachments, 2) {
|
||||
sort(attachments)
|
||||
assert.Equal(t, "758e41f6-e3b7-4420-b34f-1920da0858aa", attachments[0].UUID)
|
||||
assert.Equal(t, "d2570bab-c843-486f-b7b7-23e011c42815", attachments[1].UUID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
-
|
||||
id: 1001
|
||||
uuid: 70d3e7b8-5e46-41eb-bd2d-afaba53056bd
|
||||
repo_id: 0
|
||||
issue_id: 0
|
||||
release_id: 0
|
||||
uploader_id: 0
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300000
|
||||
|
||||
-
|
||||
id: 1002
|
||||
uuid: 31b6f65e-2745-4e87-b02c-e6bb9890d399
|
||||
repo_id: 1001
|
||||
issue_id: 0
|
||||
release_id: 0
|
||||
uploader_id: 1001
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300001
|
||||
|
||||
-
|
||||
id: 1003
|
||||
uuid: 03158f6c-487c-4bc5-b24b-10f13e21c2e7
|
||||
repo_id: 1001
|
||||
issue_id: 0
|
||||
release_id: 0
|
||||
uploader_id: 1002
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300002
|
||||
|
||||
-
|
||||
id: 1004
|
||||
uuid: 17bcdb6b-dd84-4da1-b37a-671165402d8d
|
||||
repo_id: 1001
|
||||
issue_id: 1001
|
||||
release_id: 0
|
||||
uploader_id: 1001
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300003
|
||||
|
||||
-
|
||||
id: 1005
|
||||
uuid: e19fd169-c2d1-4fd0-a6d5-9658fd4affed
|
||||
repo_id: 1001
|
||||
issue_id: 1001
|
||||
release_id: 0
|
||||
uploader_id: 1002
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300004
|
||||
|
||||
-
|
||||
id: 1006
|
||||
uuid: 758e41f6-e3b7-4420-b34f-1920da0858aa
|
||||
repo_id: 1001
|
||||
issue_id: 0
|
||||
release_id: 1001
|
||||
uploader_id: 1001
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300005
|
||||
|
||||
-
|
||||
id: 1007
|
||||
uuid: d2570bab-c843-486f-b7b7-23e011c42815
|
||||
repo_id: 1001
|
||||
issue_id: 0
|
||||
release_id: 1001
|
||||
uploader_id: 1002
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300006
|
||||
|
||||
-
|
||||
id: 1008
|
||||
uuid: edf0d986-8a12-447a-a4bb-e9aefead251b
|
||||
repo_id: 1001
|
||||
issue_id: 1001
|
||||
release_id: 0
|
||||
uploader_id: 1001
|
||||
comment_id: 1001
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300007
|
||||
|
||||
-
|
||||
id: 1009
|
||||
uuid: 774f276e-c85d-488e-b735-7bc07860c756
|
||||
repo_id: 1001
|
||||
issue_id: 1001
|
||||
release_id: 0
|
||||
uploader_id: 1002
|
||||
comment_id: 1002
|
||||
name: attach1
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 1771300008
|
||||
|
|
@ -8,13 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models" // register table model
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/perm/access" // register table model
|
||||
_ "forgejo.org/models/repo" // register table model
|
||||
_ "forgejo.org/models/user" // register table model
|
||||
_ "forgejo.org/modules/testimport"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -224,18 +224,14 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
|
|||
}
|
||||
|
||||
// AddReleaseAttachments adds a release attachments
|
||||
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
|
||||
// Check attachments
|
||||
attachments, err := GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
|
||||
func AddReleaseAttachments(ctx context.Context, release *Release, attachmentUUIDs []string) (err error) {
|
||||
attachments, err := FindRepoAttachmentsByUUID(ctx, release.RepoID, attachmentUUIDs, FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %w", attachmentUUIDs, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", attachmentUUIDs, release.RepoID, err)
|
||||
}
|
||||
|
||||
for i := range attachments {
|
||||
if attachments[i].ReleaseID != 0 {
|
||||
return util.NewPermissionDeniedErrorf("release permission denied")
|
||||
}
|
||||
attachments[i].ReleaseID = releaseID
|
||||
attachments[i].ReleaseID = release.ID
|
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
|
||||
|
|
|
|||
|
|
@ -162,14 +162,12 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
|
|||
return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
|
||||
}
|
||||
|
||||
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
|
||||
// If isShowFullName is set to true, also include full name prefix search
|
||||
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
|
||||
// GetIssuePostersWithSearch returns up to 30 users whose username starts with or full_name contains the given search string for the given repository.
|
||||
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string) ([]*user_model.User, error) {
|
||||
users := make([]*user_model.User, 0, 30)
|
||||
prefixCond := db.BuildCaseInsensitiveLike("name", search+"%")
|
||||
if isShowFullName {
|
||||
prefixCond = db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")
|
||||
}
|
||||
prefixCond := builder.Or(
|
||||
db.BuildCaseInsensitiveLike("name", search+"%"),
|
||||
db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
|
||||
|
||||
cond := builder.In("`user`.id",
|
||||
builder.Select("poster_id").From("issue").Where(
|
||||
|
|
|
|||
|
|
@ -185,6 +185,12 @@ func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error
|
|||
|
||||
// UnwatchRepos will unwatch the user from all given repositories.
|
||||
func UnwatchRepos(ctx context.Context, userID int64, repoIDs []int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("user_id=?", userID).In("repo_id", repoIDs).Delete(&Watch{})
|
||||
return err
|
||||
// Unfortunatly, we can't simply delete the Watch records because we do watcher counting in the repo relation.
|
||||
for _, repoID := range repoIDs {
|
||||
err := WatchRepoMode(ctx, userID, repoID, WatchModeNone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/activities"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models" // register models
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/system" // register models of system
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
|
@ -29,7 +30,7 @@ import (
|
|||
// test data files
|
||||
func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveMode bool) *httptest.Server {
|
||||
mockServerBaseURL := ""
|
||||
ignoredHeaders := []string{"cf-ray", "server", "date", "report-to", "nel", "x-request-id"}
|
||||
ignoredHeaders := []string{"cf-ray", "server", "date", "report-to", "nel", "x-request-id", "set-cookie", "x-gitlab-meta"}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := NormalizedFullPath(r.URL)
|
||||
|
|
@ -46,6 +47,7 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
|
|||
fixturePath = fmt.Sprintf("%s/%s", testDataDir, strings.TrimLeft(r.URL.Path, "/"))
|
||||
}
|
||||
if liveMode {
|
||||
require.NoError(t, os.MkdirAll(testDataDir, 0o755))
|
||||
liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path)
|
||||
|
||||
request, err := http.NewRequest(r.Method, liveURL, nil)
|
||||
|
|
@ -68,8 +70,8 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
|
|||
defer fixture.Close()
|
||||
fixtureWriter := bufio.NewWriter(fixture)
|
||||
|
||||
for headerName, headerValues := range response.Header {
|
||||
for _, headerValue := range headerValues {
|
||||
for _, headerName := range slices.Sorted(maps.Keys(response.Header)) {
|
||||
for _, headerValue := range response.Header[headerName] {
|
||||
if !slices.Contains(ignoredHeaders, strings.ToLower(headerName)) {
|
||||
_, err := fmt.Fprintf(fixtureWriter, "%s: %s\n", headerName, headerValue)
|
||||
require.NoError(t, err, "writing the header of the HTTP response to the fixture file failed")
|
||||
|
|
@ -91,8 +93,6 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
|
|||
fixture, err := os.ReadFile(fixturePath)
|
||||
require.NoError(t, err, "missing mock HTTP response: "+fixturePath)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// replace any mention of the live HTTP service by the mocked host
|
||||
stringFixture := strings.ReplaceAll(string(fixture), liveServerBaseURL, mockServerBaseURL)
|
||||
if isGh {
|
||||
|
|
@ -104,10 +104,16 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
|
|||
for idx, line := range lines {
|
||||
colonIndex := strings.Index(line, ": ")
|
||||
if colonIndex != -1 {
|
||||
w.Header().Set(line[0:colonIndex], line[colonIndex+2:])
|
||||
// Because we modified the body with ReplaceAll() above, we need to
|
||||
// remove Content-Length. w.Write() should add it back.
|
||||
header := line[0:colonIndex]
|
||||
if !strings.EqualFold(header, "Content-Length") {
|
||||
w.Header().Set(line[0:colonIndex], line[colonIndex+2:])
|
||||
}
|
||||
} else {
|
||||
// we reached the end of the headers (empty line), so what follows is the body
|
||||
responseBody := strings.Join(lines[idx+1:], "\n")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(responseBody))
|
||||
require.NoError(t, err, "writing the body of the HTTP response failed")
|
||||
break
|
||||
|
|
|
|||
24
models/unittest/mock_http_test.go
Normal file
24
models/unittest/mock_http_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package unittest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// NOTE: This is a test of the unittest helper itself
|
||||
func TestMockWebServer(t *testing.T) {
|
||||
server := NewMockWebServer(t, "https://example.com", "testdata", false)
|
||||
defer server.Close()
|
||||
request, err := http.NewRequest("GET", server.URL+"/", nil)
|
||||
require.NoError(t, err)
|
||||
response, err := server.Client().Do(request)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, response.Header["Header"], 1)
|
||||
assert.Equal(t, "value", response.Header["Header"][0])
|
||||
}
|
||||
3
models/unittest/testdata/GET_%2F
vendored
Normal file
3
models/unittest/testdata/GET_%2F
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Header: value
|
||||
|
||||
bodydata
|
||||
|
|
@ -8,11 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/user"
|
||||
_ "forgejo.org/modules/testimport"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne
|
|||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dbfs Stat %q: %w", name, err)
|
||||
return nil, fmt.Errorf("WriteLogs(name=%q, offset=%d, len(rows)=%d): dbfs Stat: %w", name, offset, len(rows), err)
|
||||
}
|
||||
if stat.Size() < offset {
|
||||
// If the size is less than offset, refuse to write, or it could result in content holes.
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
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