forked from mirrors/forgejo
Compare commits
77 commits
forgejo
...
bp-v14.0/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1813fb87 | ||
|
|
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 |
314 changed files with 9322 additions and 3368 deletions
|
|
@ -238,6 +238,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
|
||||
|
|
|
|||
52
assets/go-licenses.json
generated
52
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])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
27
go.mod
27
go.mod
|
|
@ -2,16 +2,16 @@ module forgejo.org
|
|||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
toolchain go1.25.6
|
||||
|
||||
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.0
|
||||
code.forgejo.org/go-chi/binding v1.0.1
|
||||
code.forgejo.org/go-chi/cache v1.0.1
|
||||
code.forgejo.org/go-chi/captcha v1.0.2
|
||||
|
|
@ -71,7 +71,7 @@ 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
69
go.sum
69
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.0 h1:qs2mvrZeSqsR2sHQHO0kRTUErGrNMHHjm1qRy6EfLZY=
|
||||
code.forgejo.org/forgejo/runner/v12 v12.6.0/go.mod h1:G75xcAhTaFE++/57qcBu+Zk7B7R3kLiUz8y2IkIwUKY=
|
||||
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=
|
||||
|
|
@ -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=
|
||||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -185,23 +185,33 @@ func UpdateRunJobWithoutNotification(ctx context.Context, job *ActionRunJob, con
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
run.Status = AggregateJobStatus(jobs)
|
||||
|
||||
updateRequired := false
|
||||
newStatus := AggregateJobStatus(jobs)
|
||||
if run.Status != newStatus {
|
||||
run.Status = newStatus
|
||||
updateRequired = true
|
||||
}
|
||||
if run.Started.IsZero() && run.Status.IsRunning() {
|
||||
run.Started = timeutil.TimeStampNow()
|
||||
updateRequired = true
|
||||
}
|
||||
if run.Stopped.IsZero() && run.Status.IsDone() {
|
||||
run.Stopped = timeutil.TimeStampNow()
|
||||
updateRequired = true
|
||||
}
|
||||
// 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)
|
||||
if updateRequired {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ type FindRunJobOptions struct {
|
|||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
Events []string // []webhook_module.HookEventType
|
||||
RunNumber int64
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
|
|
@ -78,11 +76,5 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||
}
|
||||
if len(opts.Events) > 0 {
|
||||
cond = cond.And(builder.In("event", opts.Events))
|
||||
}
|
||||
if opts.RunNumber > 0 {
|
||||
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ package actions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
|
|
@ -65,15 +64,17 @@ 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
|
||||
Events []string // []webhook_module.HookEventType
|
||||
RunNumber int64
|
||||
CommitSHA string
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) ToConds() builder.Cond {
|
||||
|
|
@ -102,8 +103,14 @@ 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)})
|
||||
if len(opts.Events) > 0 {
|
||||
cond = cond.And(builder.In("event", opts.Events))
|
||||
}
|
||||
if opts.RunNumber > 0 {
|
||||
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
|
||||
}
|
||||
if opts.CommitSHA != "" {
|
||||
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"forgejo.org/modules/process"
|
||||
"forgejo.org/modules/queue"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/util"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -182,12 +183,12 @@ func Init() {
|
|||
log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err)
|
||||
}
|
||||
case "elasticsearch":
|
||||
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoConnStr)
|
||||
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
||||
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
||||
log.Error("You can completely remove the \"%s\" index to make Forgejo recreate the indexes", setting.Indexer.RepoConnStr)
|
||||
log.Error("You can completely remove the \"%s\" index to make Forgejo recreate the indexes", util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -197,7 +198,7 @@ func Init() {
|
|||
cancel()
|
||||
(*globalIndexer.Load()).Close()
|
||||
close(waitChannel)
|
||||
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
|
||||
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr), err)
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -14,11 +14,6 @@ import (
|
|||
"forgejo.org/modules/indexer/code/elasticsearch"
|
||||
"forgejo.org/modules/indexer/code/internal"
|
||||
|
||||
_ "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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"forgejo.org/modules/process"
|
||||
"forgejo.org/modules/queue"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/util"
|
||||
)
|
||||
|
||||
// IndexerMetadata is used to send data to the queue, so it contains only the ids.
|
||||
|
|
@ -100,7 +101,7 @@ func InitIssueIndexer(syncReindex bool) {
|
|||
issueIndexer = elasticsearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName)
|
||||
existed, err = issueIndexer.Init(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
|
||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
|
||||
}
|
||||
case "db":
|
||||
issueIndexer = db_index.NewIndexer()
|
||||
|
|
@ -108,7 +109,7 @@ func InitIssueIndexer(syncReindex bool) {
|
|||
issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName)
|
||||
existed, err = issueIndexer.Init(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
|
||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
|
||||
}
|
||||
default:
|
||||
log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType)
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ import (
|
|||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
_ "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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ import (
|
|||
"forgejo.org/modules/queue"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
_ "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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ var (
|
|||
|
||||
// valid chars in encoded path and parameter: [-+~_%.a-zA-Z0-9/]
|
||||
|
||||
// httpSchemePattern matches https:// or http://
|
||||
httpSchemePattern = regexp.MustCompile(`^https?://`)
|
||||
|
||||
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
||||
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
|
||||
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
|
||||
|
|
@ -72,7 +75,7 @@ var (
|
|||
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
||||
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
|
||||
|
||||
// Fediverse handle regex (same as emailRegex but with additonal @ or !
|
||||
// Fediverse handle regex (same as emailRegex but with additional @ or !
|
||||
// at start)
|
||||
fediRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([@!]([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+)@([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+))(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
|
||||
|
||||
|
|
@ -826,10 +829,7 @@ func pullReviewCommitPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
|
||||
text := "!" + id + " (commit "
|
||||
|
||||
baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug)
|
||||
if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) {
|
||||
text = repoSlug + "@" + text
|
||||
}
|
||||
optionalRepoSlugAndInstancePath(ctx, &text, urlFull, repoSlug)
|
||||
|
||||
aNode.AppendChild(&html.Node{
|
||||
Type: html.TextNode,
|
||||
|
|
@ -1083,10 +1083,7 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
// We need to figure out the base of the provided URL, which is up to and including the
|
||||
// `<owner>/<repo>` slug.
|
||||
// With that we can determine if it matches the current repo, or if the slug should be shown.
|
||||
baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug)
|
||||
if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) {
|
||||
text = repoSlug + "@" + text
|
||||
}
|
||||
optionalRepoSlugAndInstancePath(ctx, &text, urlFull, repoSlug)
|
||||
|
||||
// 3rd capture group matches an optional file path after the SHA
|
||||
filePath := ""
|
||||
|
|
@ -1191,10 +1188,7 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
|
||||
text := text1 + textDots + text2
|
||||
|
||||
baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug)
|
||||
if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) {
|
||||
text = repoSlug + "@" + text
|
||||
}
|
||||
optionalRepoSlugAndInstancePath(ctx, &text, urlFull, repoSlug)
|
||||
|
||||
extra := ""
|
||||
if query != "" {
|
||||
|
|
@ -1525,3 +1519,27 @@ func createDescriptionLink(href, content string) *html.Node {
|
|||
textNode.Parent = linkNode
|
||||
return linkNode
|
||||
}
|
||||
|
||||
// Adds an optional repo slug and optionally the instance domain and URL
|
||||
//
|
||||
// The repo slug is added if the link points to a different repo
|
||||
// The instance domain and sub-path is added if the link points to a different instance
|
||||
func optionalRepoSlugAndInstancePath(ctx *RenderContext, text *string, fullURL, slug string) {
|
||||
if len(ctx.Links.Base) > 0 {
|
||||
// The fullURL is the url to e.g. the commit. The slug is e.g. `forgejo/forgejo`.
|
||||
// To retrieve the instance domain and sub-path we need to remove the repo slug
|
||||
slugStart := strings.LastIndex(fullURL, slug)
|
||||
targetInstance := fullURL[:slugStart]
|
||||
|
||||
// Check if the URL points to a different instance
|
||||
if setting.AppURL != targetInstance {
|
||||
// Remove the http scheme for displaying
|
||||
targetInstance = httpSchemePattern.ReplaceAllString(targetInstance, "")
|
||||
|
||||
*text = targetInstance + slug + "@" + *text
|
||||
} else if !strings.HasSuffix(strings.TrimSuffix(ctx.Links.Base, "/"), slug) {
|
||||
// If it is a link to a different repo, but on the same instance only add the repo slug
|
||||
*text = slug + "@" + *text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -19,9 +20,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
TestAppURL = "http://localhost:3000/"
|
||||
TestOrgRepo = "gogits/gogs"
|
||||
TestRepoURL = TestAppURL + TestOrgRepo + "/"
|
||||
TestAppURL = "http://localhost:3000/"
|
||||
TestOrgRepo = "gogits/gogs"
|
||||
TestRepoURLWithoutSlash = TestAppURL + TestOrgRepo
|
||||
TestRepoURL = TestAppURL + TestOrgRepo + "/"
|
||||
)
|
||||
|
||||
// externalIssueLink an HTML link to an alphanumeric-style issue
|
||||
|
|
@ -107,7 +109,7 @@ func TestRender_IssueIndexPattern(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_IssueIndexPattern2(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
// numeric: render inputs with valid mentions
|
||||
test := func(s, expectedFmt, marker string, indices ...int) {
|
||||
|
|
@ -174,7 +176,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_IssueIndexPattern3(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
// alphanumeric: render inputs without valid mentions
|
||||
test := func(s string) {
|
||||
|
|
@ -202,7 +204,7 @@ func TestRender_IssueIndexPattern3(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_IssueIndexPattern4(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
// alphanumeric: render inputs with valid mentions
|
||||
test := func(s, expectedFmt string, names ...string) {
|
||||
|
|
@ -222,7 +224,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_IssueIndexPattern5(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
// regexp: render inputs without valid mentions
|
||||
test := func(s, expectedFmt, pattern string, ids, names []string) {
|
||||
|
|
@ -265,7 +267,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_IssueIndexPattern_Document(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
metas := map[string]string{
|
||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||
"user": "someUser",
|
||||
|
|
@ -301,9 +303,9 @@ func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *Rend
|
|||
}
|
||||
|
||||
func TestRender_AutoLink(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
test := func(input, expected, base string) {
|
||||
assert := func(input, expected, base string) {
|
||||
var buffer strings.Builder
|
||||
err := PostProcess(&RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
|
|
@ -330,7 +332,7 @@ func TestRender_AutoLink(t *testing.T) {
|
|||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
// render valid issue URLs
|
||||
test(util.URLJoin(TestRepoURL, "issues", "3333"),
|
||||
assert(util.URLJoin(TestRepoURL, "issues", "3333"),
|
||||
numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#"),
|
||||
TestRepoURL)
|
||||
})
|
||||
|
|
@ -338,81 +340,90 @@ func TestRender_AutoLink(t *testing.T) {
|
|||
t.Run("Commit", func(t *testing.T) {
|
||||
// render valid commit URLs
|
||||
tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>", TestRepoURL)
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24</code></a>", "https://localhost/forgejo/forgejo")
|
||||
test(
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24</code></a>", "/forgejo/forgejo")
|
||||
assert(
|
||||
tmp+"#diff-2",
|
||||
"<a href=\""+tmp+"#diff-2\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>",
|
||||
TestRepoURL,
|
||||
)
|
||||
test(
|
||||
assert(
|
||||
tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc",
|
||||
"<a href=\""+tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-953bb4f01b)</code></a>",
|
||||
TestRepoURL,
|
||||
TestRepoURLWithoutSlash,
|
||||
)
|
||||
|
||||
// render other commit URLs
|
||||
tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">go-gitea/gitea@d8a994ef24 (diff-2)</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">external-link.gitea.io/go-gitea/gitea@d8a994ef24 (diff-2)</code></a>", TestOrgRepo)
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://external-link.gitea.io/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
tmp = "http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
tmp = TestAppURL + "gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
|
||||
tmp = "http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/gogs@190d949293</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
|
||||
tmp = "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/gogs@190d949293</code></a>", "http://localhost:3000/sub1/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub1/sub2/sub3/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "http://localhost:3000/sub1/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
// if the repository happens to be named like one of the known app routes (e.g. `src`),
|
||||
// we can parse the URL correctly, if there is no sub path
|
||||
tmp = "http://localhost:3000/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
tmp = "http://localhost:3000/gogits/src/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
// but if there is a sub path, we cannot reliably distinguish the repo name from the app route
|
||||
tmp = "http://localhost:3000/sub/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">sub/gogits@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">sub/gogits@190d949293</code></a>", TestRepoURL)
|
||||
})
|
||||
|
||||
t.Run("Compare", func(t *testing.T) {
|
||||
tmp := util.URLJoin(TestRepoURL, "compare", "d8a994ef243349f321568f9e36d5c3f444b99cae..190d9492934af498c3f669d6a2431dc5459e5b20")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">d8a994ef24..190d949293</code></a>", TestRepoURL)
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24..190d949293</code></a>", "https://localhost/forgejo/forgejo")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">d8a994ef24..190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24..190d949293</code></a>", "https://localhost/forgejo/forgejo")
|
||||
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub/")()
|
||||
tmp = "http://localhost:3000/sub/gogits/gogs/compare/190d9492934af498c3f669d6a2431dc5459e5b20..d8a994ef243349f321568f9e36d5c3f444b99cae"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub/gogits/gugs")
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://external-link.gitea.io/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293..d8a994ef24</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub1/sub2/sub3/")()
|
||||
tmp = "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/compare/190d9492934af498c3f669d6a2431dc5459e5b20..d8a994ef243349f321568f9e36d5c3f444b99cae"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub1/gogits/gogs")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">190d949293..d8a994ef24</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "/gogits/gous")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">gogits/gogs@190d949293..d8a994ef24</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
tmp = "https://codeberg.org/forgejo/forgejo/compare/8bbac4c679bea930c74849c355a60ed3c52f8eb5...e2278e5a38187a1dc84dc41d583ec8b44e7257c1?files=options/locale/locale_fi-FI.ini"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>", "https://codeberg.org/forgejo/forgejo")
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>", TestRepoURL)
|
||||
test(tmp+".", "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>.", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">codeberg.org/forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>", TestRepoURL)
|
||||
assert(tmp+".", "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">codeberg.org/forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>.", TestRepoURL)
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://codeberg.org/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)</code></a>", "https://codeberg.org/forgejo/forgejo")
|
||||
|
||||
tmp = "https://codeberg.org/forgejo/forgejo/compare/8bbac4c679bea930c74849c355a60ed3c52f8eb5...e2278e5a38187a1dc84dc41d583ec8b44e7257c1?files=options/locale/locale_fi-FI.ini#L2"
|
||||
test(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini#L2)</code></a>", "https://codeberg.org/forgejo/forgejo")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"compare\"><code class=\"nohighlight\">8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini#L2)</code></a>", "https://codeberg.org/forgejo/forgejo")
|
||||
})
|
||||
|
||||
t.Run("Invalid URLs", func(t *testing.T) {
|
||||
tmp := "https://local host/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
test(tmp, "<a href=\"https://local\" class=\"link\">https://local</a> host/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20", TestRepoURL)
|
||||
assert(tmp, "<a href=\"https://local\" class=\"link\">https://local</a> host/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20", TestRepoURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRender_IssueIndexPatternRef(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
test := func(input, expected string) {
|
||||
var buf strings.Builder
|
||||
|
|
@ -428,7 +439,7 @@ func TestRender_IssueIndexPatternRef(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_FullIssueURLs(t *testing.T) {
|
||||
setting.AppURL = TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
|
||||
test := func(input, expected string) {
|
||||
var result strings.Builder
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestRender_Commits(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
test := func(input, expected string) {
|
||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
|
|
@ -94,10 +94,19 @@ func TestRender_Commits(t *testing.T) {
|
|||
|
||||
fileStrangeChars := util.URLJoin(repo, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d", "path", "to", "file%20%23.txt")
|
||||
test(fileStrangeChars, `<p><a href="`+fileStrangeChars+`" rel="nofollow"><code>eeb243c339/path/to/file #.txt</code></a></p>`)
|
||||
|
||||
commitLink := util.URLJoin(repo, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d")
|
||||
test(commitLink, `<p><a href="`+commitLink+`" rel="nofollow"><code>eeb243c339</code></a></p>`)
|
||||
|
||||
crossCommitLink := util.URLJoin(markup.TestAppURL, "forgejo/forgejo", "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d")
|
||||
test(crossCommitLink, `<p><a href="`+crossCommitLink+`" rel="nofollow"><code>forgejo/forgejo@eeb243c339</code></a></p>`)
|
||||
|
||||
extCommitLink := util.URLJoin("https://codeberg.org/", markup.TestOrgRepo, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d")
|
||||
test(extCommitLink, `<p><a href="`+extCommitLink+`" rel="nofollow"><code>codeberg.org/`+markup.TestOrgRepo+`@eeb243c339</code></a></p>`)
|
||||
}
|
||||
|
||||
func TestRender_CrossReferences(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
||||
|
|
@ -140,7 +149,7 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_links(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
||||
|
|
@ -242,12 +251,12 @@ func TestRender_links(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_PullReviewCommitLink(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
prCommitLink := util.URLJoin(markup.TestRepoURL, "pulls", "1", "commits", sha)
|
||||
|
||||
test := func(input, expected, base string) {
|
||||
assert := func(input, expected, base string) {
|
||||
buffer, err := markup.RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
RelativePath: ".md",
|
||||
|
|
@ -261,27 +270,28 @@ func TestRender_PullReviewCommitLink(t *testing.T) {
|
|||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
test(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`, markup.TestRepoURL)
|
||||
assert(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`, markup.TestRepoURL)
|
||||
|
||||
prCommitLink = util.URLJoin(markup.TestAppURL, "sub1", "sub2", markup.TestOrgRepo, "pulls", "1", "commits", sha)
|
||||
test(
|
||||
assert(
|
||||
prCommitLink,
|
||||
`<p><a href="`+prCommitLink+`" rel="nofollow">!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`,
|
||||
`<p><a href="`+prCommitLink+`" rel="nofollow">localhost:3000/sub1/sub2/gogits/gogs@!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`,
|
||||
util.URLJoin(markup.TestAppURL, "sub1", "sub2", markup.TestOrgRepo),
|
||||
)
|
||||
test(
|
||||
assert(
|
||||
prCommitLink,
|
||||
`<p><a href="`+prCommitLink+`" rel="nofollow">`+markup.TestOrgRepo+`@!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`,
|
||||
`<p><a href="`+prCommitLink+`" rel="nofollow">localhost:3000/sub1/sub2/gogits/gogs@!1 (commit <code>`+sha[0:10]+`</code>)</a></p>`,
|
||||
markup.TestRepoURL,
|
||||
)
|
||||
|
||||
prCommitLink = "https://codeberg.org/forgejo/forgejo/pulls/7979/commits/4d968c08e0a8d24bd2f3fb2a3a48b37e6d84a327#diff-7649acfa98a9ee3faf0d28b488bbff428317fc72"
|
||||
test(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">!7979 (commit <code>4d968c08e0</code>)</a></p>`, "https://codeberg.org/forgejo/forgejo")
|
||||
test(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">forgejo/forgejo@!7979 (commit <code>4d968c08e0</code>)</a></p>`, markup.TestRepoURL)
|
||||
assert(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">codeberg.org/forgejo/forgejo@!7979 (commit <code>4d968c08e0</code>)</a></p>`, markup.TestRepoURL)
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://codeberg.org/")()
|
||||
assert(prCommitLink, `<p><a href="`+prCommitLink+`" rel="nofollow">!7979 (commit <code>4d968c08e0</code>)</a></p>`, "https://codeberg.org/forgejo/forgejo")
|
||||
}
|
||||
|
||||
func TestRender_email(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
test := func(input, expected string) {
|
||||
res, err := markup.RenderString(&markup.RenderContext{
|
||||
|
|
@ -365,7 +375,7 @@ func TestRender_email(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_emoji(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
setting.StaticURLPrefix = markup.TestAppURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
|
|
@ -432,7 +442,7 @@ func TestRender_emoji(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_ShortLinks(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
tree := util.URLJoin(markup.TestRepoURL, "src", "master")
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
|
|
@ -545,7 +555,7 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRender_RelativeImages(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
test := func(input, expected, expectedWiki string) {
|
||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
||||
|
|
@ -585,7 +595,7 @@ func TestRender_RelativeImages(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_ParseClusterFuzz(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
localMetas := map[string]string{
|
||||
"user": "go-gitea",
|
||||
|
|
@ -621,7 +631,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostProcess_RenderDocument(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
|
||||
|
||||
localMetas := map[string]string{
|
||||
|
|
@ -666,7 +676,7 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIssue16020(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
|
||||
localMetas := map[string]string{
|
||||
"user": "go-gitea",
|
||||
|
|
@ -731,7 +741,7 @@ func TestIssue18471(t *testing.T) {
|
|||
}, strings.NewReader(data), &res)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
|
||||
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">domain/org/repo@783b039...da951ce</code></a>", res.String())
|
||||
}
|
||||
|
||||
func TestRender_FilePreview(t *testing.T) {
|
||||
|
|
@ -740,7 +750,7 @@ func TestRender_FilePreview(t *testing.T) {
|
|||
defer test.MockVariableValue(&setting.Langs, []string{"en-US"})()
|
||||
translation.InitLocales(t.Context())
|
||||
|
||||
setting.AppURL = markup.TestAppURL
|
||||
defer test.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
|
||||
markup.Init(&markup.ProcessorHelper{
|
||||
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
|
||||
gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
|
||||
|
|
@ -880,7 +890,7 @@ func TestRender_FilePreview(t *testing.T) {
|
|||
|
||||
testRender(
|
||||
urlWithSub,
|
||||
`<p><a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>gogits/gogs@190d949293/path/to/file.go (L2-L3)</code></a></p>`,
|
||||
`<p><a href="http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>localhost:3000/sub/gogits/gogs@190d949293/path/to/file.go (L2-L3)</code></a></p>`,
|
||||
localMetas,
|
||||
)
|
||||
|
||||
|
|
@ -920,7 +930,7 @@ func TestRender_FilePreview(t *testing.T) {
|
|||
|
||||
testRender(
|
||||
"first without sub "+commitFilePreview+" second "+urlWithSub,
|
||||
`<p>first without sub <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>190d949293/path/to/file.go (L2-L3)</code></a> second </p>`+
|
||||
`<p>first without sub <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" rel="nofollow"><code>localhost:3000/gogits/gogs@190d949293/path/to/file.go (L2-L3)</code></a> second </p>`+
|
||||
`<div class="file-preview-box">`+
|
||||
`<div class="header">`+
|
||||
`<div>`+
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markdown
|
||||
|
|
@ -87,6 +88,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||
if scope, found := ctx.Metas["scope"]; found {
|
||||
v.Name = fmt.Appendf(v.Name, "-%s", scope)
|
||||
}
|
||||
case *ast.RawHTML:
|
||||
g.transformRawHTML(ctx, v, reader)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markdown_test
|
||||
|
|
@ -125,6 +126,32 @@ func TestRender_Images(t *testing.T) {
|
|||
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
|
||||
}
|
||||
|
||||
func TestRender_Buttons(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: FullURL,
|
||||
},
|
||||
}, input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
|
||||
}
|
||||
|
||||
test(
|
||||
"<button>Test</button>",
|
||||
`<p><button type="button">Test</button></p>`)
|
||||
|
||||
test(
|
||||
`<button class="toggle-escape-button btn interact-bg">Test</button>`,
|
||||
`<p><button type="button" class="toggle-escape-button btn interact-bg">Test</button></p>`)
|
||||
test(
|
||||
`<button type="submit" class="toggle-escape-button btn interact-bg">Test</button>`,
|
||||
`<p><button type="button" class="toggle-escape-button btn interact-bg">Test</button></p>`)
|
||||
}
|
||||
|
||||
func testAnswers(baseURLContent, baseURLImages string) []string {
|
||||
return []string{
|
||||
`<p>Wiki! Enjoy :)</p>
|
||||
|
|
|
|||
28
modules/markup/markdown/transform_html.go
Normal file
28
modules/markup/markdown/transform_html.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"forgejo.org/modules/markup"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
func (g *ASTTransformer) addTypeToButton(v *ast.RawHTML, segment string) {
|
||||
segment = strings.TrimPrefix(segment, "<button")
|
||||
newTag := ast.NewString([]byte(`<button type="button"` + segment))
|
||||
newTag.SetCode(true)
|
||||
v.Parent().ReplaceChild(v.Parent(), v, newTag)
|
||||
}
|
||||
|
||||
func (g *ASTTransformer) transformRawHTML(_ *markup.RenderContext, v *ast.RawHTML, reader text.Reader) {
|
||||
segment := string(v.Segments.Value(reader.Source()))
|
||||
|
||||
if strings.HasPrefix(segment, "<button") {
|
||||
g.addTypeToButton(v, segment)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markup
|
||||
|
|
@ -78,6 +79,9 @@ func createDefaultPolicy() *bluemonday.Policy {
|
|||
policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||
policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input")
|
||||
|
||||
// Buttons
|
||||
policy.AllowAttrs("type").Matching(regexp.MustCompile(`^button$`)).OnElements("button")
|
||||
|
||||
// Custom URL-Schemes
|
||||
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
|
|
@ -239,12 +239,19 @@ func dbConnStrWithHost(host string) (string, error) {
|
|||
if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil {
|
||||
return "", fmt.Errorf("failed to create directories: %w", err)
|
||||
}
|
||||
journalMode := ""
|
||||
opts := ""
|
||||
if Database.SQLiteJournalMode != "" {
|
||||
journalMode = "&_journal_mode=" + Database.SQLiteJournalMode
|
||||
opts = "&_journal_mode=" + Database.SQLiteJournalMode
|
||||
}
|
||||
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
|
||||
Database.Path, Database.Timeout, journalMode)
|
||||
|
||||
// in memory mode needs shared cache to be usable by multiple connections
|
||||
// only used in tests normally
|
||||
if Database.Path == ":memory:" {
|
||||
opts += "&cache=shared"
|
||||
} else {
|
||||
opts += "&mode=rwc"
|
||||
}
|
||||
connStr = fmt.Sprintf("file:%s?_busy_timeout=%d&_txlock=immediate%s", Database.Path, Database.Timeout, opts)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown database type: %s", Database.Type)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ func Init(ctx context.Context) error {
|
|||
detailConcat := strings.Join(unexpectedKeys, "\n\t")
|
||||
log.Fatal("An unexpected ssh public key was discovered. Forgejo will shutdown to require this to be fixed. Fix by either:\n"+
|
||||
"Option 1: Delete the file %s, and Forgejo will recreate it with only expected ssh public keys.\n"+
|
||||
"Option 2: Permit unexpected keys by setting [server].SSH_ALLOW_UNEXPECTED_AUTHORIZED_KEYS=true in Forgejo's config file.\n\t"+
|
||||
"Option 2: Permit unexpected keys by setting [server].SSH_ALLOW_UNEXPECTED_AUTHORIZED_KEYS=true in Forgejo's config file.\n"+
|
||||
"Option 3: If unused, disable SSH support by setting [server].DISABLE_SSH=true in Forgejo's config file.\n"+
|
||||
"\t"+
|
||||
detailConcat, filepath.Join(setting.SSH.RootPath, "authorized_keys"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,27 +32,3 @@ type ActionTaskResponse struct {
|
|||
Entries []*ActionTask `json:"workflow_runs"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
||||
// ActionRunnerLabel represents a Runner Label
|
||||
type ActionRunnerLabel struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ActionRunner represents a Runner
|
||||
type ActionRunner struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Busy bool `json:"busy"`
|
||||
// currently unused as forgejo does not support ephemeral runners, but they are defined in gh api spec
|
||||
Ephemeral bool `json:"ephemeral"`
|
||||
Labels []*ActionRunnerLabel `json:"labels"`
|
||||
}
|
||||
|
||||
// ActionRunnersResponse returns Runners
|
||||
type ActionRunnersResponse struct {
|
||||
Entries []*ActionRunner `json:"runners"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
"forgejo.org/modules/markup"
|
||||
|
||||
_ "forgejo.org/models"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/issues"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
|||
17
modules/testimport/import.go
Normal file
17
modules/testimport/import.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2026 The Forgejo Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package testimport
|
||||
|
||||
// ensure the init() function of those modules are called in a test
|
||||
// environment that may not include them. It matters when the engine
|
||||
// is trying to figure out the ordering of foreign keys, for instance
|
||||
|
||||
import ( //revive:disable:blank-imports
|
||||
_ "forgejo.org/models/actions"
|
||||
_ "forgejo.org/models/activities"
|
||||
_ "forgejo.org/models/auth"
|
||||
_ "forgejo.org/models/forgefed"
|
||||
_ "forgejo.org/models/perm/access"
|
||||
_ "forgejo.org/models/repo"
|
||||
)
|
||||
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