forked from mirrors/forgejo
Compare commits
95 commits
forgejo
...
bp-v14.0/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
377215d76e | ||
|
|
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 |
377 changed files with 10535 additions and 3504 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 }}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
56
assets/go-licenses.json
generated
56
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -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],
|
||||
|
|
|
|||
31
go.mod
31
go.mod
|
|
@ -2,16 +2,16 @@ module forgejo.org
|
|||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
toolchain go1.25.7
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -107,7 +107,7 @@ require (
|
|||
golang.org/x/net v0.47.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/sys v0.40.0
|
||||
golang.org/x/text v0.32.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
|
|
@ -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
|
||||
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
|
||||
|
|
@ -159,7 +157,6 @@ require (
|
|||
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/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
|
||||
|
|
@ -179,7 +175,7 @@ require (
|
|||
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-git/v5 v5.16.4 // 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,7 +206,6 @@ 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/libdns/libdns v1.0.0 // 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,7 +254,7 @@ 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/time v0.14.0 // indirect
|
||||
|
|
|
|||
77
go.sum
77
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=
|
||||
|
|
@ -56,8 +56,6 @@ 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=
|
||||
|
|
@ -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=
|
||||
|
|
@ -192,8 +185,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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=
|
||||
|
|
@ -297,10 +284,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D
|
|||
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-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
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=
|
||||
|
|
@ -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=
|
||||
|
|
@ -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,7 +718,6 @@ 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=
|
||||
|
|
@ -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=
|
||||
|
|
@ -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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.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.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -91,8 +91,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 +102,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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import (
|
|||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/activitypub"
|
||||
|
||||
_ "forgejo.org/models" // https://forum.gitea.com/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -141,6 +141,28 @@ func (b *Blob) Name() string {
|
|||
return b.name
|
||||
}
|
||||
|
||||
// NewReader return a blob-reader which fails immediately with [BlobTooLargeError] if the file is bigger than the limit
|
||||
func (b *Blob) NewReader(limit int64) (rc io.ReadCloser, actualSize int64, err error) {
|
||||
actualSize = b.Size()
|
||||
if actualSize > limit {
|
||||
return nil, actualSize, BlobTooLargeError{
|
||||
Size: actualSize,
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
r, _, cancel, err := b.newReader()
|
||||
if err != nil {
|
||||
return nil, actualSize, err
|
||||
}
|
||||
|
||||
return &blobReader{
|
||||
rd: r,
|
||||
n: actualSize,
|
||||
additionalDiscard: 0,
|
||||
cancel: cancel,
|
||||
}, actualSize, nil
|
||||
}
|
||||
|
||||
// NewTruncatedReader return a blob-reader which silently truncates when the limit is reached (io.EOF will be returned)
|
||||
func (b *Blob) NewTruncatedReader(limit int64) (rc io.ReadCloser, fullSize int64, err error) {
|
||||
r, fullSize, cancel, err := b.newReader()
|
||||
|
|
@ -168,14 +190,7 @@ func (b BlobTooLargeError) Error() string {
|
|||
// GetContentBase64 Reads the content of the blob and returns it as base64 encoded string.
|
||||
// Returns [BlobTooLargeError] if the (unencoded) content is larger than the limit.
|
||||
func (b *Blob) GetContentBase64(limit int64) (string, error) {
|
||||
if b.Size() > limit {
|
||||
return "", BlobTooLargeError{
|
||||
Size: b.Size(),
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
rc, size, err := b.NewTruncatedReader(limit)
|
||||
rc, size, err := b.NewReader(limit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ var (
|
|||
// `useMergebase` is specified then the merge base between `base` and `head` is
|
||||
// used to compare against `head`.
|
||||
func (repo *Repository) GetShortStat(base, head string, useMergebase bool) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat")
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat", "--find-renames")
|
||||
if useMergebase {
|
||||
cmd = cmd.AddArguments("--merge-base")
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ func (repo *Repository) GetShortStat(base, head string, useMergebase bool) (numF
|
|||
|
||||
// GetCommitShortStat returns the number of files, total additions and total deletions the commit has.
|
||||
func (repo *Repository) GetCommitShortStat(commitID string) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat", "--no-commit-id", "--root").AddDynamicArguments(commitID)
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat", "--no-commit-id", "--root", "--find-renames").AddDynamicArguments(commitID)
|
||||
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
|
|
|
|||
|
|
@ -242,6 +242,18 @@ func TestGetCommitShortStat(t *testing.T) {
|
|||
assert.Equal(t, 6, totalAddition)
|
||||
assert.Equal(t, 0, totalDeletions)
|
||||
})
|
||||
|
||||
t.Run("Renames", func(t *testing.T) {
|
||||
repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "renames"))
|
||||
require.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
numFiles, totalAddition, totalDeletions, err := repo.GetCommitShortStat("f667f3a24223414e3bfbe01ab6e445c703ab8e25")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, numFiles)
|
||||
assert.Zero(t, totalAddition)
|
||||
assert.Zero(t, totalDeletions)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetShortStat(t *testing.T) {
|
||||
|
|
@ -301,6 +313,28 @@ func TestGetShortStat(t *testing.T) {
|
|||
assert.Zero(t, totalAdditions)
|
||||
assert.Zero(t, totalDeletions)
|
||||
})
|
||||
|
||||
t.Run("Renames", func(t *testing.T) {
|
||||
repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "renames"))
|
||||
require.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
t.Run("Only rename", func(t *testing.T) {
|
||||
numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("bc40f00489096a7d4090a609a6572f528e1acb76", "f667f3a24223414e3bfbe01ab6e445c703ab8e25", true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, numFiles)
|
||||
assert.Zero(t, totalAdditions)
|
||||
assert.Zero(t, totalDeletions)
|
||||
})
|
||||
|
||||
t.Run("Too much diverged", func(t *testing.T) {
|
||||
numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("bc40f00489096a7d4090a609a6572f528e1acb76", "acdee217ada3fea6e503acfb969724cc799fc516", true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, numFiles)
|
||||
assert.Equal(t, 3, totalAdditions)
|
||||
assert.Equal(t, 1, totalDeletions)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMergeBaseSimple(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -19,6 +20,8 @@ import (
|
|||
"gopkg.in/ini.v1" //nolint:depguard // used to read .gitmodules
|
||||
)
|
||||
|
||||
const MaxGitmodulesFileSize = 64 * 1024
|
||||
|
||||
// GetSubmodule returns the Submodule of a given path
|
||||
func (c *Commit) GetSubmodule(path string, entry *TreeEntry) (Submodule, error) {
|
||||
err := c.readSubmodules()
|
||||
|
|
@ -55,8 +58,12 @@ func (c *Commit) readSubmodules() error {
|
|||
return err
|
||||
}
|
||||
|
||||
rc, _, err := entry.Blob().NewTruncatedReader(10 * 1024)
|
||||
rc, _, err := entry.Blob().NewReader(MaxGitmodulesFileSize)
|
||||
if err != nil {
|
||||
if errors.As(err, &BlobTooLargeError{}) {
|
||||
c.submodules = make(map[string]Submodule)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
|
|
|||
1
modules/git/tests/repos/renames/HEAD
Normal file
1
modules/git/tests/repos/renames/HEAD
Normal file
|
|
@ -0,0 +1 @@
|
|||
ref: refs/heads/main
|
||||
6
modules/git/tests/repos/renames/config
Normal file
6
modules/git/tests/repos/renames/config
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
[remote "origin"]
|
||||
url = /home/gusted/Desktop/renames
|
||||
BIN
modules/git/tests/repos/renames/objects/info/commit-graph
Normal file
BIN
modules/git/tests/repos/renames/objects/info/commit-graph
Normal file
Binary file not shown.
3
modules/git/tests/repos/renames/objects/info/packs
Normal file
3
modules/git/tests/repos/renames/objects/info/packs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
P pack-3c04d9ebf3e2c4620d7142f736b92d739834e2d4.pack
|
||||
P pack-d1cbea94fcad36c556cd5921e8df7feff7cbbbb2.pack
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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