Merge branch 'main' into lunny/refactor_getpatch

This commit is contained in:
Lunny Xiao 2025-01-02 21:11:37 -08:00
commit d9bc413b81
405 changed files with 5889 additions and 4433 deletions

View File

@ -674,7 +674,7 @@ module.exports = {
'no-this-before-super': [2], 'no-this-before-super': [2],
'no-throw-literal': [2], 'no-throw-literal': [2],
'no-undef-init': [2], 'no-undef-init': [2],
'no-undef': [0], 'no-undef': [2], // it is still needed by eslint & IDE to prompt undefined names in real time
'no-undefined': [0], 'no-undefined': [0],
'no-underscore-dangle': [0], 'no-underscore-dangle': [0],
'no-unexpected-multiline': [2], 'no-unexpected-multiline': [2],

4
.gitignore vendored
View File

@ -9,6 +9,10 @@ _test
# IntelliJ # IntelliJ
.idea .idea
# IntelliJ Gateway
.uuid
# Goland's output filename can not be set manually # Goland's output filename can not be set manually
/go_build_* /go_build_*
/gitea_* /gitea_*

View File

@ -31,7 +31,6 @@ Gary Kim <gary@garykim.dev> (@gary-kim)
Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k) Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
Mura Li <typeless@ctli.io> (@typeless) Mura Li <typeless@ctli.io> (@typeless)
6543 <6543@obermui.de> (@6543) 6543 <6543@obermui.de> (@6543)
jaqra <jaqra@hotmail.com> (@jaqra)
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson) David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
a1012112796 <1012112796@qq.com> (@a1012112796) a1012112796 <1012112796@qq.com> (@a1012112796)
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise) Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
@ -63,3 +62,4 @@ Yu Liu <1240335630@qq.com> (@HEREYUA)
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb) Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
Rowan Bohde <rowan.bohde@gmail.com> (@bohde) Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
hiifong <i@hiif.ong> (@hiifong) hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci)

View File

@ -806,22 +806,22 @@ $(DIST_DIRS):
.PHONY: release-windows .PHONY: release-windows
release-windows: | $(DIST_DIRS) release-windows: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq (,$(findstring gogit,$(TAGS))) ifeq (,$(findstring gogit,$(TAGS)))
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
endif endif
.PHONY: release-linux .PHONY: release-linux
release-linux: | $(DIST_DIRS) release-linux: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
.PHONY: release-darwin .PHONY: release-darwin
release-darwin: | $(DIST_DIRS) release-darwin: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
.PHONY: release-freebsd .PHONY: release-freebsd
release-freebsd: | $(DIST_DIRS) release-freebsd: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
.PHONY: release-copy .PHONY: release-copy
release-copy: | $(DIST_DIRS) release-copy: | $(DIST_DIRS)

File diff suppressed because one or more lines are too long

View File

@ -69,6 +69,10 @@ var microcmdUserCreate = &cli.Command{
} }
func runCreateUser(c *cli.Context) error { func runCreateUser(c *cli.Context) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
if err := argsSet(c, "email"); err != nil { if err := argsSet(c, "email"); err != nil {
return err return err
} }

View File

@ -165,6 +165,7 @@ func NewMainApp(appVer AppVersion) *cli.App {
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
setting.InitGiteaEnvVars()
return app return app
} }

View File

@ -6,7 +6,6 @@ package cmd
import ( import (
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -113,37 +112,17 @@ func TestCliCmd(t *testing.T) {
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil return nil
}) })
var envBackup []string
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
}
}
}
defer func() {
clearGiteaEnv()
for _, s := range envBackup {
k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases { for _, c := range cases {
clearGiteaEnv() t.Run(c.cmd, func(t *testing.T) {
for k, v := range c.env { for k, v := range c.env {
_ = os.Setenv(k, v) t.Setenv(k, v)
} }
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...) r, err := runTestApp(app, args...)
assert.NoError(t, err, c.cmd) assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd) assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd) assert.Contains(t, r.Stdout, c.exp, c.cmd)
})
} }
} }

View File

@ -18,7 +18,7 @@ import (
var CmdMigrate = &cli.Command{ var CmdMigrate = &cli.Command{
Name: "migrate", Name: "migrate",
Usage: "Migrate the database", Usage: "Migrate the database",
Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.", Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
Action: runMigrate, Action: runMigrate,
} }

View File

@ -12,6 +12,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running _ "net/http/pprof" // Used for debugging if enabled and a web server is running
@ -115,6 +116,16 @@ func showWebStartupMessage(msg string) {
log.Info("* CustomPath: %s", setting.CustomPath) log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf) log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg) // show startup message log.Info("%s", msg) // show startup message
if setting.CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
if setting.DefaultUILocation != time.Local {
log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
}
if setting.MailService != nil {
log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
}
} }
func serveInstall(ctx *cli.Context) error { func serveInstall(ctx *cli.Context) error {

View File

@ -54,7 +54,7 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p altTLSALPNPort = p
} }
magic := certmagic.NewDefault() magic := &certmagic.Default
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust // Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool var certPool *x509.CertPool

View File

@ -1339,6 +1339,9 @@ LEVEL = Info
;; Number of repos that are displayed on one page ;; Number of repos that are displayed on one page
;REPO_PAGING_NUM = 15 ;REPO_PAGING_NUM = 15
;; Number of orgs that are displayed on profile page
;ORG_PAGING_NUM = 15
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[ui.meta] ;[ui.meta]
@ -1482,6 +1485,10 @@ LEVEL = Info
;REPO_INDEXER_EXCLUDE = ;REPO_INDEXER_EXCLUDE =
;; ;;
;MAX_FILE_SIZE = 1048576 ;MAX_FILE_SIZE = 1048576
;;
;; Bleve engine has performance problems with fuzzy search, so we limit the fuzziness to 0 by default to disable it.
;; If you'd like to enable it, you can set it to a value between 0 and 2.
;TYPE_BLEVE_MAX_FUZZINESS = 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

10
go.mod
View File

@ -28,7 +28,6 @@ require (
github.com/PuerkitoBio/goquery v1.10.0 github.com/PuerkitoBio/goquery v1.10.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/alecthomas/chroma/v2 v2.14.0 github.com/alecthomas/chroma/v2 v2.14.0
github.com/aws/aws-sdk-go v1.55.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 github.com/aws/aws-sdk-go-v2/credentials v1.17.42
github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3 github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@ -61,7 +60,6 @@ require (
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/go-swagger/go-swagger v0.31.0 github.com/go-swagger/go-swagger v0.31.0
github.com/go-testfixtures/testfixtures/v3 v3.11.0
github.com/go-webauthn/webauthn v0.11.2 github.com/go-webauthn/webauthn v0.11.2
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
@ -145,8 +143,6 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/ClickHouse/ch-go v0.63.1 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.24.0 // indirect
github.com/DataDog/zstd v1.5.6 // indirect github.com/DataDog/zstd v1.5.6 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
@ -204,8 +200,6 @@ require (
github.com/go-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect github.com/go-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
@ -270,7 +264,6 @@ require (
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/paulmach/orb v0.11.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
@ -285,7 +278,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
@ -310,8 +302,6 @@ require (
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.3.11 // indirect go.etcd.io/bbolt v1.3.11 // indirect
go.mongodb.org/mongo-driver v1.17.1 // indirect go.mongodb.org/mongo-driver v1.17.1 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect

62
go.sum
View File

@ -59,10 +59,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/ch-go v0.63.1 h1:s2JyZvWLTCSAGdtjMBBmAgQQHMco6pawLJMOXi0FODM=
github.com/ClickHouse/ch-go v0.63.1/go.mod h1:I1kJJCL3WJcBMGe1m+HVK0+nREaG+JOYYBWjrDrF3R0=
github.com/ClickHouse/clickhouse-go/v2 v2.24.0 h1:L/n/pVVpk95KtkHOiKuSnO7cu2ckeW4gICbbOh5qs74=
github.com/ClickHouse/clickhouse-go/v2 v2.24.0/go.mod h1:iDTViXk2Fgvf1jn2dbJd1ys+fBkdD1UMRnXlwmhijhQ=
github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
@ -109,8 +105,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM=
@ -237,8 +231,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA= github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA=
@ -317,10 +309,6 @@ github.com/go-enry/go-enry/v2 v2.9.1 h1:G9iDteJ/Mc0F4Di5NeQknf83R2OkRbwY9cAYmcqV
github.com/go-enry/go-enry/v2 v2.9.1/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-enry/v2 v2.9.1/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@ -372,8 +360,6 @@ github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-testfixtures/testfixtures/v3 v3.11.0 h1:XxQr8AnPORcZkyNd7go5UNLPD3dULN8ixYISlzrlfEQ=
github.com/go-testfixtures/testfixtures/v3 v3.11.0/go.mod h1:THmudHF1Ixq++J2/UodcJpxUphfyEd77m83TvDtryqE=
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU= github.com/go-webauthn/x v0.1.15 h1:eG1OhggBJTkDE8gUeOlGRbRe8E/PSVG26YG4AyFbwkU=
@ -385,7 +371,6 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0= github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
@ -410,7 +395,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -497,22 +481,6 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -533,10 +501,6 @@ github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bB
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw= github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs= github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -550,11 +514,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4= github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4=
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@ -629,7 +590,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
@ -670,9 +630,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
@ -735,8 +692,6 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@ -783,7 +738,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -797,7 +751,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
@ -823,9 +776,6 @@ github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+D
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 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/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@ -842,9 +792,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs= github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -863,13 +811,8 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
@ -941,7 +884,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@ -1022,10 +964,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@ -1046,8 +986,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -275,7 +275,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return err return err
} }
run.Index = index run.Index = index
run.Title, _ = util.SplitStringAtByteN(run.Title, 255) run.Title = util.EllipsisDisplayString(run.Title, 255)
if err := db.Insert(ctx, run); err != nil { if err := db.Insert(ctx, run); err != nil {
return err return err
@ -308,7 +308,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
} else { } else {
hasWaiting = true hasWaiting = true
} }
job.Name, _ = util.SplitStringAtByteN(job.Name, 255) job.Name = util.EllipsisDisplayString(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{ runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID, RunID: run.ID,
RepoID: run.RepoID, RepoID: run.RepoID,
@ -402,7 +402,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
if len(cols) > 0 { if len(cols) > 0 {
sess.Cols(cols...) sess.Cols(cols...)
} }
run.Title, _ = util.SplitStringAtByteN(run.Title, 255) run.Title = util.EllipsisDisplayString(run.Title, 255)
affected, err := sess.Update(run) affected, err := sess.Update(run)
if err != nil { if err != nil {
return err return err

View File

@ -252,7 +252,7 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
// UpdateRunner updates runner's information. // UpdateRunner updates runner's information.
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error { func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
r.Name, _ = util.SplitStringAtByteN(r.Name, 255) r.Name = util.EllipsisDisplayString(r.Name, 255)
var err error var err error
if len(cols) == 0 { if len(cols) == 0 {
_, err = e.ID(r.ID).AllCols().Update(r) _, err = e.ID(r.ID).AllCols().Update(r)
@ -279,7 +279,7 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
// Remove OwnerID to avoid confusion; it's not worth returning an error here. // Remove OwnerID to avoid confusion; it's not worth returning an error here.
t.OwnerID = 0 t.OwnerID = 0
} }
t.Name, _ = util.SplitStringAtByteN(t.Name, 255) t.Name = util.EllipsisDisplayString(t.Name, 255)
return db.Insert(ctx, t) return db.Insert(ctx, t)
} }

View File

@ -10,7 +10,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -52,7 +51,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist) return nil, fmt.Errorf(`runner token "%s...": %w`, util.TruncateRunes(token, 3), util.ErrNotExist)
} }
return &runnerToken, nil return &runnerToken, nil
} }

View File

@ -68,7 +68,7 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
// Loop through each schedule row // Loop through each schedule row
for _, row := range rows { for _, row := range rows {
row.Title, _ = util.SplitStringAtByteN(row.Title, 255) row.Title = util.EllipsisDisplayString(row.Title, 255)
// Create new schedule row // Create new schedule row
if err = db.Insert(ctx, row); err != nil { if err = db.Insert(ctx, row); err != nil {
return err return err

View File

@ -298,7 +298,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if len(workflowJob.Steps) > 0 { if len(workflowJob.Steps) > 0 {
steps := make([]*ActionTaskStep, len(workflowJob.Steps)) steps := make([]*ActionTaskStep, len(workflowJob.Steps))
for i, v := range workflowJob.Steps { for i, v := range workflowJob.Steps {
name, _ := util.SplitStringAtByteN(v.String(), 255) name := util.EllipsisDisplayString(v.String(), 255)
steps[i] = &ActionTaskStep{ steps[i] = &ActionTaskStep{
Name: name, Name: name,
TaskID: task.ID, TaskID: task.ID,

View File

@ -20,12 +20,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
@ -226,7 +226,7 @@ func (a *Action) GetActUserName(ctx context.Context) string {
// ShortActUserName gets the action's user name trimmed to max 20 // ShortActUserName gets the action's user name trimmed to max 20
// chars. // chars.
func (a *Action) ShortActUserName(ctx context.Context) string { func (a *Action) ShortActUserName(ctx context.Context) string {
return base.EllipsisString(a.GetActUserName(ctx), 20) return util.EllipsisDisplayString(a.GetActUserName(ctx), 20)
} }
// GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank. // GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
@ -260,7 +260,7 @@ func (a *Action) GetRepoUserName(ctx context.Context) string {
// ShortRepoUserName returns the name of the action repository owner // ShortRepoUserName returns the name of the action repository owner
// trimmed to max 20 chars. // trimmed to max 20 chars.
func (a *Action) ShortRepoUserName(ctx context.Context) string { func (a *Action) ShortRepoUserName(ctx context.Context) string {
return base.EllipsisString(a.GetRepoUserName(ctx), 20) return util.EllipsisDisplayString(a.GetRepoUserName(ctx), 20)
} }
// GetRepoName returns the name of the action repository. // GetRepoName returns the name of the action repository.
@ -275,7 +275,7 @@ func (a *Action) GetRepoName(ctx context.Context) string {
// ShortRepoName returns the name of the action repository // ShortRepoName returns the name of the action repository
// trimmed to max 33 chars. // trimmed to max 33 chars.
func (a *Action) ShortRepoName(ctx context.Context) string { func (a *Action) ShortRepoName(ctx context.Context) string {
return base.EllipsisString(a.GetRepoName(ctx), 33) return util.EllipsisDisplayString(a.GetRepoName(ctx), 33)
} }
// GetRepoPath returns the virtual path to the action repository. // GetRepoPath returns the virtual path to the action repository.

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -337,8 +338,10 @@ func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false). And("issue.is_pull = ?", false).
And("issue.created_unix >= ?", fromTime.Unix()). And(builder.Or(
Or("issue.closed_unix >= ?", fromTime.Unix()) builder.Gte{"issue.created_unix": fromTime.Unix()},
builder.Gte{"issue.closed_unix": fromTime.Unix()},
))
return sess return sess
} }

View File

@ -64,11 +64,9 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID})
doer := &user_model.User{ID: tc.doerID} var doer *user_model.User
_, err := unittest.LoadBeanIfExists(doer) if tc.doerID != 0 {
assert.NoError(t, err) doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.doerID})
if tc.doerID == 0 {
doer = nil
} }
// get the action for comparison // get the action for comparison

View File

@ -44,7 +44,7 @@ func TestWebAuthnCredential_UpdateSignCount(t *testing.T) {
cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
cred.SignCount = 1 cred.SignCount = 1
assert.NoError(t, cred.UpdateSignCount(db.DefaultContext)) assert.NoError(t, cred.UpdateSignCount(db.DefaultContext))
unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1}) unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1})
} }
func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) { func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) {
@ -52,7 +52,7 @@ func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) {
cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
cred.SignCount = 0xffffffff cred.SignCount = 0xffffffff
assert.NoError(t, cred.UpdateSignCount(db.DefaultContext)) assert.NoError(t, cred.UpdateSignCount(db.DefaultContext))
unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff}) unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff})
} }
func TestCreateCredential(t *testing.T) { func TestCreateCredential(t *testing.T) {
@ -63,5 +63,5 @@ func TestCreateCredential(t *testing.T) {
assert.Equal(t, "WebAuthn Created Credential", res.Name) assert.Equal(t, "WebAuthn Created Credential", res.Name)
assert.Equal(t, []byte("Test"), res.CredentialID) assert.Equal(t, []byte("Test"), res.CredentialID)
unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
} }

View File

@ -5,20 +5,13 @@ package db
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
var ( var ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
// ErrNameEmpty name is empty error
ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
// AlphaDashDotPattern characters prohibited in a username (anything except A-Za-z0-9_.-)
AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
// ErrNameReserved represents a "reserved name" error. // ErrNameReserved represents a "reserved name" error.
type ErrNameReserved struct { type ErrNameReserved struct {
@ -82,20 +75,20 @@ func (err ErrNameCharsNotAllowed) Unwrap() error {
// IsUsableName checks if name is reserved or pattern of name is not allowed // IsUsableName checks if name is reserved or pattern of name is not allowed
// based on given reserved names and patterns. // based on given reserved names and patterns.
// Names are exact match, patterns can be prefix or suffix match with placeholder '*'. // Names are exact match, patterns can be a prefix or suffix match with placeholder '*'.
func IsUsableName(names, patterns []string, name string) error { func IsUsableName(reservedNames, reservedPatterns []string, name string) error {
name = strings.TrimSpace(strings.ToLower(name)) name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 { if utf8.RuneCountInString(name) == 0 {
return ErrNameEmpty return ErrNameEmpty
} }
for i := range names { for i := range reservedNames {
if name == names[i] { if name == reservedNames[i] {
return ErrNameReserved{name} return ErrNameReserved{name}
} }
} }
for _, pat := range patterns { for _, pat := range reservedPatterns {
if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) || if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) { (pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
return ErrNamePatternNotAllowed{pat} return ErrNamePatternNotAllowed{pat}

View File

@ -64,7 +64,7 @@
name: job2 name: job2
attempt: 1 attempt: 1
job_id: job2 job_id: job2
needs: [job1] needs: '["job1"]'
task_id: 51 task_id: 51
status: 5 status: 5
started: 1683636528 started: 1683636528

View File

@ -96,3 +96,14 @@
num_issues: 0 num_issues: 0
num_closed_issues: 0 num_closed_issues: 0
archived_unix: 0 archived_unix: 0
-
id: 10
repo_id: 3
org_id: 0
name: repo3label1
color: '#112233'
exclusive: false
num_issues: 0
num_closed_issues: 0
archived_unix: 0

View File

@ -2,23 +2,23 @@
id: 1 id: 1
repo_id: 4 repo_id: 4
name_pattern: /v.+/ name_pattern: /v.+/
allowlist_user_i_ds: [] allowlist_user_i_ds: "[]"
allowlist_team_i_ds: [] allowlist_team_i_ds: "[]"
created_unix: 1715596037 created_unix: 1715596037
updated_unix: 1715596037 updated_unix: 1715596037
- -
id: 2 id: 2
repo_id: 1 repo_id: 1
name_pattern: v-* name_pattern: v-*
allowlist_user_i_ds: [] allowlist_user_i_ds: "[]"
allowlist_team_i_ds: [] allowlist_team_i_ds: "[]"
created_unix: 1715596037 created_unix: 1715596037
updated_unix: 1715596037 updated_unix: 1715596037
- -
id: 3 id: 3
repo_id: 1 repo_id: 1
name_pattern: v-1.1 name_pattern: v-1.1
allowlist_user_i_ds: [2] allowlist_user_i_ds: "[2]"
allowlist_team_i_ds: [] allowlist_team_i_ds: "[]"
created_unix: 1715596037 created_unix: 1715596037
updated_unix: 1715596037 updated_unix: 1715596037

View File

@ -197,6 +197,20 @@ func (t CommentType) HasMailReplySupport() bool {
return false return false
} }
func (t CommentType) CountedAsConversation() bool {
for _, ct := range ConversationCountedCommentType() {
if t == ct {
return true
}
}
return false
}
// ConversationCountedCommentType returns the comment types that are counted as a conversation
func ConversationCountedCommentType() []CommentType {
return []CommentType{CommentTypeComment, CommentTypeReview}
}
// RoleInRepo presents the user's participation in the repo // RoleInRepo presents the user's participation in the repo
type RoleInRepo string type RoleInRepo string
@ -592,14 +606,12 @@ func (c *Comment) LoadAttachments(ctx context.Context) error {
return nil return nil
} }
// UpdateAttachments update attachments by UUIDs for the comment // UpdateCommentAttachments update attachments by UUIDs for the comment
func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error { func UpdateCommentAttachments(ctx context.Context, c *Comment, uuids []string) error {
ctx, committer, err := db.TxContext(ctx) if len(uuids) == 0 {
if err != nil { return nil
return err
} }
defer committer.Close() return db.WithTx(ctx, func(ctx context.Context) error {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids) attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
if err != nil { if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
@ -611,7 +623,9 @@ func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error {
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err) return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
} }
} }
return committer.Commit() c.Attachments = attachments
return nil
})
} }
// LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees // LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees
@ -878,7 +892,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
// Check comment type. // Check comment type.
switch opts.Type { switch opts.Type {
case CommentTypeCode: case CommentTypeCode:
if err = updateAttachments(ctx, opts, comment); err != nil { if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err return err
} }
if comment.ReviewID != 0 { if comment.ReviewID != 0 {
@ -893,12 +907,12 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
} }
fallthrough fallthrough
case CommentTypeComment: case CommentTypeComment:
if _, err = db.Exec(ctx, "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { if err := UpdateIssueNumComments(ctx, opts.Issue.ID); err != nil {
return err return err
} }
fallthrough fallthrough
case CommentTypeReview: case CommentTypeReview:
if err = updateAttachments(ctx, opts, comment); err != nil { if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err return err
} }
case CommentTypeReopen, CommentTypeClose: case CommentTypeReopen, CommentTypeClose:
@ -910,23 +924,6 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
return UpdateIssueCols(ctx, opts.Issue, "updated_unix") return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
} }
func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
}
for i := range attachments {
attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
}
}
comment.Attachments = attachments
return nil
}
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) { func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
var content string var content string
var commentType CommentType var commentType CommentType
@ -1182,8 +1179,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
return err return err
} }
if comment.Type == CommentTypeComment { if comment.Type.CountedAsConversation() {
if _, err := e.ID(comment.IssueID).Decr("num_comments").Update(new(Issue)); err != nil { if err := UpdateIssueNumComments(ctx, comment.IssueID); err != nil {
return err return err
} }
} }
@ -1300,6 +1297,21 @@ func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0 return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
} }
func UpdateIssueNumCommentsBuilder(issueID int64) *builder.Builder {
subQuery := builder.Select("COUNT(*)").From("`comment`").Where(
builder.Eq{"issue_id": issueID}.And(
builder.In("`type`", ConversationCountedCommentType()),
))
return builder.Update(builder.Eq{"num_comments": subQuery}).
From("`issue`").Where(builder.Eq{"id": issueID})
}
func UpdateIssueNumComments(ctx context.Context, issueID int64) error {
_, err := db.GetEngine(ctx).Exec(UpdateIssueNumCommentsBuilder(issueID))
return err
}
// InsertIssueComments inserts many comments of issues. // InsertIssueComments inserts many comments of issues.
func InsertIssueComments(ctx context.Context, comments []*Comment) error { func InsertIssueComments(ctx context.Context, comments []*Comment) error {
if len(comments) == 0 { if len(comments) == 0 {
@ -1332,8 +1344,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
} }
for _, issueID := range issueIDs { for _, issueID := range issueIDs {
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?", if err := UpdateIssueNumComments(ctx, issueID); err != nil {
issueID, CommentTypeComment, issueID); err != nil {
return err return err
} }
} }

View File

@ -45,6 +45,24 @@ func TestCreateComment(t *testing.T) {
unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
} }
func Test_UpdateCommentAttachment(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
attachment := repo_model.Attachment{
Name: "test.txt",
}
assert.NoError(t, db.Insert(db.DefaultContext, &attachment))
err := issues_model.UpdateCommentAttachments(db.DefaultContext, comment, []string{attachment.UUID})
assert.NoError(t, err)
attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID})
assert.EqualValues(t, attachment.Name, attachment2.Name)
assert.EqualValues(t, comment.ID, attachment2.CommentID)
assert.EqualValues(t, comment.IssueID, attachment2.IssueID)
}
func TestFetchCodeComments(t *testing.T) { func TestFetchCodeComments(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
@ -97,3 +115,12 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
unittest.CheckConsistencyFor(t, &issues_model.Issue{}) unittest.CheckConsistencyFor(t, &issues_model.Issue{})
} }
func Test_UpdateIssueNumComments(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.NoError(t, issues_model.UpdateIssueNumComments(db.DefaultContext, issue2.ID))
issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.EqualValues(t, 1, issue2.NumComments)
}

View File

@ -49,9 +49,13 @@ func TestCreateIssueDependency(t *testing.T) {
assert.False(t, left) assert.False(t, left)
// Close #2 and check again // Close #2 and check again
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true) _, err = issues_model.CloseIssue(db.DefaultContext, issue2, user1)
assert.NoError(t, err) assert.NoError(t, err)
issue2Closed, err := issues_model.GetIssueByID(db.DefaultContext, 2)
assert.NoError(t, err)
assert.True(t, issue2Closed.IsClosed)
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1) left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, left) assert.True(t, left)
@ -59,4 +63,11 @@ func TestCreateIssueDependency(t *testing.T) {
// Test removing the dependency // Test removing the dependency
err = issues_model.RemoveIssueDependency(db.DefaultContext, user1, issue1, issue2, issues_model.DependencyTypeBlockedBy) err = issues_model.RemoveIssueDependency(db.DefaultContext, user1, issue1, issue2, issues_model.DependencyTypeBlockedBy)
assert.NoError(t, err) assert.NoError(t, err)
_, err = issues_model.ReopenIssue(db.DefaultContext, issue2, user1)
assert.NoError(t, err)
issue2Reopened, err := issues_model.GetIssueByID(db.DefaultContext, 2)
assert.NoError(t, err)
assert.False(t, issue2Reopened.IsClosed)
} }

View File

@ -34,7 +34,7 @@ func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
return nil return nil
} }
func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) { func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
// Reload the issue // Reload the issue
currentIssue, err := GetIssueByID(ctx, issue.ID) currentIssue, err := GetIssueByID(ctx, issue.ID)
if err != nil { if err != nil {
@ -119,8 +119,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
}) })
} }
// ChangeIssueStatus changes issue status to open or closed. // CloseIssue changes issue status to closed.
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) { func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if err := issue.LoadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, err return nil, err
} }
@ -128,7 +128,45 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
return nil, err return nil, err
} }
return changeIssueStatus(ctx, issue, doer, isClosed, false) ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
comment, err := ChangeIssueStatus(ctx, issue, doer, true, false)
if err != nil {
return nil, err
}
if err := committer.Commit(); err != nil {
return nil, err
}
return comment, nil
}
// ReopenIssue changes issue status to open.
func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
if err := issue.LoadPoster(ctx); err != nil {
return nil, err
}
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
comment, err := ChangeIssueStatus(ctx, issue, doer, false, false)
if err != nil {
return nil, err
}
if err := committer.Commit(); err != nil {
return nil, err
}
return comment, nil
} }
// ChangeIssueTitle changes the title of this issue, as the given user. // ChangeIssueTitle changes the title of this issue, as the given user.
@ -139,7 +177,7 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User,
} }
defer committer.Close() defer committer.Close()
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255) issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = UpdateIssueCols(ctx, issue, "name"); err != nil { if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
return fmt.Errorf("updateIssueCols: %w", err) return fmt.Errorf("updateIssueCols: %w", err)
} }
@ -367,19 +405,10 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
return err return err
} }
if len(opts.Attachments) > 0 { if err := UpdateIssueAttachments(ctx, opts.Issue.ID, opts.Attachments); err != nil {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments) return err
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
} }
for i := 0; i < len(attachments); i++ {
attachments[i].IssueID = opts.Issue.ID
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
}
}
}
if err = opts.Issue.LoadAttributes(ctx); err != nil { if err = opts.Issue.LoadAttributes(ctx); err != nil {
return err return err
} }
@ -402,7 +431,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
} }
issue.Index = idx issue.Index = idx
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255) issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo, Repo: repo,

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_NewIssueUsers(t *testing.T) { func Test_NewIssueUsers(t *testing.T) {
@ -27,9 +28,8 @@ func Test_NewIssueUsers(t *testing.T) {
} }
// artificially insert new issue // artificially insert new issue
unittest.AssertSuccessfulInsert(t, newIssue) require.NoError(t, db.Insert(db.DefaultContext, newIssue))
require.NoError(t, issues_model.NewIssueUsers(db.DefaultContext, repo, newIssue))
assert.NoError(t, issues_model.NewIssueUsers(db.DefaultContext, repo, newIssue))
// issue_user table should now have entries for new issue // issue_user table should now have entries for new issue
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID})

View File

@ -98,7 +98,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false) i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false) i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false) i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true) _, err := issues_model.CloseIssue(db.DefaultContext, i3, d)
assert.NoError(t, err) assert.NoError(t, err)
pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index)) pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))

View File

@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st
Find(&labelIDs) Find(&labelIDs)
} }
// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org.
func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
labelIDs := make([]int64, 0, len(labelNames))
return labelIDs, db.GetEngine(ctx).Table("label").
Where("org_id = ?", orgID).
In("name", labelNames).
Asc("name").
Cols("id").
Find(&labelIDs)
}
// BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names // BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names
func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
return builder.Select("issue_label.issue_id"). return builder.Select("issue_label.issue_id").

View File

@ -387,7 +387,7 @@ func TestDeleteIssueLabel(t *testing.T) {
expectedNumIssues := label.NumIssues expectedNumIssues := label.NumIssues
expectedNumClosedIssues := label.NumClosedIssues expectedNumClosedIssues := label.NumClosedIssues
if unittest.BeanExists(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: labelID}) { if unittest.GetBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: labelID}) != nil {
expectedNumIssues-- expectedNumIssues--
if issue.IsClosed { if issue.IsClosed {
expectedNumClosedIssues-- expectedNumClosedIssues--

View File

@ -499,65 +499,6 @@ func (pr *PullRequest) IsFromFork() bool {
return pr.HeadRepoID != pr.BaseRepoID return pr.HeadRepoID != pr.BaseRepoID
} }
// SetMerged sets a pull request to merged and closes the corresponding issue
func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
if pr.HasMerged {
return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
}
if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
return false, fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
}
pr.HasMerged = true
sess := db.GetEngine(ctx)
if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil {
return false, err
}
if _, err := sess.Exec("UPDATE `pull_request` SET `issue_id` = `issue_id` WHERE `id` = ?", pr.ID); err != nil {
return false, err
}
pr.Issue = nil
if err := pr.LoadIssue(ctx); err != nil {
return false, err
}
if tmpPr, err := GetPullRequestByID(ctx, pr.ID); err != nil {
return false, err
} else if tmpPr.HasMerged {
if pr.Issue.IsClosed {
return false, nil
}
return false, fmt.Errorf("PullRequest[%d] already merged but it's associated issue [%d] is not closed", pr.Index, pr.IssueID)
} else if pr.Issue.IsClosed {
return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index)
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
return false, err
}
if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
return false, err
}
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
return false, fmt.Errorf("Issue.changeStatus: %w", err)
}
// reset the conflicted files as there cannot be any if we're merged
pr.ConflictedFiles = []string{}
// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").Update(pr); err != nil {
return false, fmt.Errorf("Failed to update pr[%d]: %w", pr.ID, err)
}
return true, nil
}
// NewPullRequest creates new pull request with labels for repository. // NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) { func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
@ -572,7 +513,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
} }
issue.Index = idx issue.Index = idx
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255) issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo, Repo: repo,

View File

@ -639,6 +639,10 @@ func InsertReviews(ctx context.Context, reviews []*Review) error {
return err return err
} }
} }
if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
return err
}
} }
return committer.Commit() return committer.Commit()

View File

@ -76,7 +76,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err)
return x, deferFn return x, deferFn
} }
if err := unittest.LoadFixtures(x); err != nil { if err := unittest.LoadFixtures(); err != nil {
t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err) t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err)
return x, deferFn return x, deferFn
} }

View File

@ -131,7 +131,7 @@ func TestAddOrgUser(t *testing.T) {
testSuccess := func(orgID, userID int64, isPublic bool) { testSuccess := func(orgID, userID int64, isPublic bool) {
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
expectedNumMembers := org.NumMembers expectedNumMembers := org.NumMembers
if !unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) { if unittest.GetBean(t, &organization.OrgUser{OrgID: orgID, UID: userID}) == nil {
expectedNumMembers++ expectedNumMembers++
} }
assert.NoError(t, organization.AddOrgUser(db.DefaultContext, orgID, userID)) assert.NoError(t, organization.AddOrgUser(db.DefaultContext, orgID, userID))

View File

@ -248,6 +248,18 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
return p, nil return p, nil
} }
// UpdatePackageNameByID updates the package's name, it is only for internal usage, for example: rename some legacy packages
func UpdatePackageNameByID(ctx context.Context, ownerID int64, packageType Type, packageID int64, name string) error {
var cond builder.Cond = builder.Eq{
"package.id": packageID,
"package.owner_id": ownerID,
"package.type": packageType,
"package.is_internal": false,
}
_, err := db.GetEngine(ctx).Where(cond).Update(&Package{Name: name, LowerName: strings.ToLower(name)})
return err
}
// GetPackageByName gets a package by name // GetPackageByName gets a package by name
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) { func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
var cond builder.Cond = builder.Eq{ var cond builder.Cond = builder.Eq{

View File

@ -126,6 +126,14 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
return err return err
} }
func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint
return fmt.Sprintf("%s/-/projects/%d", org.HomeLink(), projectID)
}
func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint
return fmt.Sprintf("%s/projects/%d", repo.Link(), projectID)
}
// Link returns the project's relative URL. // Link returns the project's relative URL.
func (p *Project) Link(ctx context.Context) string { func (p *Project) Link(ctx context.Context) string {
if p.OwnerID > 0 { if p.OwnerID > 0 {
@ -134,7 +142,7 @@ func (p *Project) Link(ctx context.Context) string {
log.Error("LoadOwner: %v", err) log.Error("LoadOwner: %v", err)
return "" return ""
} }
return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID) return ProjectLinkForOrg(p.Owner, p.ID)
} }
if p.RepoID > 0 { if p.RepoID > 0 {
err := p.LoadRepo(ctx) err := p.LoadRepo(ctx)
@ -142,7 +150,7 @@ func (p *Project) Link(ctx context.Context) string {
log.Error("LoadRepo: %v", err) log.Error("LoadRepo: %v", err)
return "" return ""
} }
return fmt.Sprintf("%s/projects/%d", p.Repo.Link(), p.ID) return ProjectLinkForRepo(p.Repo, p.ID)
} }
return "" return ""
} }
@ -256,7 +264,7 @@ func NewProject(ctx context.Context, p *Project) error {
return util.NewInvalidArgumentErrorf("project type is not valid") return util.NewInvalidArgumentErrorf("project type is not valid")
} }
p.Title, _ = util.SplitStringAtByteN(p.Title, 255) p.Title = util.EllipsisDisplayString(p.Title, 255)
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
if err := db.Insert(ctx, p); err != nil { if err := db.Insert(ctx, p); err != nil {
@ -311,7 +319,7 @@ func UpdateProject(ctx context.Context, p *Project) error {
p.CardType = CardTypeTextOnly p.CardType = CardTypeTextOnly
} }
p.Title, _ = util.SplitStringAtByteN(p.Title, 255) p.Title = util.EllipsisDisplayString(p.Title, 255)
_, err := db.GetEngine(ctx).ID(p.ID).Cols( _, err := db.GetEngine(ctx).ID(p.ID).Cols(
"title", "title",
"description", "description",

View File

@ -16,6 +16,8 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"xorm.io/builder"
) )
// Init initialize model // Init initialize model
@ -24,7 +26,7 @@ func Init(ctx context.Context) error {
} }
type repoChecker struct { type repoChecker struct {
querySQL func(ctx context.Context) ([]map[string][]byte, error) querySQL func(ctx context.Context) ([]int64, error)
correctSQL func(ctx context.Context, id int64) error correctSQL func(ctx context.Context, id int64) error
desc string desc string
} }
@ -35,8 +37,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
log.Error("Select %s: %v", checker.desc, err) log.Error("Select %s: %v", checker.desc, err)
return return
} }
for _, result := range results { for _, id := range results {
id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id) log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
@ -51,21 +52,23 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
} }
} }
func StatsCorrectSQL(ctx context.Context, sql string, id int64) error { func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
_, err := db.GetEngine(ctx).Exec(sql, id, id) args := []any{sql}
args = append(args, ids...)
_, err := db.GetEngine(ctx).Exec(args...)
return err return err
} }
func repoStatsCorrectNumWatches(ctx context.Context, id int64) error { func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id, id)
} }
func repoStatsCorrectNumStars(ctx context.Context, id int64) error { func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id, id)
} }
func labelStatsCorrectNumIssues(ctx context.Context, id int64) error { func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id, id)
} }
func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
@ -102,11 +105,11 @@ func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
} }
func userStatsCorrectNumRepos(ctx context.Context, id int64) error { func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id, id)
} }
func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error { func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id) return StatsCorrectSQL(ctx, issues_model.UpdateIssueNumCommentsBuilder(id))
} }
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error { func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
@ -125,9 +128,12 @@ func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true) return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
} }
func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) { // statsQuery returns a function that queries the database for a list of IDs
return func(ctx context.Context) ([]map[string][]byte, error) { // sql could be a string or a *builder.Builder
return db.GetEngine(ctx).Query(args...) func statsQuery(sql any, args ...any) func(context.Context) ([]int64, error) {
return func(ctx context.Context) ([]int64, error) {
var ids []int64
return ids, db.GetEngine(ctx).SQL(sql, args...).Find(&ids)
} }
} }
@ -198,7 +204,16 @@ func CheckRepoStats(ctx context.Context) error {
}, },
// Issue.NumComments // Issue.NumComments
{ {
statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"), statsQuery(builder.Select("`issue`.id").From("`issue`").Where(
builder.Neq{
"`issue`.num_comments": builder.Select("COUNT(*)").From("`comment`").Where(
builder.Expr("issue_id = `issue`.id").And(
builder.In("type", issues_model.ConversationCountedCommentType()),
),
),
},
),
),
repoStatsCorrectIssueNumComments, repoStatsCorrectIssueNumComments,
"issue count 'num_comments'", "issue count 'num_comments'",
}, },

View File

@ -156,7 +156,7 @@ func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, er
// UpdateRelease updates all columns of a release // UpdateRelease updates all columns of a release
func UpdateRelease(ctx context.Context, rel *Release) error { func UpdateRelease(ctx context.Context, rel *Release) error {
rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255) rel.Title = util.EllipsisDisplayString(rel.Title, 255)
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel) _, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
return err return err
} }

View File

@ -11,6 +11,7 @@ import (
"net" "net"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -60,13 +61,15 @@ func (err ErrRepoIsArchived) Error() string {
} }
var ( var (
validRepoNamePattern = regexp.MustCompile(`[-.\w]+`)
invalidRepoNamePattern = regexp.MustCompile(`[.]{2,}`)
reservedRepoNames = []string{".", "..", "-"} reservedRepoNames = []string{".", "..", "-"}
reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
) )
// IsUsableRepoName returns true when repository is usable // IsUsableRepoName returns true when name is usable
func IsUsableRepoName(name string) error { func IsUsableRepoName(name string) error {
if db.AlphaDashDotPattern.MatchString(name) { if !validRepoNamePattern.MatchString(name) || invalidRepoNamePattern.MatchString(name) {
// Note: usually this error is normally caught up earlier in the UI // Note: usually this error is normally caught up earlier in the UI
return db.ErrNameCharsNotAllowed{Name: name} return db.ErrNameCharsNotAllowed{Name: name}
} }

View File

@ -217,3 +217,15 @@ func TestComposeSSHCloneURL(t *testing.T) {
setting.SSH.Port = 123 setting.SSH.Port = 123
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
} }
func TestIsUsableRepoName(t *testing.T) {
assert.NoError(t, IsUsableRepoName("a"))
assert.NoError(t, IsUsableRepoName("-1_."))
assert.NoError(t, IsUsableRepoName(".profile"))
assert.Error(t, IsUsableRepoName("-"))
assert.Error(t, IsUsableRepoName("🌞"))
assert.Error(t, IsUsableRepoName("the..repo"))
assert.Error(t, IsUsableRepoName("foo.wiki"))
assert.Error(t, IsUsableRepoName("foo.git"))
}

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -22,3 +23,16 @@ func TestDoctorUserStarNum(t *testing.T) {
assert.NoError(t, DoctorUserStarNum(db.DefaultContext)) assert.NoError(t, DoctorUserStarNum(db.DefaultContext))
} }
func Test_repoStatsCorrectIssueNumComments(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.NotNil(t, issue2)
assert.EqualValues(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here
assert.NoError(t, repoStatsCorrectIssueNumComments(db.DefaultContext, 2))
// reload the issue
issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.EqualValues(t, 1, issue2.NumComments)
}

View File

@ -1,96 +1,32 @@
// Copyright 2021 The Gitea Authors. All rights reserved. // Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//nolint:forbidigo
package unittest package unittest
import ( import (
"fmt" "fmt"
"os"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/go-testfixtures/testfixtures/v3"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
var fixturesLoader *testfixtures.Loader type FixturesLoader interface {
Load() error
}
var fixturesLoader FixturesLoader
// GetXORMEngine gets the XORM engine // GetXORMEngine gets the XORM engine
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) { func GetXORMEngine() (x *xorm.Engine) {
if len(engine) == 1 {
return engine[0]
}
return db.GetEngine(db.DefaultContext).(*xorm.Engine) return db.GetEngine(db.DefaultContext).(*xorm.Engine)
} }
// InitFixtures initialize test fixtures for a test database func loadFixtureResetSeqPgsql(e *xorm.Engine) error {
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
e := GetXORMEngine(engine...)
var fixtureOptionFiles func(*testfixtures.Loader) error
if opts.Dir != "" {
fixtureOptionFiles = testfixtures.Directory(opts.Dir)
} else {
fixtureOptionFiles = testfixtures.Files(opts.Files...)
}
dialect := "unknown"
switch e.Dialect().URI().DBType {
case schemas.POSTGRES:
dialect = "postgres"
case schemas.MYSQL:
dialect = "mysql"
case schemas.MSSQL:
dialect = "mssql"
case schemas.SQLITE:
dialect = "sqlite3"
default:
fmt.Println("Unsupported RDBMS for integration tests")
os.Exit(1)
}
loaderOptions := []func(loader *testfixtures.Loader) error{
testfixtures.Database(e.DB().DB),
testfixtures.Dialect(dialect),
testfixtures.DangerousSkipTestDatabaseCheck(),
fixtureOptionFiles,
}
if e.Dialect().URI().DBType == schemas.POSTGRES {
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
}
fixturesLoader, err = testfixtures.New(loaderOptions...)
if err != nil {
return err
}
// register the dummy hash algorithm function used in the test fixtures
_ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
return err
}
// LoadFixtures load fixtures for a test database
func LoadFixtures(engine ...*xorm.Engine) error {
e := GetXORMEngine(engine...)
var err error
// (doubt) database transaction conflicts could occur and result in ROLLBACK? just try for a few times.
for i := 0; i < 5; i++ {
if err = fixturesLoader.Load(); err == nil {
break
}
time.Sleep(200 * time.Millisecond)
}
if err != nil {
fmt.Printf("LoadFixtures failed after retries: %v\n", err)
}
// Now if we're running postgres we need to tell it to update the sequences
if e.Dialect().URI().DBType == schemas.POSTGRES {
results, err := e.QueryString(`SELECT 'SELECT SETVAL(' || results, err := e.QueryString(`SELECT 'SELECT SETVAL(' ||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) || quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' || ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
@ -108,21 +44,41 @@ func LoadFixtures(engine ...*xorm.Engine) error {
AND T.relname = PGT.tablename AND T.relname = PGT.tablename
ORDER BY S.relname;`) ORDER BY S.relname;`)
if err != nil { if err != nil {
fmt.Printf("Failed to generate sequence update: %v\n", err) return fmt.Errorf("failed to generate sequence update: %w", err)
return err
} }
for _, r := range results { for _, r := range results {
for _, value := range r { for _, value := range r {
_, err = e.Exec(value) _, err = e.Exec(value)
if err != nil { if err != nil {
fmt.Printf("Failed to update sequence: %s Error: %v\n", value, err) return fmt.Errorf("failed to update sequence: %s, error: %w", value, err)
return err
} }
} }
} }
return nil
} }
// InitFixtures initialize test fixtures for a test database
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
xormEngine := util.IfZero(util.OptionalArg(engine), GetXORMEngine())
fixturesLoader, err = NewFixturesLoader(xormEngine, opts)
// fixturesLoader = NewFixturesLoaderVendor(xormEngine, opts)
// register the dummy hash algorithm function used in the test fixtures
_ = hash.Register("dummy", hash.NewDummyHasher) _ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
return err return err
} }
// LoadFixtures load fixtures for a test database
func LoadFixtures() error {
if err := fixturesLoader.Load(); err != nil {
return err
}
// Now if we're running postgres we need to tell it to update the sequences
if GetXORMEngine().Dialect().URI().DBType == schemas.POSTGRES {
if err := loadFixtureResetSeqPgsql(GetXORMEngine()); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,201 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package unittest
import (
"database/sql"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"gopkg.in/yaml.v3"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
type fixtureItem struct {
tableName string
tableNameQuoted string
sqlInserts []string
sqlInsertArgs [][]any
mssqlHasIdentityColumn bool
}
type fixturesLoaderInternal struct {
db *sql.DB
dbType schemas.DBType
files []string
fixtures map[string]*fixtureItem
quoteObject func(string) string
paramPlaceholder func(idx int) string
}
func (f *fixturesLoaderInternal) mssqlTableHasIdentityColumn(db *sql.DB, tableName string) (bool, error) {
row := db.QueryRow(`SELECT COUNT(*) FROM sys.identity_columns WHERE OBJECT_ID = OBJECT_ID(?)`, tableName)
var count int
if err := row.Scan(&count); err != nil {
return false, err
}
return count > 0, nil
}
func (f *fixturesLoaderInternal) preprocessFixtureRow(row []map[string]any) (err error) {
for _, m := range row {
for k, v := range m {
if s, ok := v.(string); ok {
if strings.HasPrefix(s, "0x") {
if m[k], err = hex.DecodeString(s[2:]); err != nil {
return err
}
}
}
}
}
return nil
}
func (f *fixturesLoaderInternal) prepareFixtureItem(file string) (_ *fixtureItem, err error) {
fixture := &fixtureItem{}
fixture.tableName, _, _ = strings.Cut(filepath.Base(file), ".")
fixture.tableNameQuoted = f.quoteObject(fixture.tableName)
if f.dbType == schemas.MSSQL {
fixture.mssqlHasIdentityColumn, err = f.mssqlTableHasIdentityColumn(f.db, fixture.tableName)
if err != nil {
return nil, err
}
}
data, err := os.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("failed to read file %q: %w", file, err)
}
var rows []map[string]any
if err = yaml.Unmarshal(data, &rows); err != nil {
return nil, fmt.Errorf("failed to unmarshal yaml data from %q: %w", file, err)
}
if err = f.preprocessFixtureRow(rows); err != nil {
return nil, fmt.Errorf("failed to preprocess fixture rows from %q: %w", file, err)
}
var sqlBuf []byte
var sqlArguments []any
for _, row := range rows {
sqlBuf = append(sqlBuf, fmt.Sprintf("INSERT INTO %s (", fixture.tableNameQuoted)...)
for k, v := range row {
sqlBuf = append(sqlBuf, f.quoteObject(k)...)
sqlBuf = append(sqlBuf, ","...)
sqlArguments = append(sqlArguments, v)
}
sqlBuf = sqlBuf[:len(sqlBuf)-1]
sqlBuf = append(sqlBuf, ") VALUES ("...)
paramIdx := 1
for range row {
sqlBuf = append(sqlBuf, f.paramPlaceholder(paramIdx)...)
sqlBuf = append(sqlBuf, ',')
paramIdx++
}
sqlBuf[len(sqlBuf)-1] = ')'
fixture.sqlInserts = append(fixture.sqlInserts, string(sqlBuf))
fixture.sqlInsertArgs = append(fixture.sqlInsertArgs, slices.Clone(sqlArguments))
sqlBuf = sqlBuf[:0]
sqlArguments = sqlArguments[:0]
}
return fixture, nil
}
func (f *fixturesLoaderInternal) loadFixtures(tx *sql.Tx, file string) (err error) {
fixture := f.fixtures[file]
if fixture == nil {
if fixture, err = f.prepareFixtureItem(file); err != nil {
return err
}
f.fixtures[file] = fixture
}
_, err = tx.Exec(fmt.Sprintf("DELETE FROM %s", fixture.tableNameQuoted)) // sqlite3 doesn't support truncate
if err != nil {
return err
}
if fixture.mssqlHasIdentityColumn {
_, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", fixture.tableNameQuoted))
if err != nil {
return err
}
defer func() { _, err = tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", fixture.tableNameQuoted)) }()
}
for i := range fixture.sqlInserts {
_, err = tx.Exec(fixture.sqlInserts[i], fixture.sqlInsertArgs[i]...)
}
if err != nil {
return err
}
return nil
}
func (f *fixturesLoaderInternal) Load() error {
tx, err := f.db.Begin()
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
for _, file := range f.files {
if err := f.loadFixtures(tx, file); err != nil {
return fmt.Errorf("failed to load fixtures from %s: %w", file, err)
}
}
return tx.Commit()
}
func FixturesFileFullPaths(dir string, files []string) ([]string, error) {
if files != nil && len(files) == 0 {
return nil, nil // load nothing
}
files = slices.Clone(files)
if len(files) == 0 {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, e := range entries {
files = append(files, e.Name())
}
}
for i, file := range files {
if !filepath.IsAbs(file) {
files[i] = filepath.Join(dir, file)
}
}
return files, nil
}
func NewFixturesLoader(x *xorm.Engine, opts FixturesOptions) (FixturesLoader, error) {
files, err := FixturesFileFullPaths(opts.Dir, opts.Files)
if err != nil {
return nil, fmt.Errorf("failed to get fixtures files: %w", err)
}
f := &fixturesLoaderInternal{db: x.DB().DB, dbType: x.Dialect().URI().DBType, files: files, fixtures: map[string]*fixtureItem{}}
switch f.dbType {
case schemas.SQLITE:
f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
f.paramPlaceholder = func(idx int) string { return "?" }
case schemas.POSTGRES:
f.quoteObject = func(s string) string { return fmt.Sprintf(`"%s"`, s) }
f.paramPlaceholder = func(idx int) string { return fmt.Sprintf(`$%d`, idx) }
case schemas.MYSQL:
f.quoteObject = func(s string) string { return fmt.Sprintf("`%s`", s) }
f.paramPlaceholder = func(idx int) string { return "?" }
case schemas.MSSQL:
f.quoteObject = func(s string) string { return fmt.Sprintf("[%s]", s) }
f.paramPlaceholder = func(idx int) string { return "?" }
}
return f, nil
}

View File

@ -0,0 +1,114 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package unittest_test
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/require"
"xorm.io/xorm"
)
var NewFixturesLoaderVendor = func(e *xorm.Engine, opts unittest.FixturesOptions) (unittest.FixturesLoader, error) {
return nil, nil
}
/*
// the old code is kept here in case we are still interested in benchmarking the two implementations
func init() {
NewFixturesLoaderVendor = func(e *xorm.Engine, opts unittest.FixturesOptions) (unittest.FixturesLoader, error) {
return NewFixturesLoaderVendorGoTestfixtures(e, opts)
}
}
func NewFixturesLoaderVendorGoTestfixtures(e *xorm.Engine, opts unittest.FixturesOptions) (*testfixtures.Loader, error) {
files, err := unittest.FixturesFileFullPaths(opts.Dir, opts.Files)
if err != nil {
return nil, fmt.Errorf("failed to get fixtures files: %w", err)
}
var dialect string
switch e.Dialect().URI().DBType {
case schemas.POSTGRES:
dialect = "postgres"
case schemas.MYSQL:
dialect = "mysql"
case schemas.MSSQL:
dialect = "mssql"
case schemas.SQLITE:
dialect = "sqlite3"
default:
return nil, fmt.Errorf("unsupported RDBMS for integration tests: %q", e.Dialect().URI().DBType)
}
loaderOptions := []func(loader *testfixtures.Loader) error{
testfixtures.Database(e.DB().DB),
testfixtures.Dialect(dialect),
testfixtures.DangerousSkipTestDatabaseCheck(),
testfixtures.Files(files...),
}
if e.Dialect().URI().DBType == schemas.POSTGRES {
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
}
return testfixtures.New(loaderOptions...)
}
*/
func prepareTestFixturesLoaders(t testing.TB) unittest.FixturesOptions {
_ = user_model.User{}
opts := unittest.FixturesOptions{Dir: filepath.Join(test.SetupGiteaRoot(), "models", "fixtures"), Files: []string{
"user.yml",
}}
require.NoError(t, unittest.CreateTestEngine(opts))
return opts
}
func TestFixturesLoader(t *testing.T) {
opts := prepareTestFixturesLoaders(t)
loaderInternal, err := unittest.NewFixturesLoader(unittest.GetXORMEngine(), opts)
require.NoError(t, err)
loaderVendor, err := NewFixturesLoaderVendor(unittest.GetXORMEngine(), opts)
require.NoError(t, err)
t.Run("Internal", func(t *testing.T) {
require.NoError(t, loaderInternal.Load())
require.NoError(t, loaderInternal.Load())
})
t.Run("Vendor", func(t *testing.T) {
if loaderVendor == nil {
t.Skip()
}
require.NoError(t, loaderVendor.Load())
require.NoError(t, loaderVendor.Load())
})
}
func BenchmarkFixturesLoader(b *testing.B) {
opts := prepareTestFixturesLoaders(b)
require.NoError(b, unittest.CreateTestEngine(opts))
loaderInternal, err := unittest.NewFixturesLoader(unittest.GetXORMEngine(), opts)
require.NoError(b, err)
loaderVendor, err := NewFixturesLoaderVendor(unittest.GetXORMEngine(), opts)
require.NoError(b, err)
// BenchmarkFixturesLoader/Vendor
// BenchmarkFixturesLoader/Vendor-12 1696 719416 ns/op
// BenchmarkFixturesLoader/Internal
// BenchmarkFixturesLoader/Internal-12 1746 670457 ns/op
b.Run("Internal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
require.NoError(b, loaderInternal.Load())
}
})
b.Run("Vendor", func(b *testing.B) {
if loaderVendor == nil {
b.Skip()
}
for i := 0; i < b.N; i++ {
require.NoError(b, loaderVendor.Load())
}
})
}

View File

@ -67,7 +67,7 @@ func SyncDirs(srcPath, destPath string) error {
} }
// find and delete all untracked files // find and delete all untracked files
destFiles, err := util.StatDir(destPath, true) destFiles, err := util.ListDirRecursively(destPath, &util.ListDirOptions{IncludeDir: true})
if err != nil { if err != nil {
return err return err
} }
@ -86,13 +86,13 @@ func SyncDirs(srcPath, destPath string) error {
} }
// sync src files to dest // sync src files to dest
srcFiles, err := util.StatDir(srcPath, true) srcFiles, err := util.ListDirRecursively(srcPath, &util.ListDirOptions{IncludeDir: true})
if err != nil { if err != nil {
return err return err
} }
for _, srcFile := range srcFiles { for _, srcFile := range srcFiles {
destFilePath := filepath.Join(destPath, srcFile) destFilePath := filepath.Join(destPath, srcFile)
// util.StatDir appends a slash to the directory name // util.ListDirRecursively appends a slash to the directory name
if strings.HasSuffix(srcFile, "/") { if strings.HasSuffix(srcFile, "/") {
err = os.MkdirAll(destFilePath, os.ModePerm) err = os.MkdirAll(destFilePath, os.ModePerm)
} else { } else {

View File

@ -4,7 +4,7 @@
package unittest package unittest
import ( import (
"log" "fmt"
"reflect" "reflect"
) )
@ -14,7 +14,7 @@ func fieldByName(v reflect.Value, field string) reflect.Value {
} }
f := v.FieldByName(field) f := v.FieldByName(field)
if !f.IsValid() { if !f.IsValid() {
log.Panicf("can not read %s for %v", field, v) panic(fmt.Errorf("can not read %s for %v", field, v))
} }
return f return f
} }

View File

@ -28,16 +28,7 @@ import (
"xorm.io/xorm/names" "xorm.io/xorm/names"
) )
// giteaRoot a path to the gitea root var giteaRoot string
var (
giteaRoot string
fixturesDir string
)
// FixturesDir returns the fixture directory
func FixturesDir() string {
return fixturesDir
}
func fatalTestError(fmtStr string, args ...any) { func fatalTestError(fmtStr string, args ...any) {
_, _ = fmt.Fprintf(os.Stderr, fmtStr, args...) _, _ = fmt.Fprintf(os.Stderr, fmtStr, args...)
@ -68,6 +59,7 @@ func InitSettings() {
_ = hash.Register("dummy", hash.NewDummyHasher) _ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
setting.InitGiteaEnvVarsForTesting()
} }
// TestOptions represents test options // TestOptions represents test options
@ -79,39 +71,14 @@ type TestOptions struct {
// MainTest a reusable TestMain(..) function for unit tests that need to use a // MainTest a reusable TestMain(..) function for unit tests that need to use a
// test database. Creates the test database, and sets necessary settings. // test database. Creates the test database, and sets necessary settings.
func MainTest(m *testing.M, testOpts ...*TestOptions) { func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
searchDir, _ := os.Getwd() testOpts := util.OptionalArg(testOptsArg, &TestOptions{})
for searchDir != "" { giteaRoot = test.SetupGiteaRoot()
if _, err := os.Stat(filepath.Join(searchDir, "go.mod")); err == nil {
break // The "go.mod" should be the one for Gitea repository
}
if dir := filepath.Dir(searchDir); dir == searchDir {
searchDir = "" // reaches the root of filesystem
} else {
searchDir = dir
}
}
if searchDir == "" {
panic("The tests should run in a Gitea repository, there should be a 'go.mod' in the root")
}
giteaRoot = searchDir
setting.CustomPath = filepath.Join(giteaRoot, "custom") setting.CustomPath = filepath.Join(giteaRoot, "custom")
InitSettings() InitSettings()
fixturesDir = filepath.Join(giteaRoot, "models", "fixtures") fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles}
var opts FixturesOptions if err := CreateTestEngine(fixturesOpts); err != nil {
if len(testOpts) == 0 || len(testOpts[0].FixtureFiles) == 0 {
opts.Dir = fixturesDir
} else {
for _, f := range testOpts[0].FixtureFiles {
if len(f) != 0 {
opts.Files = append(opts.Files, filepath.Join(fixturesDir, f))
}
}
}
if err := CreateTestEngine(opts); err != nil {
fatalTestError("Error creating test engine: %v\n", err) fatalTestError("Error creating test engine: %v\n", err)
} }
@ -172,16 +139,16 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
fatalTestError("git.Init: %v\n", err) fatalTestError("git.Init: %v\n", err)
} }
if len(testOpts) > 0 && testOpts[0].SetUp != nil { if testOpts.SetUp != nil {
if err := testOpts[0].SetUp(); err != nil { if err := testOpts.SetUp(); err != nil {
fatalTestError("set up failed: %v\n", err) fatalTestError("set up failed: %v\n", err)
} }
} }
exitStatus := m.Run() exitStatus := m.Run()
if len(testOpts) > 0 && testOpts[0].TearDown != nil { if testOpts.TearDown != nil {
if err := testOpts[0].TearDown(); err != nil { if err := testOpts.TearDown(); err != nil {
fatalTestError("tear down failed: %v\n", err) fatalTestError("tear down failed: %v\n", err)
} }
} }

View File

@ -4,13 +4,17 @@
package unittest package unittest
import ( import (
"fmt"
"math" "math"
"os"
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm"
) )
// Code in this file is mainly used by unittest.CheckConsistencyFor, which is not in the unit test for various reasons. // Code in this file is mainly used by unittest.CheckConsistencyFor, which is not in the unit test for various reasons.
@ -51,22 +55,23 @@ func whereOrderConditions(e db.Engine, conditions []any) db.Engine {
return e.OrderBy(orderBy) return e.OrderBy(orderBy)
} }
// LoadBeanIfExists loads beans from fixture database if exist func getBeanIfExists(bean any, conditions ...any) (bool, error) {
func LoadBeanIfExists(bean any, conditions ...any) (bool, error) {
e := db.GetEngine(db.DefaultContext) e := db.GetEngine(db.DefaultContext)
return whereOrderConditions(e, conditions).Get(bean) return whereOrderConditions(e, conditions).Get(bean)
} }
// BeanExists for testing, check if a bean exists func GetBean[T any](t require.TestingT, bean T, conditions ...any) (ret T) {
func BeanExists(t assert.TestingT, bean any, conditions ...any) bool { exists, err := getBeanIfExists(bean, conditions...)
exists, err := LoadBeanIfExists(bean, conditions...) require.NoError(t, err)
assert.NoError(t, err) if exists {
return exists return bean
}
return ret
} }
// AssertExistsAndLoadBean assert that a bean exists and load it from the test database // AssertExistsAndLoadBean assert that a bean exists and load it from the test database
func AssertExistsAndLoadBean[T any](t require.TestingT, bean T, conditions ...any) T { func AssertExistsAndLoadBean[T any](t require.TestingT, bean T, conditions ...any) T {
exists, err := LoadBeanIfExists(bean, conditions...) exists, err := getBeanIfExists(bean, conditions...)
require.NoError(t, err) require.NoError(t, err)
require.True(t, exists, require.True(t, exists,
"Expected to find %+v (of type %T, with conditions %+v), but did not", "Expected to find %+v (of type %T, with conditions %+v), but did not",
@ -112,25 +117,11 @@ func GetCount(t assert.TestingT, bean any, conditions ...any) int {
// AssertNotExistsBean assert that a bean does not exist in the test database // AssertNotExistsBean assert that a bean does not exist in the test database
func AssertNotExistsBean(t assert.TestingT, bean any, conditions ...any) { func AssertNotExistsBean(t assert.TestingT, bean any, conditions ...any) {
exists, err := LoadBeanIfExists(bean, conditions...) exists, err := getBeanIfExists(bean, conditions...)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, exists) assert.False(t, exists)
} }
// AssertExistsIf asserts that a bean exists or does not exist, depending on
// what is expected.
func AssertExistsIf(t assert.TestingT, expected bool, bean any, conditions ...any) {
exists, err := LoadBeanIfExists(bean, conditions...)
assert.NoError(t, err)
assert.Equal(t, expected, exists)
}
// AssertSuccessfulInsert assert that beans is successfully inserted
func AssertSuccessfulInsert(t assert.TestingT, beans ...any) {
err := db.Insert(db.DefaultContext, beans...)
assert.NoError(t, err)
}
// AssertCount assert the count of a bean // AssertCount assert the count of a bean
func AssertCount(t assert.TestingT, bean, expected any) bool { func AssertCount(t assert.TestingT, bean, expected any) bool {
return assert.EqualValues(t, expected, GetCount(t, bean)) return assert.EqualValues(t, expected, GetCount(t, bean))
@ -155,3 +146,39 @@ func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, e
return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond), return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
"Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond) "Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond)
} }
// DumpQueryResult dumps the result of a query for debugging purpose
func DumpQueryResult(t require.TestingT, sqlOrBean any, sqlArgs ...any) {
x := db.GetEngine(db.DefaultContext).(*xorm.Engine)
goDB := x.DB().DB
sql, ok := sqlOrBean.(string)
if !ok {
sql = fmt.Sprintf("SELECT * FROM %s", db.TableName(sqlOrBean))
} else if !strings.Contains(sql, " ") {
sql = fmt.Sprintf("SELECT * FROM %s", sql)
}
rows, err := goDB.Query(sql, sqlArgs...)
require.NoError(t, err)
defer rows.Close()
columns, err := rows.Columns()
require.NoError(t, err)
_, _ = fmt.Fprintf(os.Stdout, "====== DumpQueryResult: %s ======\n", sql)
idx := 0
for rows.Next() {
row := make([]any, len(columns))
rowPointers := make([]any, len(columns))
for i := range row {
rowPointers[i] = &row[i]
}
require.NoError(t, rows.Scan(rowPointers...))
_, _ = fmt.Fprintf(os.Stdout, "- # row[%d]\n", idx)
for i, col := range columns {
_, _ = fmt.Fprintf(os.Stdout, " %s: %v\n", col, row[i])
}
idx++
}
if idx == 0 {
_, _ = fmt.Fprintf(os.Stdout, "(no result, columns: %s)\n", strings.Join(columns, ", "))
}
}

View File

@ -357,8 +357,8 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
if user := GetVerifyUser(ctx, code); user != nil { if user := GetVerifyUser(ctx, code); user != nil {
// time limit code // time limit code
prefix := code[:base.TimeLimitCodeLength] prefix := code[:base.TimeLimitCodeLength]
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands) opts := &TimeLimitCodeOptions{Purpose: TimeLimitCodeActivateEmail, NewEmail: email}
data := makeTimeLimitCodeHashData(opts, user)
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) { if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
emailAddress := &EmailAddress{UID: user.ID, Email: email} emailAddress := &EmailAddress{UID: user.ID, Email: email}
if has, _ := db.GetEngine(ctx).Get(emailAddress); has { if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
@ -486,10 +486,10 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
// Activate/deactivate a user's primary email address and account // Activate/deactivate a user's primary email address and account
if addr.IsPrimary { if addr.IsPrimary {
user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID, "email": email}) user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
if err != nil { if err != nil {
return err return err
} else if !exist { } else if !exist || !strings.EqualFold(user.Email, email) {
return fmt.Errorf("no user with ID: %d and Email: %s", userID, email) return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
} }

View File

@ -39,8 +39,6 @@ type SearchUserOptions struct {
IsTwoFactorEnabled optional.Option[bool] IsTwoFactorEnabled optional.Option[bool]
IsProhibitLogin optional.Option[bool] IsProhibitLogin optional.Option[bool]
IncludeReserved bool IncludeReserved bool
ExtraParamStrings map[string]string
} }
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session { func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {

View File

@ -181,7 +181,8 @@ func (u *User) BeforeUpdate() {
u.MaxRepoCreation = -1 u.MaxRepoCreation = -1
} }
// Organization does not need email // FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field
// This trick could be removed in new releases to display the user inputed email as-is.
u.Email = strings.ToLower(u.Email) u.Email = strings.ToLower(u.Email)
if !u.IsOrganization() { if !u.IsOrganization() {
if len(u.AvatarEmail) == 0 { if len(u.AvatarEmail) == 0 {
@ -190,9 +191,9 @@ func (u *User) BeforeUpdate() {
} }
u.LowerName = strings.ToLower(u.Name) u.LowerName = strings.ToLower(u.Name)
u.Location = base.TruncateString(u.Location, 255) u.Location = util.TruncateRunes(u.Location, 255)
u.Website = base.TruncateString(u.Website, 255) u.Website = util.TruncateRunes(u.Website, 255)
u.Description = base.TruncateString(u.Description, 255) u.Description = util.TruncateRunes(u.Description, 255)
} }
// AfterLoad is invoked from XORM after filling all the fields of this object. // AfterLoad is invoked from XORM after filling all the fields of this object.
@ -310,17 +311,6 @@ func (u *User) OrganisationLink() string {
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name) return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
} }
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
func (u *User) GenerateEmailActivateCode(email string) string {
code := base.CreateTimeLimitCode(
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
setting.Service.ActiveCodeLives, time.Now(), nil)
// Add tail hex username
code += hex.EncodeToString([]byte(u.LowerName))
return code
}
// GetUserFollowers returns range of user's followers. // GetUserFollowers returns range of user's followers.
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
@ -501,9 +491,9 @@ func (u *User) GitName() string {
// ShortName ellipses username to length // ShortName ellipses username to length
func (u *User) ShortName(length int) string { func (u *User) ShortName(length int) string {
if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
return base.EllipsisString(u.FullName, length) return util.EllipsisDisplayString(u.FullName, length)
} }
return base.EllipsisString(u.Name, length) return util.EllipsisDisplayString(u.Name, length)
} }
// IsMailable checks if a user is eligible // IsMailable checks if a user is eligible
@ -863,12 +853,38 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
return nil return nil
} }
// VerifyUserActiveCode verifies active code when active account type TimeLimitCodePurpose string
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
const (
TimeLimitCodeActivateAccount TimeLimitCodePurpose = "activate_account"
TimeLimitCodeActivateEmail TimeLimitCodePurpose = "activate_email"
TimeLimitCodeResetPassword TimeLimitCodePurpose = "reset_password"
)
type TimeLimitCodeOptions struct {
Purpose TimeLimitCodePurpose
NewEmail string
}
func makeTimeLimitCodeHashData(opts *TimeLimitCodeOptions, u *User) string {
return fmt.Sprintf("%s|%d|%s|%s|%s|%s", opts.Purpose, u.ID, strings.ToLower(util.IfZero(opts.NewEmail, u.Email)), u.LowerName, u.Passwd, u.Rands)
}
// GenerateUserTimeLimitCode generates a time-limit code based on user information and given e-mail.
// TODO: need to use cache or db to store it to make sure a code can only be consumed once
func GenerateUserTimeLimitCode(opts *TimeLimitCodeOptions, u *User) string {
data := makeTimeLimitCodeHashData(opts, u)
code := base.CreateTimeLimitCode(data, setting.Service.ActiveCodeLives, time.Now(), nil)
code += hex.EncodeToString([]byte(u.LowerName)) // Add tail hex username
return code
}
// VerifyUserTimeLimitCode verifies the time-limit code
func VerifyUserTimeLimitCode(ctx context.Context, opts *TimeLimitCodeOptions, code string) (user *User) {
if user = GetVerifyUser(ctx, code); user != nil { if user = GetVerifyUser(ctx, code); user != nil {
// time limit code // time limit code
prefix := code[:base.TimeLimitCodeLength] prefix := code[:base.TimeLimitCodeLength]
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands) data := makeTimeLimitCodeHashData(opts, user)
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) { if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
return user return user
} }

View File

@ -103,7 +103,7 @@ func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) {
} }
func shouldInclude(info fs.FileInfo, fileMode ...bool) bool { func shouldInclude(info fs.FileInfo, fileMode ...bool) bool {
if util.CommonSkip(info.Name()) { if util.IsCommonHiddenFileName(info.Name()) {
return false return false
} }
if len(fileMode) == 0 { if len(fileMode) == 0 {

View File

@ -16,11 +16,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
) )
@ -35,7 +35,7 @@ func EncodeSha256(str string) string {
// ShortSha is basically just truncating. // ShortSha is basically just truncating.
// It is DEPRECATED and will be removed in the future. // It is DEPRECATED and will be removed in the future.
func ShortSha(sha1 string) string { func ShortSha(sha1 string) string {
return TruncateString(sha1, 10) return util.TruncateRunes(sha1, 10)
} }
// BasicAuthDecode decode basic auth string // BasicAuthDecode decode basic auth string
@ -116,27 +116,6 @@ func FileSize(s int64) string {
return humanize.IBytes(uint64(s)) return humanize.IBytes(uint64(s))
} }
// EllipsisString returns a truncated short string,
// it appends '...' in the end of the length of string is too large.
func EllipsisString(str string, length int) string {
if length <= 3 {
return "..."
}
if utf8.RuneCountInString(str) <= length {
return str
}
return string([]rune(str)[:length-3]) + "..."
}
// TruncateString returns a truncated string with given limit,
// it returns input string if length is not reached limit.
func TruncateString(str string, limit int) string {
if utf8.RuneCountInString(str) < limit {
return str
}
return string([]rune(str)[:limit])
}
// StringsToInt64s converts a slice of string to a slice of int64. // StringsToInt64s converts a slice of string to a slice of int64.
func StringsToInt64s(strs []string) ([]int64, error) { func StringsToInt64s(strs []string) ([]int64, error) {
if strs == nil { if strs == nil {

View File

@ -113,36 +113,6 @@ func TestFileSize(t *testing.T) {
assert.Equal(t, "2.0 EiB", FileSize(size)) assert.Equal(t, "2.0 EiB", FileSize(size))
} }
func TestEllipsisString(t *testing.T) {
assert.Equal(t, "...", EllipsisString("foobar", 0))
assert.Equal(t, "...", EllipsisString("foobar", 1))
assert.Equal(t, "...", EllipsisString("foobar", 2))
assert.Equal(t, "...", EllipsisString("foobar", 3))
assert.Equal(t, "f...", EllipsisString("foobar", 4))
assert.Equal(t, "fo...", EllipsisString("foobar", 5))
assert.Equal(t, "foobar", EllipsisString("foobar", 6))
assert.Equal(t, "foobar", EllipsisString("foobar", 10))
assert.Equal(t, "测...", EllipsisString("测试文本一二三四", 4))
assert.Equal(t, "测试...", EllipsisString("测试文本一二三四", 5))
assert.Equal(t, "测试文...", EllipsisString("测试文本一二三四", 6))
assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10))
}
func TestTruncateString(t *testing.T) {
assert.Equal(t, "", TruncateString("foobar", 0))
assert.Equal(t, "f", TruncateString("foobar", 1))
assert.Equal(t, "fo", TruncateString("foobar", 2))
assert.Equal(t, "foo", TruncateString("foobar", 3))
assert.Equal(t, "foob", TruncateString("foobar", 4))
assert.Equal(t, "fooba", TruncateString("foobar", 5))
assert.Equal(t, "foobar", TruncateString("foobar", 6))
assert.Equal(t, "foobar", TruncateString("foobar", 7))
assert.Equal(t, "测试文本", TruncateString("测试文本一二三四", 4))
assert.Equal(t, "测试文本一", TruncateString("测试文本一二三四", 5))
assert.Equal(t, "测试文本一二", TruncateString("测试文本一二三四", 6))
assert.Equal(t, "测试文本一二三", TruncateString("测试文本一二三四", 7))
}
func TestStringsToInt64s(t *testing.T) { func TestStringsToInt64s(t *testing.T) {
testSuccess := func(input []string, expected []int64) { testSuccess := func(input []string, expected []int64) {
result, err := StringsToInt64s(input) result, err := StringsToInt64s(input)

View File

@ -242,7 +242,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
return out return out
} }
// ParseTreeLine reads an entry from a tree in a cat-file --batch stream // ParseCatFileTreeLine reads an entry from a tree in a cat-file --batch stream
// This carefully avoids allocations - except where fnameBuf is too small. // This carefully avoids allocations - except where fnameBuf is too small.
// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations // It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
// //
@ -250,7 +250,7 @@ func BinToHex(objectFormat ObjectFormat, sha, out []byte) []byte {
// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH> // <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <binary HASH>
// //
// We don't attempt to convert the raw HASH to save a lot of time // We don't attempt to convert the raw HASH to save a lot of time
func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) { func ParseCatFileTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
var readBytes []byte var readBytes []byte
// Read the Mode & fname // Read the Mode & fname
@ -260,7 +260,7 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu
} }
idx := bytes.IndexByte(readBytes, ' ') idx := bytes.IndexByte(readBytes, ' ')
if idx < 0 { if idx < 0 {
log.Debug("missing space in readBytes ParseTreeLine: %s", readBytes) log.Debug("missing space in readBytes ParseCatFileTreeLine: %s", readBytes)
return mode, fname, sha, n, &ErrNotExist{} return mode, fname, sha, n, &ErrNotExist{}
} }

View File

@ -236,10 +236,16 @@ type RunOpts struct {
} }
func commonBaseEnvs() []string { func commonBaseEnvs() []string {
// at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
envs := []string{ envs := []string{
"HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config // Make Gitea use internal git config only, to prevent conflicts with user's git config
"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace) // It's better to use GIT_CONFIG_GLOBAL, but it requires git >= 2.32, so we still use HOME at the moment.
"HOME=" + HomeDir(),
// Avoid using system git config, it would cause problems (eg: use macOS osxkeychain to show a modal dialog, auto installing lfs hooks)
// This might be a breaking change in 1.24, because some users said that they have put some configs like "receive.certNonceSeed" in "/etc/gitconfig"
// For these users, they need to migrate the necessary configs to Gitea's git config file manually.
"GIT_CONFIG_NOSYSTEM=1",
// Ignore replace references (https://git-scm.com/docs/git-replace)
"GIT_NO_REPLACE_OBJECTS=1",
} }
// some environment variables should be passed to git command // some environment variables should be passed to git command

78
modules/git/parse.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"bytes"
"fmt"
"strconv"
"strings"
"code.gitea.io/gitea/modules/optional"
)
var sepSpace = []byte{' '}
type LsTreeEntry struct {
ID ObjectID
EntryMode EntryMode
Name string
Size optional.Option[int64]
}
func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
// expect line to be of the form:
// <mode> <type> <sha> <space-padded-size>\t<filename>
// <mode> <type> <sha>\t<filename>
var err error
posTab := bytes.IndexByte(line, '\t')
if posTab == -1 {
return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
}
entry := new(LsTreeEntry)
entryAttrs := line[:posTab]
entryName := line[posTab+1:]
entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
if len(entryAttrs) > 0 {
entrySize := entryAttrs // the last field is the space-padded-size
size, _ := strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
entry.Size = optional.Some(size)
}
switch string(entryMode) {
case "100644":
entry.EntryMode = EntryModeBlob
case "100755":
entry.EntryMode = EntryModeExec
case "120000":
entry.EntryMode = EntryModeSymlink
case "160000":
entry.EntryMode = EntryModeCommit
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
entry.EntryMode = EntryModeTree
default:
return nil, fmt.Errorf("unknown type: %v", string(entryMode))
}
entry.ID, err = NewIDFromString(string(entryObjectID))
if err != nil {
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
}
if len(entryName) > 0 && entryName[0] == '"' {
entry.Name, err = strconv.Unquote(string(entryName))
if err != nil {
return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
}
} else {
entry.Name = string(entryName)
}
return entry, nil
}

View File

@ -10,8 +10,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
@ -21,71 +19,30 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil) return parseTreeEntries(data, nil)
} }
var sepSpace = []byte{' '} // parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
var err error
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
for pos := 0; pos < len(data); { for pos := 0; pos < len(data); {
// expect line to be of the form:
// <mode> <type> <sha> <space-padded-size>\t<filename>
// <mode> <type> <sha>\t<filename>
posEnd := bytes.IndexByte(data[pos:], '\n') posEnd := bytes.IndexByte(data[pos:], '\n')
if posEnd == -1 { if posEnd == -1 {
posEnd = len(data) posEnd = len(data)
} else { } else {
posEnd += pos posEnd += pos
} }
line := data[pos:posEnd] line := data[pos:posEnd]
posTab := bytes.IndexByte(line, '\t') lsTreeLine, err := parseLsTreeLine(line)
if posTab == -1 {
return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
}
entry := new(TreeEntry)
entry.ptree = ptree
entryAttrs := line[:posTab]
entryName := line[posTab+1:]
entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
if len(entryAttrs) > 0 {
entrySize := entryAttrs // the last field is the space-padded-size
entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
entry.sized = true
}
switch string(entryMode) {
case "100644":
entry.entryMode = EntryModeBlob
case "100755":
entry.entryMode = EntryModeExec
case "120000":
entry.entryMode = EntryModeSymlink
case "160000":
entry.entryMode = EntryModeCommit
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
entry.entryMode = EntryModeTree
default:
return nil, fmt.Errorf("unknown type: %v", string(entryMode))
}
entry.ID, err = NewIDFromString(string(entryObjectID))
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err) return nil, err
} }
entry := &TreeEntry{
if len(entryName) > 0 && entryName[0] == '"' { ptree: ptree,
entry.name, err = strconv.Unquote(string(entryName)) ID: lsTreeLine.ID,
if err != nil { entryMode: lsTreeLine.EntryMode,
return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err) name: lsTreeLine.Name,
size: lsTreeLine.Size.Value(),
sized: lsTreeLine.Size.Has(),
} }
} else {
entry.name = string(entryName)
}
pos = posEnd + 1 pos = posEnd + 1
entries = append(entries, entry) entries = append(entries, entry)
} }
@ -100,7 +57,7 @@ func catBatchParseTreeEntries(objectFormat ObjectFormat, ptree *Tree, rd *bufio.
loop: loop:
for sz > 0 { for sz > 0 {
mode, fname, sha, count, err := ParseTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf) mode, fname, sha, count, err := ParseCatFileTreeLine(objectFormat, rd, modeBuf, fnameBuf, shaBuf)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break loop break loop

View File

@ -114,7 +114,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
case "tree": case "tree":
var n int64 var n int64
for n < size { for n < size {
mode, fname, binObjectID, count, err := git.ParseTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf) mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,7 +8,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@ -63,15 +62,11 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
cmd.AddOptionFormat("--format=%s", format.String()) cmd.AddOptionFormat("--format=%s", format.String())
cmd.AddDynamicArguments(commitID) cmd.AddDynamicArguments(commitID)
// Avoid LFS hooks getting installed because of /etc/gitconfig, which can break pull requests.
env := append(os.Environ(), "GIT_CONFIG_NOSYSTEM=1")
var stderr strings.Builder var stderr strings.Builder
err := cmd.Run(&RunOpts{ err := cmd.Run(&RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: target, Stdout: target,
Stderr: &stderr, Stderr: &stderr,
Env: env,
}) })
if err != nil { if err != nil {
return ConcatenateError(err, stderr.String()) return ConcatenateError(err, stderr.String())

66
modules/git/submodule.go Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"bufio"
"context"
"fmt"
"os"
"code.gitea.io/gitea/modules/log"
)
type TemplateSubmoduleCommit struct {
Path string
Commit string
}
// GetTemplateSubmoduleCommits returns a list of submodules paths and their commits from a repository
// This function is only for generating new repos based on existing template, the template couldn't be too large.
func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submoduleCommits []TemplateSubmoduleCommit, _ error) {
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
opts := &RunOpts{
Dir: repoPath,
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
defer stdoutReader.Close()
scanner := bufio.NewScanner(stdoutReader)
for scanner.Scan() {
entry, err := parseLsTreeLine(scanner.Bytes())
if err != nil {
cancel()
return err
}
if entry.EntryMode == EntryModeCommit {
submoduleCommits = append(submoduleCommits, TemplateSubmoduleCommit{Path: entry.Name, Commit: entry.ID.String()})
}
}
return scanner.Err()
},
}
err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts)
if err != nil {
return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err)
}
return submoduleCommits, nil
}
// AddTemplateSubmoduleIndexes Adds the given submodules to the git index.
// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir.
func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error {
for _, submodule := range submodules {
cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path)
if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil {
log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err)
return err
}
}
return nil
}

View File

@ -0,0 +1,48 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetTemplateSubmoduleCommits(t *testing.T) {
testRepoPath := filepath.Join(testReposDir, "repo4_submodules")
submodules, err := GetTemplateSubmoduleCommits(DefaultContext, testRepoPath)
require.NoError(t, err)
assert.Len(t, submodules, 2)
assert.EqualValues(t, "<°)))><", submodules[0].Path)
assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit)
assert.EqualValues(t, "libtest", submodules[1].Path)
assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit)
}
func TestAddTemplateSubmoduleIndexes(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
var err error
_, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir})
require.NoError(t, err)
_ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755)
err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}})
require.NoError(t, err)
_, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir})
require.NoError(t, err)
_, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir})
require.NoError(t, err)
submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir)
require.NoError(t, err)
assert.Len(t, submodules, 1)
assert.EqualValues(t, "new-dir", submodules[0].Path)
assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit)
}

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View File

@ -0,0 +1,2 @@
x<01><>[
Β0EύΞ*ζ_<CEB6>ι$MΡ5tifBk IΕ•Ή7ζk~ήΓ9ά<39>—εά ό¦π.jΦΘ Ε δΙ"zΒ`ί#I<>irF…µΝΉΐΨ$%ΉΒης|4)°―?tΌΙ=”Λ:K¦ο­#[$DΏ―ϋΏ^<5E><>…΅®Σy½HU/<2F>f?G

View File

@ -0,0 +1 @@
e1e59caba97193d48862d6809912043871f37437

View File

@ -62,3 +62,14 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
return filelist, err return filelist, err
} }
// GetTreePathLatestCommit returns the latest commit of a tree path
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
AddDynamicArguments(refName).AddDashesAndList(treePath).
RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return repo.GetCommit(strings.TrimSpace(stdout))
}

View File

@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
ptree: t, ptree: t,
ID: t.ID, ID: t.ID,
name: "", name: "",
fullName: "",
entryMode: EntryModeTree, entryMode: EntryModeTree,
}, nil }, nil
} }

View File

@ -10,22 +10,16 @@ import "code.gitea.io/gitea/modules/log"
// TreeEntry the leaf in the git tree // TreeEntry the leaf in the git tree
type TreeEntry struct { type TreeEntry struct {
ID ObjectID ID ObjectID
ptree *Tree ptree *Tree
entryMode EntryMode entryMode EntryMode
name string name string
size int64 size int64
sized bool sized bool
fullName string
} }
// Name returns the name of the entry // Name returns the name of the entry
func (te *TreeEntry) Name() string { func (te *TreeEntry) Name() string {
if te.fullName != "" {
return te.fullName
}
return te.name return te.name
} }

View File

@ -25,3 +25,18 @@ func TestSubTree_Issue29101(t *testing.T) {
assert.True(t, IsErrNotExist(err)) assert.True(t, IsErrNotExist(err))
} }
} }
func Test_GetTreePathLatestCommit(t *testing.T) {
repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo6_blame"))
assert.NoError(t, err)
defer repo.Close()
commitID, err := repo.GetBranchCommitID("master")
assert.NoError(t, err)
assert.EqualValues(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID)
commit, err := repo.GetTreePathLatestCommit("master", "blame.txt")
assert.NoError(t, err)
assert.NotNil(t, commit)
assert.EqualValues(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String())
}

View File

@ -43,19 +43,20 @@ type contextKey struct {
} }
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it // RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
// The caller must call "defer gitRepo.Close()"
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) { func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
ds := reqctx.GetRequestDataStore(ctx) reqCtx := reqctx.FromContext(ctx)
if ds != nil { if reqCtx != nil {
gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo) gitRepo, err := RepositoryFromRequestContextOrOpen(reqCtx, repo)
return gitRepo, util.NopCloser{}, err return gitRepo, util.NopCloser{}, err
} }
gitRepo, err := OpenRepository(ctx, repo) gitRepo, err := OpenRepository(ctx, repo)
return gitRepo, gitRepo, err return gitRepo, gitRepo, err
} }
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context // RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
// The repo will be automatically closed when the request context is done // Caller shouldn't close the git repo manually, the git repo will be automatically closed when the request context is done.
func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) { func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
ck := contextKey{repoPath: repoPath(repo)} ck := contextKey{repoPath: repoPath(repo)}
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok { if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
return gitRepo, nil return gitRepo, nil
@ -64,7 +65,7 @@ func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDa
if err != nil { if err != nil {
return nil, err return nil, err
} }
ds.AddCloser(gitRepo) ctx.AddCloser(gitRepo)
ds.SetContextValue(ck, gitRepo) ctx.SetContextValue(ck, gitRepo)
return gitRepo, nil return gitRepo, nil
} }

View File

@ -123,13 +123,12 @@ func Init() {
for _, indexerData := range items { for _, indexerData := range items {
log.Trace("IndexerData Process Repo: %d", indexerData.RepoID) log.Trace("IndexerData Process Repo: %d", indexerData.RepoID)
if err := index(ctx, indexer, indexerData.RepoID); err != nil { if err := index(ctx, indexer, indexerData.RepoID); err != nil {
unhandled = append(unhandled, indexerData)
if !setting.IsInTesting { if !setting.IsInTesting {
log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err) log.Error("Codes indexer handler: index error for repo %v: %v", indexerData.RepoID, err)
} }
} }
} }
return unhandled return nil // do not re-queue the failed items, otherwise some broken repo will block the queue
} }
indexerQueue = queue.CreateUniqueQueue(ctx, "code_indexer", handler) indexerQueue = queue.CreateUniqueQueue(ctx, "code_indexer", handler)

View File

@ -15,6 +15,8 @@ import (
"code.gitea.io/gitea/modules/indexer/code/bleve" "code.gitea.io/gitea/modules/indexer/code/bleve"
"code.gitea.io/gitea/modules/indexer/code/elasticsearch" "code.gitea.io/gitea/modules/indexer/code/elasticsearch"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
_ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models"
_ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/actions"
@ -279,7 +281,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
func TestBleveIndexAndSearch(t *testing.T) { func TestBleveIndexAndSearch(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
dir := t.TempDir() dir := t.TempDir()
idx := bleve.NewIndexer(dir) idx := bleve.NewIndexer(dir)

View File

@ -9,6 +9,7 @@ import (
"unicode" "unicode"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2"
@ -54,9 +55,9 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
return index, 0, nil return index, 0, nil
} }
// This method test the GuessFuzzinessByKeyword method. The fuzziness is based on the levenshtein distance and determines how many chars // GuessFuzzinessByKeyword guesses fuzziness based on the levenshtein distance and determines how many chars
// may be different on two string and they still be considered equivalent. // may be different on two string, and they still be considered equivalent.
// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero. // Given a phrase, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
func GuessFuzzinessByKeyword(s string) int { func GuessFuzzinessByKeyword(s string) int {
tokenizer := unicode_tokenizer.NewUnicodeTokenizer() tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
tokens := tokenizer.Tokenize([]byte(s)) tokens := tokenizer.Tokenize([]byte(s))
@ -85,5 +86,5 @@ func guessFuzzinessByKeyword(s string) int {
return 0 return 0
} }
} }
return min(maxFuzziness, len(s)/4) return min(min(setting.Indexer.TypeBleveMaxFuzzniess, maxFuzziness), len(s)/4)
} }

View File

@ -7,10 +7,15 @@ import (
"fmt" "fmt"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestBleveGuessFuzzinessByKeyword(t *testing.T) { func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
defer test.MockVariableValue(&setting.Indexer.TypeBleveMaxFuzzniess, 2)()
scenarios := []struct { scenarios := []struct {
Input string Input string
Fuzziness int // See util.go for the definition of fuzziness in this particular context Fuzziness int // See util.go for the definition of fuzziness in this particular context
@ -46,7 +51,7 @@ func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
t.Run(fmt.Sprintf("ensure fuzziness of '%s' is '%d'", scenario.Input, scenario.Fuzziness), func(t *testing.T) { t.Run(fmt.Sprintf("Fuziniess:%s=%d", scenario.Input, scenario.Fuzziness), func(t *testing.T) {
assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input)) assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input))
}) })
} }

View File

@ -109,7 +109,7 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
it.Content = string(content) it.Content = string(content)
it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath! it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
it.About, _ = util.SplitStringAtByteN(it.Content, 80) it.About = util.EllipsisDisplayString(it.Content, 80)
} else { } else {
it.Content = templateBody it.Content = templateBody
if it.About == "" { if it.About == "" {

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/markup/common" "code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/util"
"golang.org/x/net/html" "golang.org/x/net/html"
"golang.org/x/net/html/atom" "golang.org/x/net/html/atom"
@ -171,6 +172,10 @@ func linkProcessor(ctx *RenderContext, node *html.Node) {
} }
uri := node.Data[m[0]:m[1]] uri := node.Data[m[0]:m[1]]
remaining := node.Data[m[1]:]
if util.IsLikelyEllipsisLeftPart(remaining) {
return
}
replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/)) replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/))
node = node.NextSibling.NextSibling node = node.NextSibling.NextSibling
} }

View File

@ -206,6 +206,16 @@ func TestRender_links(t *testing.T) {
test( test(
"ftps://gitea.com", "ftps://gitea.com",
`<p>ftps://gitea.com</p>`) `<p>ftps://gitea.com</p>`)
t.Run("LinkEllipsis", func(t *testing.T) {
input := util.EllipsisDisplayString("http://10.1.2.3", 12)
assert.Equal(t, "http://10…", input)
test(input, "<p>http://10…</p>")
input = util.EllipsisDisplayString("http://10.1.2.3", 13)
assert.Equal(t, "http://10.…", input)
test(input, "<p>http://10.…</p>")
})
} }
func TestRender_email(t *testing.T) { func TestRender_email(t *testing.T) {

View File

@ -48,7 +48,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li") policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
// Allow 'color' and 'background-color' properties for the style attribute on text elements. // Allow 'color' and 'background-color' properties for the style attribute on text elements.
policy.AllowStyles("color", "background-color").OnElements("span", "p") policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video") policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")

View File

@ -7,6 +7,7 @@ import (
"encoding/xml" "encoding/xml"
"io" "io"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
"golang.org/x/net/html/charset" "golang.org/x/net/html/charset"
@ -32,17 +33,26 @@ type Dependency struct {
type pomStruct struct { type pomStruct struct {
XMLName xml.Name `xml:"project"` XMLName xml.Name `xml:"project"`
Parent struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
} `xml:"parent"`
GroupID string `xml:"groupId"` GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"` ArtifactID string `xml:"artifactId"`
Version string `xml:"version"` Version string `xml:"version"`
Name string `xml:"name"` Name string `xml:"name"`
Description string `xml:"description"` Description string `xml:"description"`
URL string `xml:"url"` URL string `xml:"url"`
Licenses []struct { Licenses []struct {
Name string `xml:"name"` Name string `xml:"name"`
URL string `xml:"url"` URL string `xml:"url"`
Distribution string `xml:"distribution"` Distribution string `xml:"distribution"`
} `xml:"licenses>license"` } `xml:"licenses>license"`
Dependencies []struct { Dependencies []struct {
GroupID string `xml:"groupId"` GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"` ArtifactID string `xml:"artifactId"`
@ -81,8 +91,16 @@ func ParsePackageMetaData(r io.Reader) (*Metadata, error) {
}) })
} }
pomGroupID := pom.GroupID
if pomGroupID == "" {
// the current module could inherit parent: https://maven.apache.org/pom.html#Inheritance
pomGroupID = pom.Parent.GroupID
}
if pomGroupID == "" {
return nil, util.ErrInvalidArgument
}
return &Metadata{ return &Metadata{
GroupID: pom.GroupID, GroupID: pomGroupID,
ArtifactID: pom.ArtifactID, ArtifactID: pom.ArtifactID,
Name: pom.Name, Name: pom.Name,
Description: pom.Description, Description: pom.Description,

View File

@ -7,7 +7,10 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/charmap"
) )
@ -86,4 +89,35 @@ func TestParsePackageMetaData(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, m) assert.NotNil(t, m)
}) })
t.Run("ParentInherit", func(t *testing.T) {
pom := `<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>submodule1</artifactId>
</project>
`
m, err := ParsePackageMetaData(strings.NewReader(pom))
require.NoError(t, err)
require.NotNil(t, m)
assert.Equal(t, "com.mycompany.app", m.GroupID)
assert.Equal(t, "submodule1", m.ArtifactID)
})
t.Run("ParentInherit", func(t *testing.T) {
pom := `<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId></artifactId>
</project>
`
_, err := ParsePackageMetaData(strings.NewReader(pom))
require.ErrorIs(t, err, util.ErrInvalidArgument)
})
} }

View File

@ -81,7 +81,7 @@ func LoadRepoConfig() error {
if isDir, err := util.IsDir(customPath); err != nil { if isDir, err := util.IsDir(customPath); err != nil {
return fmt.Errorf("failed to check custom %s dir: %w", t, err) return fmt.Errorf("failed to check custom %s dir: %w", t, err)
} else if isDir { } else if isDir {
if typeFiles[i].custom, err = util.StatDir(customPath); err != nil { if typeFiles[i].custom, err = util.ListDirRecursively(customPath, &util.ListDirOptions{SkipCommonHiddenNames: true}); err != nil {
return fmt.Errorf("failed to list custom %s files: %w", t, err) return fmt.Errorf("failed to list custom %s files: %w", t, err)
} }
} }

View File

@ -88,6 +88,21 @@ func (r *requestDataStore) cleanUp() {
} }
} }
type RequestContext interface {
context.Context
RequestDataStore
}
func FromContext(ctx context.Context) RequestContext {
// here we must use the current ctx and the underlying store
// the current ctx guarantees that the ctx deadline/cancellation/values are respected
// the underlying store guarantees that the request-specific data is available
if store := GetRequestDataStore(ctx); store != nil {
return &requestContext{Context: ctx, RequestDataStore: store}
}
return nil
}
func GetRequestDataStore(ctx context.Context) RequestDataStore { func GetRequestDataStore(ctx context.Context) RequestDataStore {
if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok { if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok {
return req return req
@ -97,11 +112,11 @@ func GetRequestDataStore(ctx context.Context) RequestDataStore {
type requestContext struct { type requestContext struct {
context.Context context.Context
dataStore *requestDataStore RequestDataStore
} }
func (c *requestContext) Value(key any) any { func (c *requestContext) Value(key any) any {
if v := c.dataStore.GetContextValue(key); v != nil { if v := c.GetContextValue(key); v != nil {
return v return v
} }
return c.Context.Value(key) return c.Context.Value(key)
@ -109,9 +124,10 @@ func (c *requestContext) Value(key any) any {
func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) { func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) {
ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true) ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true)
reqCtx := &requestContext{Context: ctx, dataStore: &requestDataStore{values: make(map[any]any)}} store := &requestDataStore{values: make(map[any]any)}
reqCtx := &requestContext{Context: ctx, RequestDataStore: store}
return reqCtx, func() { return reqCtx, func() {
reqCtx.dataStore.cleanUp() store.cleanUp()
processFinished() processFinished()
} }
} }
@ -119,5 +135,5 @@ func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Co
// NewRequestContextForTest creates a new RequestContext for testing purposes // NewRequestContextForTest creates a new RequestContext for testing purposes
// It doesn't add the context to the process manager, nor do cleanup // It doesn't add the context to the process manager, nor do cleanup
func NewRequestContextForTest(parentCtx context.Context) context.Context { func NewRequestContextForTest(parentCtx context.Context) context.Context {
return &requestContext{Context: parentCtx, dataStore: &requestDataStore{values: make(map[any]any)}} return &requestContext{Context: parentCtx, RequestDataStore: &requestDataStore{values: make(map[any]any)}}
} }

View File

@ -166,3 +166,25 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
} }
return changed return changed
} }
// InitGiteaEnvVars initializes the environment variables for gitea
func InitGiteaEnvVars() {
// Ideally Gitea should only accept the environment variables which it clearly knows instead of unsetting the ones it doesn't want,
// but the ideal behavior would be a breaking change, and it seems not bringing enough benefits to end users,
// so at the moment we could still keep "unsetting the unnecessary environments"
// HOME is managed by Gitea, Gitea's git should use "HOME/.gitconfig".
// But git would try "XDG_CONFIG_HOME/git/config" first if "HOME/.gitconfig" does not exist,
// then our git.InitFull would still write to "XDG_CONFIG_HOME/git/config" if XDG_CONFIG_HOME is set.
_ = os.Unsetenv("XDG_CONFIG_HOME")
}
func InitGiteaEnvVarsForTesting() {
InitGiteaEnvVars()
_ = os.Unsetenv("GIT_AUTHOR_NAME")
_ = os.Unsetenv("GIT_AUTHOR_EMAIL")
_ = os.Unsetenv("GIT_AUTHOR_DATE")
_ = os.Unsetenv("GIT_COMMITTER_NAME")
_ = os.Unsetenv("GIT_COMMITTER_EMAIL")
_ = os.Unsetenv("GIT_COMMITTER_DATE")
}

View File

@ -5,8 +5,6 @@ package setting
import ( import (
"time" "time"
"code.gitea.io/gitea/modules/log"
) )
// CORSConfig defines CORS settings // CORSConfig defines CORS settings
@ -28,7 +26,4 @@ var CORSConfig = struct {
func loadCorsFrom(rootCfg ConfigProvider) { func loadCorsFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "cors", &CORSConfig) mustMapSetting(rootCfg, "cors", &CORSConfig)
if CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
} }

View File

@ -31,6 +31,8 @@ var Indexer = struct {
IncludePatterns []*GlobMatcher IncludePatterns []*GlobMatcher
ExcludePatterns []*GlobMatcher ExcludePatterns []*GlobMatcher
ExcludeVendored bool ExcludeVendored bool
TypeBleveMaxFuzzniess int
}{ }{
IssueType: "bleve", IssueType: "bleve",
IssuePath: "indexers/issues.bleve", IssuePath: "indexers/issues.bleve",
@ -88,6 +90,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
Indexer.ExcludeVendored = sec.Key("REPO_INDEXER_EXCLUDE_VENDORED").MustBool(true) Indexer.ExcludeVendored = sec.Key("REPO_INDEXER_EXCLUDE_VENDORED").MustBool(true)
Indexer.MaxIndexerFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(1024 * 1024) Indexer.MaxIndexerFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(1024 * 1024)
Indexer.StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(30 * time.Second) Indexer.StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(30 * time.Second)
Indexer.TypeBleveMaxFuzzniess = sec.Key("TYPE_BLEVE_MAX_FUZZINESS").MustInt(0)
} }
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing // IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
@ -97,7 +100,7 @@ func IndexerGlobFromString(globstr string) []*GlobMatcher {
expr = strings.TrimSpace(expr) expr = strings.TrimSpace(expr)
if expr != "" { if expr != "" {
if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil { if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) log.Warn("Invalid glob expression '%s' (skipped): %v", expr, err)
} else { } else {
extarr = append(extarr, g) extarr = append(extarr, g)
} }

View File

@ -255,8 +255,6 @@ func loadMailerFrom(rootCfg ConfigProvider) {
MailService.OverrideEnvelopeFrom = true MailService.OverrideEnvelopeFrom = true
MailService.EnvelopeFrom = parsed.Address MailService.EnvelopeFrom = parsed.Address
} }
log.Info("Mail Service Enabled")
} }
func loadRegisterMailFrom(rootCfg ConfigProvider) { func loadRegisterMailFrom(rootCfg ConfigProvider) {
@ -267,7 +265,6 @@ func loadRegisterMailFrom(rootCfg ConfigProvider) {
return return
} }
Service.RegisterEmailConfirm = true Service.RegisterEmailConfirm = true
log.Info("Register Mail Service Enabled")
} }
func loadNotifyMailFrom(rootCfg ConfigProvider) { func loadNotifyMailFrom(rootCfg ConfigProvider) {
@ -278,7 +275,6 @@ func loadNotifyMailFrom(rootCfg ConfigProvider) {
return return
} }
Service.EnableNotifyMail = true Service.EnableNotifyMail = true
log.Info("Notify Mail Service Enabled")
} }
func tryResolveAddr(addr string) []net.IPAddr { func tryResolveAddr(addr string) []net.IPAddr {

View File

@ -13,8 +13,9 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
var (
// Security settings // Security settings
var (
InstallLock bool InstallLock bool
SecretKey string SecretKey string
InternalToken string // internal access token InternalToken string // internal access token
@ -27,7 +28,7 @@ var (
ReverseProxyTrustedProxies []string ReverseProxyTrustedProxies []string
MinPasswordLength int MinPasswordLength int
ImportLocalPaths bool ImportLocalPaths bool
DisableGitHooks bool DisableGitHooks = true
DisableWebhooks bool DisableWebhooks bool
OnlyAllowPushIfGiteaEnvironmentSet bool OnlyAllowPushIfGiteaEnvironmentSet bool
PasswordComplexity []string PasswordComplexity []string

View File

@ -73,6 +73,4 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = string(shadowConfig) SessionConfig.ProviderConfig = string(shadowConfig)
SessionConfig.OriginalProvider = SessionConfig.Provider SessionConfig.OriginalProvider = SessionConfig.Provider
SessionConfig.Provider = "VirtualSession" SessionConfig.Provider = "VirtualSession"
log.Info("Session Service Enabled")
} }

View File

@ -235,3 +235,9 @@ func checkOverlappedPath(name, path string) {
} }
configuredPaths[path] = name configuredPaths[path] = name
} }
func PanicInDevOrTesting(msg string, a ...any) {
if !IsProd || IsInTesting {
panic(fmt.Sprintf(msg, a...))
}
}

View File

@ -20,7 +20,6 @@ func loadTimeFrom(rootCfg ConfigProvider) {
if err != nil { if err != nil {
log.Fatal("Load time zone failed: %v", err) log.Fatal("Load time zone failed: %v", err)
} }
log.Info("Default UI Location is %v", zone)
} }
if DefaultUILocation == nil { if DefaultUILocation == nil {
DefaultUILocation = time.Local DefaultUILocation = time.Local

View File

@ -63,6 +63,7 @@ var UI = struct {
} `ini:"ui.admin"` } `ini:"ui.admin"`
User struct { User struct {
RepoPagingNum int RepoPagingNum int
OrgPagingNum int
} `ini:"ui.user"` } `ini:"ui.user"`
Meta struct { Meta struct {
Author string Author string
@ -127,8 +128,10 @@ var UI = struct {
}, },
User: struct { User: struct {
RepoPagingNum int RepoPagingNum int
OrgPagingNum int
}{ }{
RepoPagingNum: 15, RepoPagingNum: 15,
OrgPagingNum: 15,
}, },
Meta: struct { Meta: struct {
Author string Author string

View File

@ -70,7 +70,7 @@ func (a *azureBlobObject) Seek(offset int64, whence int) (int64, error) {
case io.SeekCurrent: case io.SeekCurrent:
offset += a.offset offset += a.offset
case io.SeekEnd: case io.SeekEnd:
offset = a.Size - offset offset = a.Size + offset
default: default:
return 0, errors.New("Seek: invalid whence") return 0, errors.New("Seek: invalid whence")
} }

Some files were not shown because too many files have changed in this diff Show More