0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-07-18 21:42:56 +02:00

merge main

This commit is contained in:
Kerwin Bryant 2025-01-10 01:57:19 +00:00
commit 335375fd76
211 changed files with 3789 additions and 3048 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ _test
# IntelliJ # IntelliJ
.idea .idea
.run
# IntelliJ Gateway # IntelliJ Gateway
.uuid .uuid

File diff suppressed because one or more lines are too long

View File

@ -54,8 +54,10 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p altTLSALPNPort = p
} }
magic := &certmagic.Default // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} // Ideally it should migrate to AppDataPath write to "AppDataPath/https"
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
magic := certmagic.NewDefault()
// 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
if setting.AcmeCARoot != "" { if setting.AcmeCARoot != "" {

View File

@ -78,8 +78,9 @@ RUN_USER = ; git
;; Set the domain for the server ;; Set the domain for the server
;DOMAIN = localhost ;DOMAIN = localhost
;; ;;
;; Overwrite the automatically generated public URL. Necessary for proxies and docker. ;; The AppURL used by Gitea to generate absolute links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ ;; Most users should set it to the real website URL of their Gitea instance.
;ROOT_URL =
;; ;;
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy. ;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
;; DO NOT USE IT IN PRODUCTION!!! ;; DO NOT USE IT IN PRODUCTION!!!
@ -103,8 +104,8 @@ RUN_USER = ; git
;REDIRECT_OTHER_PORT = false ;REDIRECT_OTHER_PORT = false
;PORT_TO_REDIRECT = 80 ;PORT_TO_REDIRECT = 80
;; ;;
;; expect PROXY protocol header on connections to https redirector. ;; expect PROXY protocol header on connections to https redirector, defaults to USE_PROXY_PROTOCOL
;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s ;REDIRECTOR_USE_PROXY_PROTOCOL =
;; Minimum and maximum supported TLS versions ;; Minimum and maximum supported TLS versions
;SSL_MIN_VERSION=TLSv1.2 ;SSL_MIN_VERSION=TLSv1.2
;SSL_MAX_VERSION= ;SSL_MAX_VERSION=
@ -128,13 +129,14 @@ RUN_USER = ; git
;; most cases you do not need to change the default value. Alter it only if ;; most cases you do not need to change the default value. Alter it only if
;; your SSH server node is not the same as HTTP node. For different protocol, the default ;; your SSH server node is not the same as HTTP node. For different protocol, the default
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`. ;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`. ;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
;; If listen on `0.0.0.0`, the default value is `%(PROTOCOL)s://localhost:%(HTTP_PORT)s/`, Otherwise the default ;; If listen on `0.0.0.0`, the default value is `{PROTOCOL}://localhost:{HTTP_PORT}/`.
;; value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`. ;; Otherwise the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`.
;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ ;; Most users don't need (and shouldn't) set this value.
;LOCAL_ROOT_URL =
;; ;;
;; When making local connections pass the PROXY protocol header. ;; When making local connections pass the PROXY protocol header, defaults to USE_PROXY_PROTOCOL
;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s ;LOCAL_USE_PROXY_PROTOCOL =
;; ;;
;; Disable SSH feature when not available ;; Disable SSH feature when not available
;DISABLE_SSH = false ;DISABLE_SSH = false
@ -146,13 +148,17 @@ RUN_USER = ; git
;SSH_SERVER_USE_PROXY_PROTOCOL = false ;SSH_SERVER_USE_PROXY_PROTOCOL = false
;; ;;
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. ;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s ;BUILTIN_SSH_SERVER_USER =
;; ;;
;; Domain name to be exposed in clone URL ;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
;SSH_DOMAIN = %(DOMAIN)s ;SSH_DOMAIN =
;; ;;
;; SSH username displayed in clone URLs. ;; SSH username displayed in clone URLs. It defaults to BUILTIN_SSH_SERVER_USER or RUN_USER.
;SSH_USER = %(BUILTIN_SSH_SERVER_USER)s ;; If it is set to "(DOER_USERNAME)", it will use current signed-in user's username.
;; This option is only for some advanced users who have configured their SSH reverse-proxy
;; and need to use different usernames for git SSH clone.
;; Most users should just leave it blank.
;SSH_USER =
;; ;;
;; The network interface the builtin SSH server should listen on ;; The network interface the builtin SSH server should listen on
;SSH_LISTEN_HOST = ;SSH_LISTEN_HOST =
@ -160,8 +166,8 @@ RUN_USER = ; git
;; Port number to be exposed in clone URL ;; Port number to be exposed in clone URL
;SSH_PORT = 22 ;SSH_PORT = 22
;; ;;
;; The port number the builtin SSH server should listen on ;; The port number the builtin SSH server should listen on, defaults to SSH_PORT
;SSH_LISTEN_PORT = %(SSH_PORT)s ;SSH_LISTEN_PORT =
;; ;;
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. ;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
;SSH_ROOT_PATH = ;SSH_ROOT_PATH =
@ -188,7 +194,7 @@ RUN_USER = ; git
;; ;;
;; For the built-in SSH server, choose the keypair to offer as the host key ;; For the built-in SSH server, choose the keypair to offer as the host key
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
;; relative paths are made absolute relative to the %(APP_DATA_PATH)s ;; relative paths are made absolute relative to the APP_DATA_PATH
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
;; ;;
;; Directory to create temporary files in when testing public keys using ssh-keygen, ;; Directory to create temporary files in when testing public keys using ssh-keygen,
@ -582,7 +588,7 @@ ENABLED = true
[log] [log]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Root path for the log files - defaults to %(GITEA_WORK_DIR)/log ;; Root path for the log files - defaults to "{AppWorkPath}/log"
;ROOT_PATH = ;ROOT_PATH =
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -682,8 +688,8 @@ LEVEL = Info
;; The path of git executable. If empty, Gitea searches through the PATH environment. ;; The path of git executable. If empty, Gitea searches through the PATH environment.
;PATH = ;PATH =
;; ;;
;; The HOME directory for Git ;; The HOME directory for Git, defaults to "{APP_DATA_PATH}/home"
;HOME_PATH = %(APP_DATA_PATH)s/home ;HOME_PATH =
;; ;;
;; Disables highlight of added and removed changes ;; Disables highlight of added and removed changes
;DISABLE_DIFF_HIGHLIGHT = false ;DISABLE_DIFF_HIGHLIGHT = false
@ -946,8 +952,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository] ;[repository]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories. ;; Root path for storing all repository data. By default, it is set to "{APP_DATA_PATH}/gitea-repositories".
;; A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s ;; A relative path is interpreted as "{AppWorkPath}/{ROOT}" (use AppWorkPath as base path).
;ROOT = ;ROOT =
;; ;;
;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available. ;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available.
@ -1506,7 +1512,8 @@ LEVEL = Info
;TYPE = persistable-channel ;TYPE = persistable-channel
;; ;;
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. ;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`. ;; Relative paths will be made absolute against "APP_DATA_PATH"
;DATADIR = queues/
;; ;;
;; Default queue length before a channel queue will block ;; Default queue length before a channel queue will block
;LENGTH = 100000 ;LENGTH = 100000

37
go.mod
View File

@ -24,10 +24,10 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.0.0 github.com/ProtonMail/go-crypto v1.1.4
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.15.0
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
@ -54,8 +54,8 @@ require (
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.1 github.com/go-enry/go-enry/v2 v2.9.1
github.com/go-git/go-billy/v5 v5.6.0 github.com/go-git/go-billy/v5 v5.6.1
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.13.1
github.com/go-ldap/ldap/v3 v3.4.8 github.com/go-ldap/ldap/v3 v3.4.8
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
@ -71,7 +71,6 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0 github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/h2non/gock v1.2.0
github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0 github.com/huandu/xstrings v1.5.0
@ -107,28 +106,28 @@ require (
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.12 github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.5 github.com/urfave/cli/v2 v2.27.5
github.com/wneessen/go-mail v0.5.2 github.com/wneessen/go-mail v0.5.2
github.com/xanzy/go-gitlab v0.112.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.31.0 gitlab.com/gitlab-org/api/client-go v0.119.0
golang.org/x/crypto v0.32.0
golang.org/x/image v0.21.0 golang.org/x/image v0.21.0
golang.org/x/net v0.33.0 golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.10.0 golang.org/x/sync v0.10.0
golang.org/x/sys v0.28.0 golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
golang.org/x/tools v0.26.0 golang.org/x/tools v0.29.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1 google.golang.org/protobuf v1.36.0
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.5.0 mvdan.cc/xurls/v2 v2.5.0
@ -187,7 +186,7 @@ require (
github.com/couchbase/gomemcached v0.3.2 // indirect github.com/couchbase/gomemcached v0.3.2 // indirect
github.com/couchbase/goutils v0.1.2 // indirect github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyphar/filepath-securejoin v0.3.4 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@ -220,7 +219,7 @@ require (
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
@ -230,7 +229,6 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
@ -255,6 +253,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mmcloughlin/avo v0.6.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
@ -266,7 +265,7 @@ require (
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // 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.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/common v0.60.1 // indirect
@ -306,8 +305,8 @@ require (
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
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mod v0.21.0 // indirect golang.org/x/mod v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.8.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

100
go.sum
View File

@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
@ -81,11 +81,11 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
@ -188,7 +188,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk= github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk=
github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0= github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0= github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
@ -205,7 +204,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -223,8 +221,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -254,8 +252,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w= github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY= github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
@ -313,12 +311,12 @@ github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5Hql
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=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
@ -385,8 +383,8 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@ -452,10 +450,6 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -585,6 +579,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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=
@ -599,8 +595,6 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
@ -637,8 +631,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -743,8 +737,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@ -772,8 +766,6 @@ github.com/wneessen/go-mail v0.5.2 h1:MZKwgHJoRboLJ+EHMLuHpZc95wo+u1xViL/4XSswDT
github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck= github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+DKw=
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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@ -808,6 +800,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
gitlab.com/gitlab-org/api/client-go v0.119.0 h1:YBZyx9XUTtEDBBYtY36cZWz6JmT7om/8HPSk37IS95g=
gitlab.com/gitlab-org/api/client-go v0.119.0/go.mod h1:ygHmS3AU3TpvK+AC6DYO1QuAxLlv6yxYK+/Votr/WFQ=
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=
@ -829,16 +823,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
@ -852,8 +844,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -865,20 +857,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -916,8 +906,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -927,14 +915,12 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
@ -942,15 +928,13 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
@ -958,8 +942,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
@ -970,8 +954,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -986,8 +970,8 @@ 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0/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=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -154,7 +154,7 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
} }
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) { func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
if run.Event == webhook_module.HookEventPullRequest || run.Event == webhook_module.HookEventPullRequestSync { if run.Event.IsPullRequest() {
var payload api.PullRequestPayload var payload api.PullRequestPayload
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
return nil, err return nil, err

View File

@ -15,6 +15,7 @@ import (
"github.com/keybase/go-crypto/openpgp/packet" "github.com/keybase/go-crypto/openpgp/packet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCheckArmoredGPGKeyString(t *testing.T) { func TestCheckArmoredGPGKeyString(t *testing.T) {
@ -107,9 +108,8 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7 =i9b7
-----END PGP PUBLIC KEY BLOCK-----` -----END PGP PUBLIC KEY BLOCK-----`
keys, err := checkArmoredGPGKeyString(testGPGArmor) keys, err := checkArmoredGPGKeyString(testGPGArmor)
if !assert.NotEmpty(t, keys) { require.NotEmpty(t, keys)
return
}
ekey := keys[0] ekey := keys[0]
assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey) assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey)

View File

@ -15,6 +15,7 @@ import (
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys _ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDumpDatabase(t *testing.T) { func TestDumpDatabase(t *testing.T) {
@ -62,9 +63,7 @@ func TestPrimaryKeys(t *testing.T) {
// Import "code.gitea.io/gitea/cmd" to make sure each db.RegisterModel in init functions has been called. // Import "code.gitea.io/gitea/cmd" to make sure each db.RegisterModel in init functions has been called.
beans, err := db.NamesToBean() beans, err := db.NamesToBean()
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
whitelist := map[string]string{ whitelist := map[string]string{
"the_table_name_to_skip_checking": "Write a note here to explain why", "the_table_name_to_skip_checking": "Write a note here to explain why",
@ -79,8 +78,6 @@ func TestPrimaryKeys(t *testing.T) {
t.Logf("ignore %q because %q", table.Name, why) t.Logf("ignore %q because %q", table.Name, why)
continue continue
} }
if len(table.PrimaryKeys) == 0 { assert.NotEmpty(t, table.PrimaryKeys, "table %q has no primary key", table.Name)
t.Errorf("table %q has no primary key", table.Name)
}
} }
} }

View File

@ -46,23 +46,6 @@ func (err ErrIssueNotExist) Unwrap() error {
return util.ErrNotExist return util.ErrNotExist
} }
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
type ErrIssueIsClosed struct {
ID int64
RepoID int64
Index int64
}
// IsErrIssueIsClosed checks if an error is a ErrIssueNotExist.
func IsErrIssueIsClosed(err error) bool {
_, ok := err.(ErrIssueIsClosed)
return ok
}
func (err ErrIssueIsClosed) Error() string {
return fmt.Sprintf("issue is closed [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
}
// ErrNewIssueInsert is used when the INSERT statement in newIssue fails // ErrNewIssueInsert is used when the INSERT statement in newIssue fails
type ErrNewIssueInsert struct { type ErrNewIssueInsert struct {
OriginalError error OriginalError error
@ -78,22 +61,6 @@ func (err ErrNewIssueInsert) Error() string {
return err.OriginalError.Error() return err.OriginalError.Error()
} }
// ErrIssueWasClosed is used when close a closed issue
type ErrIssueWasClosed struct {
ID int64
Index int64
}
// IsErrIssueWasClosed checks if an error is a ErrIssueWasClosed.
func IsErrIssueWasClosed(err error) bool {
_, ok := err.(ErrIssueWasClosed)
return ok
}
func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}
var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed") var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")
// Issue represents an issue or pull request of repository. // Issue represents an issue or pull request of repository.

View File

@ -28,38 +28,40 @@ import (
// UpdateIssueCols updates cols of issue // UpdateIssueCols updates cols of issue
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error { func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil { _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue)
return err return err
}
return nil
} }
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) { // ErrIssueIsClosed is used when close a closed issue
// Reload the issue type ErrIssueIsClosed struct {
currentIssue, err := GetIssueByID(ctx, issue.ID) ID int64
if err != nil { RepoID int64
return nil, err Index int64
} IsPull bool
// Nothing should be performed if current status is same as target status
if currentIssue.IsClosed == isClosed {
if !issue.IsPull {
return nil, ErrIssueWasClosed{
ID: issue.ID,
}
}
return nil, ErrPullWasClosed{
ID: issue.ID,
}
}
issue.IsClosed = isClosed
return doChangeIssueStatus(ctx, issue, doer, isMergePull)
} }
func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) { // IsErrIssueIsClosed checks if an error is a ErrIssueIsClosed.
func IsErrIssueIsClosed(err error) bool {
_, ok := err.(ErrIssueIsClosed)
return ok
}
func (err ErrIssueIsClosed) Error() string {
return fmt.Sprintf("%s [id: %d, repo_id: %d, index: %d] is already closed", util.Iif(err.IsPull, "Pull Request", "Issue"), err.ID, err.RepoID, err.Index)
}
func SetIssueAsClosed(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
if issue.IsClosed {
return nil, ErrIssueIsClosed{
ID: issue.ID,
RepoID: issue.RepoID,
Index: issue.Index,
IsPull: issue.IsPull,
}
}
// Check for open dependencies // Check for open dependencies
if issue.IsClosed && issue.Repo.IsDependenciesEnabled(ctx) { if issue.Repo.IsDependenciesEnabled(ctx) {
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies // only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
noDeps, err := IssueNoDependenciesLeft(ctx, issue) noDeps, err := IssueNoDependenciesLeft(ctx, issue)
if err != nil { if err != nil {
@ -71,16 +73,63 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
} }
} }
if issue.IsClosed { issue.IsClosed = true
issue.ClosedUnix = timeutil.TimeStampNow() issue.ClosedUnix = timeutil.TimeStampNow()
} else {
issue.ClosedUnix = 0
}
if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil { if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
Where("is_closed = ?", false).
Update(issue); err != nil {
return nil, err return nil, err
} else if cnt != 1 {
return nil, ErrIssueAlreadyChanged
} }
return updateIssueNumbers(ctx, issue, doer, util.Iif(isMergePull, CommentTypeMergePull, CommentTypeClose))
}
// ErrIssueIsOpen is used when reopen an opened issue
type ErrIssueIsOpen struct {
ID int64
RepoID int64
IsPull bool
Index int64
}
// IsErrIssueIsOpen checks if an error is a ErrIssueIsOpen.
func IsErrIssueIsOpen(err error) bool {
_, ok := err.(ErrIssueIsOpen)
return ok
}
func (err ErrIssueIsOpen) Error() string {
return fmt.Sprintf("%s [id: %d, repo_id: %d, index: %d] is already open", util.Iif(err.IsPull, "Pull Request", "Issue"), err.ID, err.RepoID, err.Index)
}
func setIssueAsReopen(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if !issue.IsClosed {
return nil, ErrIssueIsOpen{
ID: issue.ID,
RepoID: issue.RepoID,
Index: issue.Index,
IsPull: issue.IsPull,
}
}
issue.IsClosed = false
issue.ClosedUnix = 0
if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
Where("is_closed = ?", true).
Update(issue); err != nil {
return nil, err
} else if cnt != 1 {
return nil, ErrIssueAlreadyChanged
}
return updateIssueNumbers(ctx, issue, doer, CommentTypeReopen)
}
func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User, cmtType CommentType) (*Comment, error) {
// Update issue count of labels // Update issue count of labels
if err := issue.LoadLabels(ctx); err != nil { if err := issue.LoadLabels(ctx); err != nil {
return nil, err return nil, err
@ -103,14 +152,6 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
return nil, err return nil, err
} }
// New action comment
cmtType := CommentTypeClose
if !issue.IsClosed {
cmtType = CommentTypeReopen
} else if isMergePull {
cmtType = CommentTypeMergePull
}
return CreateComment(ctx, &CreateCommentOptions{ return CreateComment(ctx, &CreateCommentOptions{
Type: cmtType, Type: cmtType,
Doer: doer, Doer: doer,
@ -134,7 +175,7 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm
} }
defer committer.Close() defer committer.Close()
comment, err := ChangeIssueStatus(ctx, issue, doer, true, false) comment, err := SetIssueAsClosed(ctx, issue, doer, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -159,7 +200,7 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com
} }
defer committer.Close() defer committer.Close()
comment, err := ChangeIssueStatus(ctx, issue, doer, false, false) comment, err := setIssueAsReopen(ctx, issue, doer)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -80,22 +80,6 @@ func (err ErrPullRequestAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist return util.ErrAlreadyExist
} }
// ErrPullWasClosed is used close a closed pull request
type ErrPullWasClosed struct {
ID int64
Index int64
}
// IsErrPullWasClosed checks if an error is a ErrErrPullWasClosed.
func IsErrPullWasClosed(err error) bool {
_, ok := err.(ErrPullWasClosed)
return ok
}
func (err ErrPullWasClosed) Error() string {
return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
}
// PullRequestType defines pull request type // PullRequestType defines pull request type
type PullRequestType int type PullRequestType int
@ -301,7 +285,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
return nil return nil
} }
reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID) reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
if err != nil { if err != nil {
return err return err
} }
@ -320,7 +304,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
// LoadRequestedReviewersTeams loads the requested reviewers teams. // LoadRequestedReviewersTeams loads the requested reviewers teams.
func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error { func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID) reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -166,6 +166,23 @@ func (prs PullRequestList) getRepositoryIDs() []int64 {
return repoIDs.Values() return repoIDs.Values()
} }
func (prs PullRequestList) SetBaseRepo(baseRepo *repo_model.Repository) {
for _, pr := range prs {
if pr.BaseRepo == nil {
pr.BaseRepo = baseRepo
}
}
}
func (prs PullRequestList) SetHeadRepo(headRepo *repo_model.Repository) {
for _, pr := range prs {
if pr.HeadRepo == nil {
pr.HeadRepo = headRepo
pr.isHeadRepoLoaded = true
}
}
}
func (prs PullRequestList) LoadRepositories(ctx context.Context) error { func (prs PullRequestList) LoadRepositories(ctx context.Context) error {
repoIDs := prs.getRepositoryIDs() repoIDs := prs.getRepositoryIDs()
reposMap := make(map[int64]*repo_model.Repository, len(repoIDs)) reposMap := make(map[int64]*repo_model.Repository, len(repoIDs))

View File

@ -5,6 +5,8 @@ package issues
import ( import (
"context" "context"
"slices"
"sort"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
organization_model "code.gitea.io/gitea/models/organization" organization_model "code.gitea.io/gitea/models/organization"
@ -153,43 +155,60 @@ func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{}) return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
} }
// GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request
func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
reviews := make([]*Review, 0, 10)
// Get latest review of each reviewer, sorted in order they were made
if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC",
issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
Find(&reviews); err != nil {
return nil, err
}
return reviews, nil
}
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request // GetReviewsByIssueID gets the latest review of each reviewer for a pull request
func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) { // The first returned parameter is the latest review of each individual reviewer or team
// The second returned parameter is the latest review of each original author which is migrated from other systems
// The reviews are sorted by updated time
func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
reviews := make([]*Review, 0, 10) reviews := make([]*Review, 0, 10)
sess := db.GetEngine(ctx) // Get all reviews for the issue id
if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
// Get latest review of each reviewer, sorted in order they were made return nil, nil, err
if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC",
issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, false).
Find(&reviews); err != nil {
return nil, err
} }
// filter them in memory to get the latest review of each reviewer
// Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
// And since there are too less indexes in review table, it will be very slow to filter in the database
reviewersMap := make(map[int64][]*Review) // key is reviewer id
originalReviewersMap := make(map[int64][]*Review) // key is original author id
reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
for _, review := range reviews {
if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
if review.OriginalAuthorID != 0 {
originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
} else {
reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
}
} else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
}
}
individualReviews := make([]*Review, 0, 10)
for _, reviews := range reviewersMap {
individualReviews = append(individualReviews, reviews[len(reviews)-1])
}
sort.Slice(individualReviews, func(i, j int) bool {
return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
})
originalReviews := make([]*Review, 0, 10)
for _, reviews := range originalReviewersMap {
originalReviews = append(originalReviews, reviews[len(reviews)-1])
}
sort.Slice(originalReviews, func(i, j int) bool {
return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
})
teamReviewRequests := make([]*Review, 0, 5) teamReviewRequests := make([]*Review, 0, 5)
if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id <> 0 AND original_author_id = 0 GROUP BY issue_id, reviewer_team_id) ORDER BY review.updated_unix ASC", for _, reviews := range reviewTeamsMap {
issueID). teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
Find(&teamReviewRequests); err != nil {
return nil, err
} }
sort.Slice(teamReviewRequests, func(i, j int) bool {
return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
})
if len(teamReviewRequests) > 0 { return append(individualReviews, teamReviewRequests...), originalReviews, nil
reviews = append(reviews, teamReviewRequests...)
}
return reviews, nil
} }

View File

@ -162,8 +162,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
}, },
) )
allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) allReviews, migratedReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, migratedReviews)
for _, review := range allReviews { for _, review := range allReviews {
assert.NoError(t, review.LoadReviewer(db.DefaultContext)) assert.NoError(t, review.LoadReviewer(db.DefaultContext))
} }

View File

@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_21" "code.gitea.io/gitea/models/migrations/v1_21"
"code.gitea.io/gitea/models/migrations/v1_22" "code.gitea.io/gitea/models/migrations/v1_22"
"code.gitea.io/gitea/models/migrations/v1_23" "code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_24"
"code.gitea.io/gitea/models/migrations/v1_6" "code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7" "code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8" "code.gitea.io/gitea/models/migrations/v1_8"
@ -369,6 +370,9 @@ func prepareMigrationTasks() []*migration {
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices), newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch), newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable), newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge),
} }
return preparedMigrations return preparedMigrations
} }

View File

@ -172,7 +172,7 @@ func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err) return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
} }
u, err := giturl.Parse(remoteURL) u, err := giturl.ParseGitURL(remoteURL)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -0,0 +1,21 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_24 //nolint
import (
"xorm.io/xorm"
)
type pullAutoMerge struct {
DeleteBranchAfterMerge bool
}
// TableName return database table name for xorm
func (pullAutoMerge) TableName() string {
return "pull_auto_merge"
}
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
return x.Sync(new(pullAutoMerge))
}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestUser_IsOwnedBy(t *testing.T) { func TestUser_IsOwnedBy(t *testing.T) {
@ -180,9 +181,8 @@ func TestRestrictedUserOrgMembers(t *testing.T) {
ID: 29, ID: 29,
IsRestricted: true, IsRestricted: true,
}) })
if !assert.True(t, restrictedUser.IsRestricted) { // ensure fixtures return restricted user
return // ensure fixtures return restricted user require.True(t, restrictedUser.IsRestricted)
}
testCases := []struct { testCases := []struct {
name string name string

View File

@ -21,6 +21,7 @@ type AutoMerge struct {
Doer *user_model.User `xorm:"-"` Doer *user_model.User `xorm:"-"`
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"` MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
Message string `xorm:"LONGTEXT"` Message string `xorm:"LONGTEXT"`
DeleteBranchAfterMerge bool
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
} }
@ -49,7 +50,7 @@ func IsErrAlreadyScheduledToAutoMerge(err error) bool {
} }
// ScheduleAutoMerge schedules a pull request to be merged when all checks succeed // ScheduleAutoMerge schedules a pull request to be merged when all checks succeed
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string) error { func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) error {
// Check if we already have a merge scheduled for that pull request // Check if we already have a merge scheduled for that pull request
if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil { if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil {
return err return err
@ -62,6 +63,7 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
PullID: pullID, PullID: pullID,
MergeStyle: style, MergeStyle: style,
Message: message, Message: message,
DeleteBranchAfterMerge: deleteBranchAfterMerge,
}) })
return err return err
} }

View File

@ -20,6 +20,7 @@ import (
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/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
@ -279,6 +280,8 @@ func (repo *Repository) IsBroken() bool {
} }
// MarkAsBrokenEmpty marks the repo as broken and empty // MarkAsBrokenEmpty marks the repo as broken and empty
// FIXME: the status "broken" and "is_empty" were abused,
// The code always set them together, no way to distinguish whether a repo is really "empty" or "broken"
func (repo *Repository) MarkAsBrokenEmpty() { func (repo *Repository) MarkAsBrokenEmpty() {
repo.Status = RepositoryBroken repo.Status = RepositoryBroken
repo.IsEmpty = true repo.IsEmpty = true
@ -635,14 +638,26 @@ type CloneLink struct {
} }
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
func ComposeHTTPSCloneURL(owner, repo string) string { func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo)) return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo))
} }
func ComposeSSHCloneURL(ownerName, repoName string) string { func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
sshUser := setting.SSH.User sshUser := setting.SSH.User
sshDomain := setting.SSH.Domain sshDomain := setting.SSH.Domain
if sshUser == "(DOER_USERNAME)" {
// Some users use SSH reverse-proxy and need to use the current signed-in username as the SSH user
// to make the SSH reverse-proxy could prepare the user's public keys ahead.
// For most cases we have the correct "doer", then use it as the SSH user.
// If we can't get the doer, then use the built-in SSH user.
if doer != nil {
sshUser = doer.Name
} else {
sshUser = setting.SSH.BuiltinServerUser
}
}
// non-standard port, it must use full URI // non-standard port, it must use full URI
if setting.SSH.Port != 22 { if setting.SSH.Port != 22 {
sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port)) sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
@ -660,21 +675,20 @@ func ComposeSSHCloneURL(ownerName, repoName string) string {
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
} }
func (repo *Repository) cloneLink(isWiki bool) *CloneLink { func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
repoName := repo.Name
if isWiki {
repoName += ".wiki"
}
cl := new(CloneLink) cl := new(CloneLink)
cl.SSH = ComposeSSHCloneURL(repo.OwnerName, repoName) cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName)
cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName) cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName)
return cl return cl
} }
// CloneLink returns clone URLs of repository. // CloneLink returns clone URLs of repository.
func (repo *Repository) CloneLink() (cl *CloneLink) { func (repo *Repository) CloneLink(ctx context.Context, doer *user_model.User) (cl *CloneLink) {
return repo.cloneLink(false) return repo.cloneLink(ctx, doer, repo.Name)
}
func (repo *Repository) CloneLinkGeneral(ctx context.Context) (cl *CloneLink) {
return repo.cloneLink(ctx, nil /* no doer, use a general git user */, repo.Name)
} }
// GetOriginalURLHostname returns the hostname of a URL or the URL // GetOriginalURLHostname returns the hostname of a URL or the URL
@ -770,47 +784,25 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
return &repo, err return &repo, err
} }
// getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url
func getRepositoryURLPathSegments(repoURL string) []string {
if strings.HasPrefix(repoURL, setting.AppURL) {
return strings.Split(strings.TrimPrefix(repoURL, setting.AppURL), "/")
}
sshURLVariants := [4]string{
setting.SSH.Domain + ":",
setting.SSH.User + "@" + setting.SSH.Domain + ":",
"git+ssh://" + setting.SSH.Domain + "/",
"git+ssh://" + setting.SSH.User + "@" + setting.SSH.Domain + "/",
}
for _, sshURL := range sshURLVariants {
if strings.HasPrefix(repoURL, sshURL) {
return strings.Split(strings.TrimPrefix(repoURL, sshURL), "/")
}
}
return nil
}
// GetRepositoryByURL returns the repository by given url // GetRepositoryByURL returns the repository by given url
func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) { func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) {
// possible urls for git: ret, err := giturl.ParseRepositoryURL(ctx, repoURL)
// https://my.domain/sub-path/<owner>/<repo>.git if err != nil || ret.OwnerName == "" {
// https://my.domain/sub-path/<owner>/<repo>
// git+ssh://user@my.domain/<owner>/<repo>.git
// git+ssh://user@my.domain/<owner>/<repo>
// user@my.domain:<owner>/<repo>.git
// user@my.domain:<owner>/<repo>
pathSegments := getRepositoryURLPathSegments(repoURL)
if len(pathSegments) != 2 {
return nil, fmt.Errorf("unknown or malformed repository URL") return nil, fmt.Errorf("unknown or malformed repository URL")
} }
return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName)
}
ownerName := pathSegments[0] // GetRepositoryByURLRelax also accepts an SSH clone URL without user part
repoName := strings.TrimSuffix(pathSegments[1], ".git") func GetRepositoryByURLRelax(ctx context.Context, repoURL string) (*Repository, error) {
return GetRepositoryByOwnerAndName(ctx, ownerName, repoName) if !strings.Contains(repoURL, "://") && !strings.Contains(repoURL, "@") {
// convert "example.com:owner/repo" to "@example.com:owner/repo"
p1, p2, p3 := strings.Index(repoURL, "."), strings.Index(repoURL, ":"), strings.Index(repoURL, "/")
if 0 < p1 && p1 < p2 && p2 < p3 {
repoURL = "@" + repoURL
}
}
return GetRepositoryByURL(ctx, repoURL)
} }
// GetRepositoryByID returns the repository by given id if exists. // GetRepositoryByID returns the repository by given id if exists.

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -132,60 +133,43 @@ func TestGetRepositoryByURL(t *testing.T) {
t.Run("InvalidPath", func(t *testing.T) { t.Run("InvalidPath", func(t *testing.T) {
repo, err := GetRepositoryByURL(db.DefaultContext, "something") repo, err := GetRepositoryByURL(db.DefaultContext, "something")
assert.Nil(t, repo) assert.Nil(t, repo)
assert.Error(t, err) assert.Error(t, err)
}) })
t.Run("ValidHttpURL", func(t *testing.T) { testRepo2 := func(t *testing.T, url string) {
test := func(t *testing.T, url string) {
repo, err := GetRepositoryByURL(db.DefaultContext, url) repo, err := GetRepositoryByURL(db.DefaultContext, url)
require.NoError(t, err)
assert.NotNil(t, repo) assert.EqualValues(t, 2, repo.ID)
assert.NoError(t, err) assert.EqualValues(t, 2, repo.OwnerID)
assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "https://try.gitea.io/user2/repo2") t.Run("ValidHttpURL", func(t *testing.T) {
test(t, "https://try.gitea.io/user2/repo2.git") testRepo2(t, "https://try.gitea.io/user2/repo2")
testRepo2(t, "https://try.gitea.io/user2/repo2.git")
}) })
t.Run("ValidGitSshURL", func(t *testing.T) { t.Run("ValidGitSshURL", func(t *testing.T) {
test := func(t *testing.T, url string) { testRepo2(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
repo, err := GetRepositoryByURL(db.DefaultContext, url) testRepo2(t, "git+ssh://sshuser@try.gitea.io/user2/repo2.git")
assert.NotNil(t, repo) testRepo2(t, "git+ssh://try.gitea.io/user2/repo2")
assert.NoError(t, err) testRepo2(t, "git+ssh://try.gitea.io/user2/repo2.git")
assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, int64(2), repo.OwnerID)
}
test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2.git")
test(t, "git+ssh://try.gitea.io/user2/repo2")
test(t, "git+ssh://try.gitea.io/user2/repo2.git")
}) })
t.Run("ValidImplicitSshURL", func(t *testing.T) { t.Run("ValidImplicitSshURL", func(t *testing.T) {
test := func(t *testing.T, url string) { testRepo2(t, "sshuser@try.gitea.io:user2/repo2")
repo, err := GetRepositoryByURL(db.DefaultContext, url) testRepo2(t, "sshuser@try.gitea.io:user2/repo2.git")
assert.NotNil(t, repo)
assert.NoError(t, err)
testRelax := func(t *testing.T, url string) {
repo, err := GetRepositoryByURLRelax(db.DefaultContext, url)
require.NoError(t, err)
assert.Equal(t, int64(2), repo.ID) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, int64(2), repo.OwnerID) assert.Equal(t, int64(2), repo.OwnerID)
} }
// TODO: it doesn't seem to be common git ssh URL, should we really support this?
test(t, "sshuser@try.gitea.io:user2/repo2") testRelax(t, "try.gitea.io:user2/repo2")
test(t, "sshuser@try.gitea.io:user2/repo2.git") testRelax(t, "try.gitea.io:user2/repo2.git")
test(t, "try.gitea.io:user2/repo2")
test(t, "try.gitea.io:user2/repo2.git")
}) })
} }
@ -199,23 +183,30 @@ func TestComposeSSHCloneURL(t *testing.T) {
setting.SSH.Domain = "domain" setting.SSH.Domain = "domain"
setting.SSH.Port = 22 setting.SSH.Port = 22
setting.Repository.UseCompatSSHURI = false setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
setting.Repository.UseCompatSSHURI = true setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
// test SSH_DOMAIN while use non-standard SSH port // test SSH_DOMAIN while use non-standard SSH port
setting.SSH.Port = 123 setting.SSH.Port = 123
setting.Repository.UseCompatSSHURI = false setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
setting.Repository.UseCompatSSHURI = true setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
// test IPv6 SSH_DOMAIN // test IPv6 SSH_DOMAIN
setting.Repository.UseCompatSSHURI = false setting.Repository.UseCompatSSHURI = false
setting.SSH.Domain = "::1" setting.SSH.Domain = "::1"
setting.SSH.Port = 22 setting.SSH.Port = 22
assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL("user", "repo")) assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL(nil, "user", "repo"))
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(nil, "user", "repo"))
setting.SSH.User = "(DOER_USERNAME)"
setting.SSH.Domain = "domain"
setting.SSH.Port = 22
assert.Equal(t, "doer@domain:user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
setting.SSH.Port = 123
assert.Equal(t, "ssh://doer@domain:123/user/repo.git", ComposeSSHCloneURL(&user_model.User{Name: "doer"}, "user", "repo"))
} }
func TestIsUsableRepoName(t *testing.T) { func TestIsUsableRepoName(t *testing.T) {

View File

@ -46,6 +46,12 @@ func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string)
return err return err
} }
// UpdateRepositoryColsNoAutoTime updates repository's columns and but applies time change automatically
func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).NoAutoTime().Update(repo)
return err
}
// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error. // ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error.
type ErrReachLimitOfRepo struct { type ErrReachLimitOfRepo struct {
Limit int Limit int

View File

@ -5,6 +5,7 @@
package repo package repo
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings" "strings"
@ -72,8 +73,8 @@ func (err ErrWikiInvalidFileName) Unwrap() error {
} }
// WikiCloneLink returns clone URLs of repository wiki. // WikiCloneLink returns clone URLs of repository wiki.
func (repo *Repository) WikiCloneLink() *CloneLink { func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User) *CloneLink {
return repo.cloneLink(true) return repo.cloneLink(ctx, doer, repo.Name+".wiki")
} }
// WikiPath returns wiki data path by given user and repository name. // WikiPath returns wiki data path by given user and repository name.

View File

@ -4,6 +4,7 @@
package repo_test package repo_test
import ( import (
"context"
"path/filepath" "path/filepath"
"testing" "testing"
@ -18,7 +19,7 @@ func TestRepository_WikiCloneLink(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
cloneLink := repo.WikiCloneLink() cloneLink := repo.WikiCloneLink(context.Background(), nil)
assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH) assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH)
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS) assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
} }

View File

@ -84,6 +84,7 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
setting.IsInTesting = true setting.IsInTesting = true
setting.AppURL = "https://try.gitea.io/" setting.AppURL = "https://try.gitea.io/"
setting.Domain = "try.gitea.io"
setting.RunUser = "runuser" setting.RunUser = "runuser"
setting.SSH.User = "sshuser" setting.SSH.User = "sshuser"
setting.SSH.BuiltinServerUser = "builtinuser" setting.SSH.BuiltinServerUser = "builtinuser"

View File

@ -11,6 +11,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestGetUserOpenIDs(t *testing.T) { func TestGetUserOpenIDs(t *testing.T) {
@ -34,30 +35,23 @@ func TestGetUserOpenIDs(t *testing.T) {
func TestToggleUserOpenIDVisibility(t *testing.T) { func TestToggleUserOpenIDVisibility(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
oids, err := user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) oids, err := user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
if !assert.NoError(t, err) || !assert.Len(t, oids, 1) { require.NoError(t, err)
return require.Len(t, oids, 1)
}
assert.True(t, oids[0].Show) assert.True(t, oids[0].Show)
err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID) err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
if !assert.NoError(t, err) || !assert.Len(t, oids, 1) { require.NoError(t, err)
return require.Len(t, oids, 1)
}
assert.False(t, oids[0].Show) assert.False(t, oids[0].Show)
err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID) err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2)) oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
if assert.Len(t, oids, 1) { if assert.Len(t, oids, 1) {
assert.True(t, oids[0].Show) assert.True(t, oids[0].Show)
} }

View File

@ -3,7 +3,11 @@
package analyze package analyze
import "testing" import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsVendor(t *testing.T) { func TestIsVendor(t *testing.T) {
tests := []struct { tests := []struct {
@ -33,9 +37,8 @@ func TestIsVendor(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) { t.Run(tt.path, func(t *testing.T) {
if got := IsVendor(tt.path); got != tt.want { got := IsVendor(tt.path)
t.Errorf("IsVendor() = %v, want %v", got, tt.want) assert.Equal(t, tt.want, got)
}
}) })
} }
} }

View File

@ -6,6 +6,9 @@ package openid
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
type testDiscoveredInfo struct{} type testDiscoveredInfo struct{}
@ -29,21 +32,17 @@ func TestTimedDiscoveryCache(t *testing.T) {
dc.Put("foo", &testDiscoveredInfo{}) // openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"}) dc.Put("foo", &testDiscoveredInfo{}) // openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
// Make sure we can retrieve them // Make sure we can retrieve them
if di := dc.Get("foo"); di == nil { di := dc.Get("foo")
t.Errorf("Expected a result, got nil") require.NotNil(t, di)
} else if di.OpEndpoint() != "opEndpoint" || di.OpLocalID() != "opLocalID" || di.ClaimedID() != "claimedID" { assert.Equal(t, "opEndpoint", di.OpEndpoint())
t.Errorf("Expected opEndpoint opLocalID claimedID, got %v %v %v", di.OpEndpoint(), di.OpLocalID(), di.ClaimedID()) assert.Equal(t, "opLocalID", di.OpLocalID())
} assert.Equal(t, "claimedID", di.ClaimedID())
// Attempt to get a non-existent value // Attempt to get a non-existent value
if di := dc.Get("bar"); di != nil { assert.Nil(t, dc.Get("bar"))
t.Errorf("Expected nil, got %v", di)
}
// Sleep one second and try retrieve again // Sleep one second and try retrieve again
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
if di := dc.Get("foo"); di != nil { assert.Nil(t, dc.Get("foo"))
t.Errorf("Expected a nil, got a result")
}
} }

View File

@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
result, err := Auth("gitea", "user1", "false-pwd") result, err := Auth("gitea", "user1", "false-pwd")
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, "Authentication failure") assert.EqualError(t, err, "Authentication failure")
assert.Len(t, result) assert.Empty(t, result)
} }

View File

@ -4,46 +4,57 @@
package pwn package pwn
import ( import (
"errors"
"io"
"net/http" "net/http"
"strings"
"testing" "testing"
"time"
"github.com/h2non/gock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var client = New(WithHTTP(&http.Client{ type mockTransport struct{}
Timeout: time.Second * 2,
})) func (mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if req.URL.Host != "api.pwnedpasswords.com" {
return nil, errors.New("unsupported host")
}
respMap := map[string]string{
"/range/5c1d8": "EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2",
"/range/ba189": "FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4",
"/range/a1733": "C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0",
"/range/5617b": "FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0",
"/range/79082": "FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0",
}
if resp, ok := respMap[req.URL.Path]; ok {
return &http.Response{Request: req, Body: io.NopCloser(strings.NewReader(resp))}, nil
}
return nil, errors.New("unsupported path")
}
func TestPassword(t *testing.T) { func TestPassword(t *testing.T) {
defer gock.Off() client := New(WithHTTP(&http.Client{Transport: mockTransport{}}))
count, err := client.CheckPassword("", false) count, err := client.CheckPassword("", false)
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword") assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
assert.Equal(t, -1, count) assert.Equal(t, -1, count)
gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
count, err = client.CheckPassword("pwned", false) count, err = client.CheckPassword("pwned", false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, count) assert.Equal(t, 1, count)
gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
count, err = client.CheckPassword("notpwned", false) count, err = client.CheckPassword("notpwned", false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
count, err = client.CheckPassword("paddedpwned", true) count, err = client.CheckPassword("paddedpwned", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, count) assert.Equal(t, 1, count)
gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
count, err = client.CheckPassword("paddednotpwned", true) count, err = client.CheckPassword("paddednotpwned", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
count, err = client.CheckPassword("paddednotpwnedzero", true) count, err = client.CheckPassword("paddednotpwnedzero", true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, count) assert.Equal(t, 0, count)

View File

@ -5,7 +5,6 @@
package emoji package emoji
import ( import (
"reflect"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -22,32 +21,18 @@ func TestLookup(t *testing.T) {
c := FromAlias(":beer:") c := FromAlias(":beer:")
d := FromAlias("beer") d := FromAlias("beer")
if !reflect.DeepEqual(a, b) { assert.Equal(t, a, b)
t.Errorf("a and b should equal") assert.Equal(t, b, c)
} assert.Equal(t, c, d)
if !reflect.DeepEqual(b, c) { assert.Equal(t, a, d)
t.Errorf("b and c should equal")
}
if !reflect.DeepEqual(c, d) {
t.Errorf("c and d should equal")
}
if !reflect.DeepEqual(a, d) {
t.Errorf("a and d should equal")
}
m := FromCode("\U0001f44d") m := FromCode("\U0001f44d")
n := FromAlias(":thumbsup:") n := FromAlias(":thumbsup:")
o := FromAlias("+1") o := FromAlias("+1")
if !reflect.DeepEqual(m, n) { assert.Equal(t, m, n)
t.Errorf("m and n should equal") assert.Equal(t, m, o)
} assert.Equal(t, n, o)
if !reflect.DeepEqual(n, o) {
t.Errorf("n and o should equal")
}
if !reflect.DeepEqual(m, o) {
t.Errorf("m and o should equal")
}
} }
func TestReplacers(t *testing.T) { func TestReplacers(t *testing.T) {
@ -61,9 +46,7 @@ func TestReplacers(t *testing.T) {
for i, x := range tests { for i, x := range tests {
s := x.f(x.v) s := x.f(x.v)
if s != x.exp { assert.Equalf(t, x.exp, s, "test %d `%s` expected `%s`, got: `%s`", i, x.v, x.exp, s)
t.Errorf("test %d `%s` expected `%s`, got: `%s`", i, x.v, x.exp, s)
}
} }
} }

View File

@ -6,6 +6,9 @@ package eventsource
import ( import (
"bytes" "bytes"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_wrapNewlines(t *testing.T) { func Test_wrapNewlines(t *testing.T) {
@ -38,16 +41,10 @@ func Test_wrapNewlines(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{} w := &bytes.Buffer{}
gotSum, err := wrapNewlines(w, []byte(tt.prefix), []byte(tt.value)) gotSum, err := wrapNewlines(w, []byte(tt.prefix), []byte(tt.value))
if err != nil { require.NoError(t, err)
t.Errorf("wrapNewlines() error = %v", err)
return assert.EqualValues(t, len(tt.output), gotSum)
} assert.Equal(t, tt.output, w.String())
if gotSum != int64(len(tt.output)) {
t.Errorf("wrapNewlines() = %v, want %v", gotSum, int64(len(tt.output)))
}
if gotW := w.String(); gotW != tt.output {
t.Errorf("wrapNewlines() = %v, want %v", gotW, tt.output)
}
}) })
} }
} }

View File

@ -17,9 +17,7 @@ func TestBlob_Data(t *testing.T) {
output := "file2\n" output := "file2\n"
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := openRepositoryWithDefaultContext(bareRepo1Path) repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
if !assert.NoError(t, err) { require.NoError(t, err)
t.Fatal()
}
defer repo.Close() defer repo.Close()
testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375") testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")

View File

@ -7,5 +7,5 @@ package git
type CommitInfo struct { type CommitInfo struct {
Entry *TreeEntry Entry *TreeEntry
Commit *Commit Commit *Commit
SubModuleFile *CommitSubModuleFile SubmoduleFile *CommitSubmoduleFile
} }

View File

@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} else if subModule != nil { } else if subModule != nil {
subModuleURL = subModule.URL subModuleURL = subModule.URL
} }
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubModuleFile = subModuleFile commitsInfo[i].SubmoduleFile = subModuleFile
} }
} }

View File

@ -79,8 +79,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} else if subModule != nil { } else if subModule != nil {
subModuleURL = subModule.URL subModuleURL = subModule.URL
} }
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String()) subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
commitsInfo[i].SubModuleFile = subModuleFile commitsInfo[i].SubmoduleFile = subModuleFile
} }
} }

View File

@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCommitsCountSha256(t *testing.T) { func TestCommitsCountSha256(t *testing.T) {
@ -94,9 +95,7 @@ signed commit`
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
assert.NoError(t, err) assert.NoError(t, err)
if !assert.NotNil(t, commitFromReader) { require.NotNil(t, commitFromReader)
return
}
assert.EqualValues(t, sha, commitFromReader.ID) assert.EqualValues(t, sha, commitFromReader.ID)
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----

View File

@ -3,6 +3,10 @@
package git package git
type SubmoduleWebLink struct {
RepoWebLink, CommitWebLink string
}
// GetSubModules get all the submodules of current revision git tree // GetSubModules get all the submodules of current revision git tree
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) { func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
if c.submoduleCache != nil { if c.submoduleCache != nil {

View File

@ -5,107 +5,50 @@
package git package git
import ( import (
"fmt" "context"
"net"
"net/url" giturl "code.gitea.io/gitea/modules/git/url"
"path"
"regexp"
"strings"
) )
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) // CommitSubmoduleFile represents a file with submodule type.
type CommitSubmoduleFile struct {
// CommitSubModuleFile represents a file with submodule type.
type CommitSubModuleFile struct {
refURL string refURL string
parsedURL *giturl.RepositoryURL
parsed bool
refID string refID string
repoLink string
} }
// NewCommitSubModuleFile create a new submodule file // NewCommitSubmoduleFile create a new submodule file
func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile { func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile {
return &CommitSubModuleFile{ return &CommitSubmoduleFile{refURL: refURL, refID: refID}
refURL: refURL,
refID: refID,
}
} }
func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { func (sf *CommitSubmoduleFile) RefID() string {
if refURL == "" { return sf.refID // this function is only used in templates
return "" }
// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver
func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
if sf == nil {
return nil
} }
if !sf.parsed {
refURI := strings.TrimSuffix(refURL, ".git") sf.parsed = true
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
prefixURL, _ := url.Parse(urlPrefix)
urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host)
if err != nil { if err != nil {
urlPrefixHostname = prefixURL.Host return nil
} }
sf.parsedURL = parsedURL
urlPrefix = strings.TrimSuffix(urlPrefix, "/") sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
// FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules
// Relative url prefix check (according to git submodule documentation)
if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") {
return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI))
} }
var commitLink string
if !strings.Contains(refURI, "://") { if len(optCommitID) == 2 {
// scp style syntax which contains *no* port number after the : (and is not parsed by net/url) commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
// ex: git@try.gitea.io:go-gitea/gitea } else if len(optCommitID) == 1 {
match := scpSyntax.FindAllStringSubmatch(refURI, -1) commitLink = sf.repoLink + "/commit/" + optCommitID[0]
if len(match) > 0 { } else {
m := match[0] commitLink = sf.repoLink + "/commit/" + sf.refID
refHostname := m[2]
pth := m[3]
if !strings.HasPrefix(pth, "/") {
pth = "/" + pth
} }
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
if urlPrefixHostname == refHostname || refHostname == sshDomain {
return urlPrefix + path.Clean(path.Join("/", pth))
}
return "http://" + refHostname + pth
}
}
ref, err := url.Parse(refURI)
if err != nil {
return ""
}
refHostname, _, err := net.SplitHostPort(ref.Host)
if err != nil {
refHostname = ref.Host
}
supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"}
for _, scheme := range supportedSchemes {
if ref.Scheme == scheme {
if ref.Scheme == "http" || ref.Scheme == "https" {
if len(ref.User.Username()) > 0 {
return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path
}
return ref.Scheme + "://" + ref.Host + ref.Path
} else if urlPrefixHostname == refHostname || refHostname == sshDomain {
return urlPrefix + path.Clean(path.Join("/", ref.Path))
}
return "http://" + refHostname + ref.Path
}
}
return ""
}
// RefURL guesses and returns reference URL.
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
}
// RefID returns reference ID.
func (sf *CommitSubModuleFile) RefID() string {
return sf.refID
} }

View File

@ -1,42 +1,30 @@
// Copyright 2018 The Gitea Authors. All rights reserved. // Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package git package git
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCommitSubModuleFileGetRefURL(t *testing.T) { func TestCommitSubmoduleLink(t *testing.T) {
kases := []struct { sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
refURL string
prefixURL string
parentPath string
SSHDomain string
expect string
}{
{"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"},
{"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"},
{"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"},
{"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"},
{"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"},
{"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"},
{"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
{"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
{"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
{"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"},
{"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"},
{"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"},
{"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"},
{"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""},
{"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"},
{"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"},
{"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"},
}
for _, kase := range kases { wl := sf.SubmoduleWebLink(context.Background())
assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain)) assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
} assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(context.Background(), "1111")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink)
wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(context.Background())
assert.Nil(t, wl)
} }

View File

@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCommitsCount(t *testing.T) { func TestCommitsCount(t *testing.T) {
@ -91,9 +92,7 @@ empty commit`
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
assert.NoError(t, err) assert.NoError(t, err)
if !assert.NotNil(t, commitFromReader) { require.NotNil(t, commitFromReader)
return
}
assert.EqualValues(t, sha, commitFromReader.ID) assert.EqualValues(t, sha, commitFromReader.ID)
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
@ -159,9 +158,7 @@ ISO-8859-1`
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
assert.NoError(t, err) assert.NoError(t, err)
if !assert.NotNil(t, commitFromReader) { require.NotNil(t, commitFromReader)
return
}
assert.EqualValues(t, sha, commitFromReader.ID) assert.EqualValues(t, sha, commitFromReader.ID)
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----

View File

@ -39,7 +39,7 @@ func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.Git
if err != nil { if err != nil {
return nil, err return nil, err
} }
return giturl.Parse(addr) return giturl.ParseGitURL(addr)
} }
// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error. // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
@ -79,6 +79,15 @@ func (err *ErrInvalidCloneAddr) Unwrap() error {
return util.ErrInvalidArgument return util.ErrInvalidArgument
} }
// IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist.
func IsRemoteNotExistError(err error) bool {
// see: https://github.com/go-gitea/gitea/issues/32889#issuecomment-2571848216
// Should not add space in the end, sometimes git will add a `:`
prefix1 := "exit status 128 - fatal: No such remote" // git < 2.30
prefix2 := "exit status 2 - error: No such remote" // git >= 2.30
return strings.HasPrefix(err.Error(), prefix1) || strings.HasPrefix(err.Error(), prefix2)
}
// ParseRemoteAddr checks if given remote address is valid, // ParseRemoteAddr checks if given remote address is valid,
// and returns composed URL with needed username and password. // and returns composed URL with needed username and password.
func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) { func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) {

View File

@ -10,20 +10,18 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestRepository_GetLanguageStats(t *testing.T) { func TestRepository_GetLanguageStats(t *testing.T) {
repoPath := filepath.Join(testReposDir, "language_stats_repo") repoPath := filepath.Join(testReposDir, "language_stats_repo")
gitRepo, err := openRepositoryWithDefaultContext(repoPath) gitRepo, err := openRepositoryWithDefaultContext(repoPath)
if !assert.NoError(t, err) { require.NoError(t, err)
t.Fatal()
}
defer gitRepo.Close() defer gitRepo.Close()
stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3") stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3")
if !assert.NoError(t, err) { require.NoError(t, err)
t.Fatal()
}
assert.EqualValues(t, map[string]int64{ assert.EqualValues(t, map[string]int64{
"Python": 134, "Python": 134,

View File

@ -182,7 +182,6 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
// Annotated tag's name should fail // Annotated tag's name should fail
tag3, err := bareRepo1.GetAnnotatedTag(aTagName) tag3, err := bareRepo1.GetAnnotatedTag(aTagName)
assert.Error(t, err)
assert.Errorf(t, err, "Length must be 40: %d", len(aTagName)) assert.Errorf(t, err, "Length must be 40: %d", len(aTagName))
assert.Nil(t, tag3) assert.Nil(t, tag3)

View File

@ -4,9 +4,15 @@
package url package url
import ( import (
"context"
"fmt" "fmt"
"net"
stdurl "net/url" stdurl "net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
) )
// ErrWrongURLFormat represents an error with wrong url format // ErrWrongURLFormat represents an error with wrong url format
@ -21,7 +27,7 @@ func (err ErrWrongURLFormat) Error() string {
// GitURL represents a git URL // GitURL represents a git URL
type GitURL struct { type GitURL struct {
*stdurl.URL *stdurl.URL
extraMark int // 0 no extra 1 scp 2 file path with no prefix extraMark int // 0: standard URL with scheme, 1: scp short syntax (no scheme), 2: file path with no prefix
} }
// String returns the URL's string // String returns the URL's string
@ -38,8 +44,11 @@ func (u *GitURL) String() string {
} }
} }
// Parse parse all kinds of git URL // ParseGitURL parse all kinds of git URL:
func Parse(remote string) (*GitURL, error) { // * Full URL: http://git@host/path, http://git@host:port/path
// * SCP short syntax: git@host:/path
// * File path: /dir/repo/path
func ParseGitURL(remote string) (*GitURL, error) {
if strings.Contains(remote, "://") { if strings.Contains(remote, "://") {
u, err := stdurl.Parse(remote) u, err := stdurl.Parse(remote)
if err != nil { if err != nil {
@ -87,3 +96,86 @@ func Parse(remote string) (*GitURL, error) {
extraMark: 2, extraMark: 2,
}, nil }, nil
} }
type RepositoryURL struct {
GitURL *GitURL
// if the URL belongs to current Gitea instance, then the below fields have values
OwnerName string
RepoName string
RemainingPath string
}
// ParseRepositoryURL tries to parse a Git URL and extract the owner/repository name if it belongs to current Gitea instance.
func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, error) {
// possible urls for git:
// https://my.domain/sub-path/<owner>/<repo>[.git]
// git+ssh://user@my.domain/<owner>/<repo>[.git]
// ssh://user@my.domain/<owner>/<repo>[.git]
// user@my.domain:<owner>/<repo>[.git]
parsed, err := ParseGitURL(repoURL)
if err != nil {
return nil, err
}
ret := &RepositoryURL{}
ret.GitURL = parsed
fillPathParts := func(s string) {
s = strings.TrimPrefix(s, "/")
fields := strings.SplitN(s, "/", 3)
if len(fields) >= 2 {
ret.OwnerName = fields[0]
ret.RepoName = strings.TrimSuffix(fields[1], ".git")
if len(fields) == 3 {
ret.RemainingPath = "/" + fields[2]
}
}
}
if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
return ret, nil
}
fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
domainSSH := setting.SSH.Domain
domainCur := httplib.GuessCurrentHostDomain(ctx)
urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
urlDomain = util.IfZero(urlDomain, parsed.URL.Host)
if urlDomain == "" {
return ret, nil
}
// check whether URL domain is the App domain
domainMatches := domainSSH == urlDomain
// check whether URL domain is current domain from context
domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain)
if domainMatches {
fillPathParts(parsed.URL.Path)
}
}
return ret, nil
}
// MakeRepositoryWebLink generates a web link (http/https) for a git repository (by guessing sometimes)
func MakeRepositoryWebLink(repoURL *RepositoryURL) string {
if repoURL.OwnerName != "" {
return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + repoURL.RepoName
}
// now, let's guess, for example:
// * git@github.com:owner/submodule.git
// * https://github.com/example/submodule1.git
if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" {
return strings.TrimSuffix(repoURL.GitURL.String(), ".git")
} else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" {
hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host)
hostname = util.IfZero(hostname, repoURL.GitURL.Host)
urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git")
urlPath = strings.TrimPrefix(urlPath, "/")
urlFull := fmt.Sprintf("https://%s/%s", hostname, urlPath)
urlFull = strings.TrimSuffix(urlFull, "/")
return urlFull
}
return ""
}

View File

@ -4,9 +4,15 @@
package url package url
import ( import (
"context"
"net/http"
"net/url" "net/url"
"testing" "testing"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -157,10 +163,105 @@ func TestParseGitURLs(t *testing.T) {
for _, kase := range kases { for _, kase := range kases {
t.Run(kase.kase, func(t *testing.T) { t.Run(kase.kase, func(t *testing.T) {
u, err := Parse(kase.kase) u, err := ParseGitURL(kase.kase)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, kase.expected.extraMark, u.extraMark) assert.EqualValues(t, kase.expected.extraMark, u.extraMark)
assert.EqualValues(t, *kase.expected, *u) assert.EqualValues(t, *kase.expected, *u)
}) })
} }
} }
func TestParseRepositoryURL(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")()
defer test.MockVariableValue(&setting.SSH.Domain, "try.gitea.io")()
ctxURL, _ := url.Parse("https://gitea")
ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
ctxReq.Host = ctxURL.Host
ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
cases := []struct {
input string
ownerName, repoName, remaining string
}{
{input: "/user/repo"},
{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"},
{input: "https://external:3000/user/repo"},
{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"},
{input: "https://gitea:3333/user/repo"},
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
{input: "ssh://external:2222/user/repo"},
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
{input: "git+ssh://user@external/user/repo.git"},
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@external:user/repo.git"},
}
for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
ret, _ := ParseRepositoryURL(ctx, c.input)
assert.Equal(t, c.ownerName, ret.OwnerName)
assert.Equal(t, c.repoName, ret.RepoName)
assert.Equal(t, c.remaining, ret.RemainingPath)
})
}
t.Run("WithSubpath", func(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
cases = []struct {
input string
ownerName, repoName, remaining string
}{
{input: "https://localhost:3000/user/repo"},
{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
{input: "ssh://external:2222/user/repo"},
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
{input: "git+ssh://user@external/user/repo.git"},
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
{input: "root@external:user/repo.git"},
}
for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
ret, _ := ParseRepositoryURL(ctx, c.input)
assert.Equal(t, c.ownerName, ret.OwnerName)
assert.Equal(t, c.repoName, ret.RepoName)
assert.Equal(t, c.remaining, ret.RemainingPath)
})
}
})
}
func TestMakeRepositoryBaseLink(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
u, err := ParseRepositoryURL(context.Background(), "https://localhost:3000/subpath/user/repo.git")
assert.NoError(t, err)
assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u))
u, err = ParseRepositoryURL(context.Background(), "https://github.com/owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
u, err = ParseRepositoryURL(context.Background(), "git@github.com:owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
u, err = ParseRepositoryURL(context.Background(), "git+ssh://other:123/owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u))
}

View File

@ -10,6 +10,8 @@ import (
"testing" "testing"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"github.com/stretchr/testify/assert"
) )
func BenchmarkGetCommitGraph(b *testing.B) { func BenchmarkGetCommitGraph(b *testing.B) {
@ -235,9 +237,7 @@ func TestParseGlyphs(t *testing.T) {
} }
row++ row++
} }
if len(parser.availableColors) != 9 { assert.Len(t, parser.availableColors, 9)
t.Errorf("Expected 9 colors but have %d", len(parser.availableColors))
}
} }
func TestCommitStringParsing(t *testing.T) { func TestCommitStringParsing(t *testing.T) {
@ -262,9 +262,7 @@ func TestCommitStringParsing(t *testing.T) {
return return
} }
if test.commitMessage != commit.Subject { assert.Equal(t, test.commitMessage, commit.Subject)
t.Errorf("%s does not match %s", test.commitMessage, commit.Subject)
}
}) })
} }
} }

View File

@ -30,7 +30,7 @@ func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int
return size, class return size, class
} }
func HTMLFormat(s string, rawArgs ...any) template.HTML { func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
args := slices.Clone(rawArgs) args := slices.Clone(rawArgs)
for i, v := range args { for i, v := range args {
switch v := v.(type) { switch v := v.(type) {
@ -44,5 +44,5 @@ func HTMLFormat(s string, rawArgs ...any) template.HTML {
args[i] = template.HTMLEscapeString(fmt.Sprint(v)) args[i] = template.HTMLEscapeString(fmt.Sprint(v))
} }
} }
return template.HTML(fmt.Sprintf(s, args...)) return template.HTML(fmt.Sprintf(string(s), args...))
} }

View File

@ -13,6 +13,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestServeContentByReader(t *testing.T) { func TestServeContentByReader(t *testing.T) {
@ -71,9 +72,7 @@ func TestServeContentByReadSeeker(t *testing.T) {
} }
seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644) seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
defer seekReader.Close() defer seekReader.Close()
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@ -5,6 +5,7 @@ package httplib
import ( import (
"context" "context"
"net"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -81,6 +82,12 @@ func GuessCurrentHostURL(ctx context.Context) string {
return reqScheme + "://" + req.Host return reqScheme + "://" + req.Host
} }
func GuessCurrentHostDomain(ctx context.Context) string {
_, host, _ := strings.Cut(GuessCurrentHostURL(ctx), "://")
domain, _, _ := net.SplitHostPort(host)
return util.IfZero(domain, host)
}
// MakeAbsoluteURL tries to make a link to an absolute URL: // MakeAbsoluteURL tries to make a link to an absolute URL:
// * If link is empty, it returns the current app URL. // * If link is empty, it returns the current app URL.
// * If link is absolute, it returns the link. // * If link is absolute, it returns the link.
@ -105,7 +112,7 @@ func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
if cleanedPath == "" || cleanedPath == "." { if cleanedPath == "" || cleanedPath == "." {
u.Path = "/" u.Path = "/"
} else { } else {
u.Path += "/" + cleanedPath + "/" u.Path = "/" + cleanedPath + "/"
} }
} }
if urlIsRelative(s, u) { if urlIsRelative(s, u) {

View File

@ -11,6 +11,8 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/indexer/issues/internal/tests" "code.gitea.io/gitea/modules/indexer/issues/internal/tests"
"github.com/stretchr/testify/require"
) )
func TestElasticsearchIndexer(t *testing.T) { func TestElasticsearchIndexer(t *testing.T) {
@ -26,20 +28,10 @@ func TestElasticsearchIndexer(t *testing.T) {
} }
} }
ok := false require.Eventually(t, func() bool {
for i := 0; i < 60; i++ {
resp, err := http.Get(url) resp, err := http.Get(url)
if err == nil && resp.StatusCode == http.StatusOK { return err == nil && resp.StatusCode == http.StatusOK
ok = true }, time.Minute, time.Second, "Expected elasticsearch to be up")
break
}
t.Logf("Waiting for elasticsearch to be up: %v", err)
time.Sleep(time.Second)
}
if !ok {
t.Fatalf("Failed to wait for elasticsearch to be up")
return
}
indexer := NewIndexer(url, fmt.Sprintf("test_elasticsearch_indexer_%d", time.Now().Unix())) indexer := NewIndexer(url, fmt.Sprintf("test_elasticsearch_indexer_%d", time.Now().Unix()))
defer indexer.Close() defer indexer.Close()

View File

@ -19,6 +19,7 @@ import (
_ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/activities"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -26,7 +27,7 @@ func TestMain(m *testing.M) {
} }
func TestDBSearchIssues(t *testing.T) { func TestDBSearchIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) require.NoError(t, unittest.PrepareTestDatabase())
setting.Indexer.IssueType = "db" setting.Indexer.IssueType = "db"
InitIssueIndexer(true) InitIssueIndexer(true)
@ -83,9 +84,7 @@ func searchIssueWithKeyword(t *testing.T) {
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -120,9 +119,7 @@ func searchIssueByIndex(t *testing.T) {
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -166,9 +163,7 @@ func searchIssueInRepo(t *testing.T) {
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -238,9 +233,7 @@ func searchIssueByID(t *testing.T) {
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -265,9 +258,7 @@ func searchIssueIsPull(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -292,9 +283,7 @@ func searchIssueIsClosed(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -319,9 +308,7 @@ func searchIssueIsArchived(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -346,9 +333,7 @@ func searchIssueByMilestoneID(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -379,9 +364,7 @@ func searchIssueByLabelID(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -400,9 +383,7 @@ func searchIssueByTime(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -421,9 +402,7 @@ func searchIssueWithOrder(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -454,9 +433,7 @@ func searchIssueInProject(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts) issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
} }
} }
@ -479,9 +456,7 @@ func searchIssueWithPaginator(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
issueIDs, total, err := SearchIssues(context.TODO(), &test.opts) issueIDs, total, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
assert.Equal(t, test.expectedIDs, issueIDs) assert.Equal(t, test.expectedIDs, issueIDs)
assert.Equal(t, test.expectedTotal, total) assert.Equal(t, test.expectedTotal, total)
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/meilisearch/meilisearch-go" "github.com/meilisearch/meilisearch-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestMeilisearchIndexer(t *testing.T) { func TestMeilisearchIndexer(t *testing.T) {
@ -32,20 +33,10 @@ func TestMeilisearchIndexer(t *testing.T) {
key = os.Getenv("TEST_MEILISEARCH_KEY") key = os.Getenv("TEST_MEILISEARCH_KEY")
} }
ok := false require.Eventually(t, func() bool {
for i := 0; i < 60; i++ {
resp, err := http.Get(url) resp, err := http.Get(url)
if err == nil && resp.StatusCode == http.StatusOK { return err == nil && resp.StatusCode == http.StatusOK
ok = true }, time.Minute, time.Second, "Expected meilisearch to be up")
break
}
t.Logf("Waiting for meilisearch to be up: %v", err)
time.Sleep(time.Second)
}
if !ok {
t.Fatalf("Failed to wait for meilisearch to be up")
return
}
indexer := NewIndexer(url, key, fmt.Sprintf("test_meilisearch_indexer_%d", time.Now().Unix())) indexer := NewIndexer(url, key, fmt.Sprintf("test_meilisearch_indexer_%d", time.Now().Unix()))
defer indexer.Close() defer indexer.Close()

View File

@ -957,9 +957,8 @@ func Test_minQuotes(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := minQuotes(tt.args.value); got != tt.want { got := minQuotes(tt.args.value)
t.Errorf("minQuotes() = %v, want %v", got, tt.want) assert.Equal(t, tt.want, got)
}
}) })
} }
} }

View File

@ -76,7 +76,7 @@ func (r *RenderInternal) ProtectSafeAttrs(content template.HTML) template.HTML {
return template.HTML(reAttrClass().ReplaceAllString(string(content), `$1 data-attr-class="`+r.secureIDPrefix+`$2"$3`)) return template.HTML(reAttrClass().ReplaceAllString(string(content), `$1 data-attr-class="`+r.secureIDPrefix+`$2"$3`))
} }
func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt string, a ...any) error { func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt template.HTML, a ...any) error {
_, err := w.Write([]byte(r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...)))) _, err := w.Write([]byte(r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...))))
return err return err
} }

View File

@ -4,6 +4,8 @@
package math package math
import ( import (
"html/template"
"code.gitea.io/gitea/modules/markup/internal" "code.gitea.io/gitea/modules/markup/internal"
giteaUtil "code.gitea.io/gitea/modules/util" giteaUtil "code.gitea.io/gitea/modules/util"
@ -50,7 +52,7 @@ func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.N
n := node.(*Block) n := node.(*Block)
if entering { if entering {
code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">` code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
_ = r.renderInternal.FormatWithSafeAttrs(w, code) _ = r.renderInternal.FormatWithSafeAttrs(w, template.HTML(code))
r.writeLines(w, source, n) r.writeLines(w, source, n)
} else { } else {
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n") _, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")

View File

@ -7,6 +7,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -140,23 +142,13 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
Icon: "table", Icon: "table",
Lang: "", Lang: "",
} }
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil { err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got)
t.Errorf("RenderConfig.UnmarshalYAML() error = %v\n%q", err, tt.args) require.NoError(t, err)
return
}
if got.Meta != tt.expected.Meta { assert.Equal(t, tt.expected.Meta, got.Meta)
t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta) assert.Equal(t, tt.expected.Icon, got.Icon)
} assert.Equal(t, tt.expected.Lang, got.Lang)
if got.Icon != tt.expected.Icon { assert.Equal(t, tt.expected.TOC, got.TOC)
t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
}
if got.Lang != tt.expected.Lang {
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
}
if got.TOC != tt.expected.TOC {
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
}
}) })
} }
} }

View File

@ -147,7 +147,7 @@ func (r *orgWriter) resolveLink(kind, link string) string {
func (r *orgWriter) WriteRegularLink(l org.RegularLink) { func (r *orgWriter) WriteRegularLink(l org.RegularLink) {
link := r.resolveLink(l.Kind(), l.URL) link := r.resolveLink(l.Kind(), l.URL)
printHTML := func(html string, a ...any) { printHTML := func(html template.HTML, a ...any) {
_, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...)) _, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...))
} }
// Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427 // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427

View File

@ -103,8 +103,8 @@ func HelloWorld() {
} }
#+end_src #+end_src
`, `<div class="src src-go"> `, `<div class="src src-go">
<pre><code class="chroma language-go"><span class="c1">// HelloWorld prints &#34;Hello World&#34; <pre><code class="chroma language-go"><span class="c1">// HelloWorld prints &#34;Hello World&#34;</span>
</span><span class="c1"></span><span class="kd">func</span> <span class="nf">HelloWorld</span><span class="p">()</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">HelloWorld</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Hello World&#34;</span><span class="p">)</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Hello World&#34;</span><span class="p">)</span>
<span class="p">}</span></code></pre> <span class="p">}</span></code></pre>
</div>`) </div>`)

View File

@ -5,6 +5,9 @@ package nosql
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestToRedisURI(t *testing.T) { func TestToRedisURI(t *testing.T) {
@ -26,9 +29,9 @@ func TestToRedisURI(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := ToRedisURI(tt.connection); got == nil || got.String() != tt.want { got := ToRedisURI(tt.connection)
t.Errorf(`ToRedisURI(%q) = %s, want %s`, tt.connection, got.String(), tt.want) require.NotNil(t, got)
} assert.Equal(t, tt.want, got.String())
}) })
} }
} }

View File

@ -81,6 +81,7 @@ type PackageMetadataVersion struct {
BundleDependencies []string `json:"bundleDependencies,omitempty"` BundleDependencies []string `json:"bundleDependencies,omitempty"`
DevDependencies map[string]string `json:"devDependencies,omitempty"` DevDependencies map[string]string `json:"devDependencies,omitempty"`
PeerDependencies map[string]string `json:"peerDependencies,omitempty"` PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
PeerDependenciesMeta map[string]any `json:"peerDependenciesMeta,omitempty"`
Bin map[string]string `json:"bin,omitempty"` Bin map[string]string `json:"bin,omitempty"`
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"` OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
Readme string `json:"readme,omitempty"` Readme string `json:"readme,omitempty"`
@ -222,6 +223,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
BundleDependencies: meta.BundleDependencies, BundleDependencies: meta.BundleDependencies,
DevelopmentDependencies: meta.DevDependencies, DevelopmentDependencies: meta.DevDependencies,
PeerDependencies: meta.PeerDependencies, PeerDependencies: meta.PeerDependencies,
PeerDependenciesMeta: meta.PeerDependenciesMeta,
OptionalDependencies: meta.OptionalDependencies, OptionalDependencies: meta.OptionalDependencies,
Bin: meta.Bin, Bin: meta.Bin,
Readme: meta.Readme, Readme: meta.Readme,

View File

@ -19,6 +19,7 @@ type Metadata struct {
BundleDependencies []string `json:"bundleDependencies,omitempty"` BundleDependencies []string `json:"bundleDependencies,omitempty"`
DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"` DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
PeerDependencies map[string]string `json:"peer_dependencies,omitempty"` PeerDependencies map[string]string `json:"peer_dependencies,omitempty"`
PeerDependenciesMeta map[string]any `json:"peer_dependencies_meta,omitempty"`
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"` OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`
Bin map[string]string `json:"bin,omitempty"` Bin map[string]string `json:"bin,omitempty"`
Readme string `json:"readme,omitempty"` Readme string `json:"readme,omitempty"`

View File

@ -11,6 +11,7 @@ import (
"gitea.com/lunny/levelqueue" "gitea.com/lunny/levelqueue"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
) )
@ -29,9 +30,7 @@ func TestCorruptedLevelQueue(t *testing.T) {
// sometimes the levelqueue could be in a corrupted state, this test is to make sure it can recover from it // sometimes the levelqueue could be in a corrupted state, this test is to make sure it can recover from it
dbDir := t.TempDir() + "/levelqueue-test" dbDir := t.TempDir() + "/levelqueue-test"
db, err := leveldb.OpenFile(dbDir, nil) db, err := leveldb.OpenFile(dbDir, nil)
if !assert.NoError(t, err) { require.NoError(t, err)
return
}
defer db.Close() defer db.Close()
assert.NoError(t, db.Put([]byte("other-key"), []byte("other-value"), nil)) assert.NoError(t, db.Put([]byte("other-key"), []byte("other-value"), nil))

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func waitRedisReady(conn string, dur time.Duration) (ready bool) { func waitRedisReady(conn string, dur time.Duration) (ready bool) {
@ -61,9 +62,7 @@ func TestBaseRedis(t *testing.T) {
return return
} }
assert.NoError(t, redisServer.Start()) assert.NoError(t, redisServer.Start())
if !assert.True(t, waitRedisReady("redis://127.0.0.1:6379/0", 5*time.Second), "start redis-server") { require.True(t, waitRedisReady("redis://127.0.0.1:6379/0", 5*time.Second), "start redis-server")
return
}
} }
testQueueBasic(t, newBaseRedisSimple, toBaseConfig("baseRedis", setting.QueueSettings{Length: 10}), false) testQueueBasic(t, newBaseRedisSimple, toBaseConfig("baseRedis", setting.QueueSettings{Length: 10}), false)

View File

@ -6,6 +6,7 @@ package repository
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -51,6 +52,9 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
{ {
branches, _, err := gitRepo.GetBranchNames(0, 0) branches, _, err := gitRepo.GetBranchNames(0, 0)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "ref file is empty") {
return 0, nil
}
return 0, err return 0, err
} }
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches) log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)

View File

@ -5,6 +5,8 @@ package structs
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestNoBetterThan(t *testing.T) { func TestNoBetterThan(t *testing.T) {
@ -166,9 +168,7 @@ func TestNoBetterThan(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := tt.args.css.NoBetterThan(tt.args.css2) result := tt.args.css.NoBetterThan(tt.args.css2)
if result != tt.want { assert.Equal(t, tt.want, result)
t.Errorf("NoBetterThan() = %v, want %v", result, tt.want)
}
}) })
} }
} }

View File

@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap {
"Iif": iif, "Iif": iif,
"Eval": evalTokens, "Eval": evalTokens,
"SafeHTML": safeHTML, "SafeHTML": safeHTML,
"HTMLFormat": htmlutil.HTMLFormat, "HTMLFormat": htmlFormat,
"HTMLEscape": htmlEscape, "HTMLEscape": htmlEscape,
"QueryEscape": queryEscape, "QueryEscape": queryEscape,
"QueryBuild": QueryBuild, "QueryBuild": QueryBuild,
@ -207,6 +207,20 @@ func htmlEscape(s any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s)) panic(fmt.Sprintf("unexpected type %T", s))
} }
func htmlFormat(s any, args ...any) template.HTML {
if len(args) == 0 {
// to prevent developers from calling "HTMLFormat $userInput" by mistake which will lead to XSS
panic("missing arguments for HTMLFormat")
}
switch v := s.(type) {
case string:
return htmlutil.HTMLFormat(template.HTML(v), args...)
case template.HTML:
return htmlutil.HTMLFormat(v, args...)
}
panic(fmt.Sprintf("unexpected type %T", s))
}
func jsEscapeSafe(s string) template.HTML { func jsEscapeSafe(s string) template.HTML {
return template.HTML(template.JSEscapeString(s)) return template.HTML(template.JSEscapeString(s))
} }

View File

@ -8,7 +8,6 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -88,7 +87,7 @@ func TestTemplateIif(t *testing.T) {
func TestTemplateEscape(t *testing.T) { func TestTemplateEscape(t *testing.T) {
execTmpl := func(code string) string { execTmpl := func(code string) string {
tmpl := template.New("test") tmpl := template.New("test")
tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlutil.HTMLFormat}) tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlFormat})
template.Must(tmpl.Parse(code)) template.Must(tmpl.Parse(code))
w := &strings.Builder{} w := &strings.Builder{}
assert.NoError(t, tmpl.Execute(w, nil)) assert.NoError(t, tmpl.Execute(w, nil))

View File

@ -150,7 +150,7 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
return ret return ret
} }
u, err := giturl.Parse(remoteURL) u, err := giturl.ParseGitURL(remoteURL)
if err != nil { if err != nil {
log.Error("giturl.Parse %v", err) log.Error("giturl.Parse %v", err)
return ret return ret

View File

@ -8,6 +8,9 @@ import (
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func getWhoamiOutput() (string, error) { func getWhoamiOutput() (string, error) {
@ -20,24 +23,19 @@ func getWhoamiOutput() (string, error) {
func TestCurrentUsername(t *testing.T) { func TestCurrentUsername(t *testing.T) {
user := CurrentUsername() user := CurrentUsername()
if len(user) == 0 { require.NotEmpty(t, user)
t.Errorf("expected non-empty user, got: %s", user)
}
// Windows whoami is weird, so just skip remaining tests // Windows whoami is weird, so just skip remaining tests
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipped test because of weird whoami on Windows") t.Skip("skipped test because of weird whoami on Windows")
} }
whoami, err := getWhoamiOutput() whoami, err := getWhoamiOutput()
if err != nil { require.NoError(t, err)
t.Errorf("failed to run whoami to test current user: %f", err)
}
user = CurrentUsername() user = CurrentUsername()
if user != whoami { assert.Equal(t, whoami, user)
t.Errorf("expected %s as user, got: %s", whoami, user)
}
t.Setenv("USER", "spoofed") t.Setenv("USER", "spoofed")
user = CurrentUsername() user = CurrentUsername()
if user != whoami { assert.Equal(t, whoami, user)
t.Errorf("expected %s as user, got: %s", whoami, user)
}
} }

View File

@ -3,7 +3,11 @@
package util package util
import "testing" import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestShellEscape(t *testing.T) { func TestShellEscape(t *testing.T) {
tests := []struct { tests := []struct {
@ -83,9 +87,7 @@ func TestShellEscape(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := ShellEscape(tt.toEscape); got != tt.want { assert.Equal(t, tt.want, ShellEscape(tt.toEscape))
t.Errorf("ShellEscape(%q):\nGot: %s\nWanted: %s", tt.toEscape, got, tt.want)
}
}) })
} }
} }

View File

@ -89,11 +89,23 @@ func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool {
return true return true
} }
func isValidMethod(name string) bool {
switch name {
case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodHead, http.MethodOptions, http.MethodConnect, http.MethodTrace:
return true
}
return false
}
func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher { func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h) middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc} p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
for _, method := range strings.Split(methods, ",") { for _, method := range strings.Split(methods, ",") {
p.methods.Add(strings.TrimSpace(method)) method = strings.TrimSpace(method)
if !isValidMethod(method) {
panic(fmt.Sprintf("invalid HTTP method: %s", method))
}
p.methods.Add(method)
} }
re := []byte{'^'} re := []byte{'^'}
lastEnd := 0 lastEnd := 0

View File

@ -6,6 +6,8 @@ package routing
import ( import (
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func Test_shortenFilename(t *testing.T) { func Test_shortenFilename(t *testing.T) {
@ -37,9 +39,8 @@ func Test_shortenFilename(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(fmt.Sprintf("shortenFilename('%s')", tt.filename), func(t *testing.T) { t.Run(fmt.Sprintf("shortenFilename('%s')", tt.filename), func(t *testing.T) {
if gotShort := shortenFilename(tt.filename, tt.fallback); gotShort != tt.expected { gotShort := shortenFilename(tt.filename, tt.fallback)
t.Errorf("shortenFilename('%s'), expect '%s', but get '%s'", tt.filename, tt.expected, gotShort) assert.Equal(t, tt.expected, gotShort)
}
}) })
} }
} }
@ -72,9 +73,8 @@ func Test_trimAnonymousFunctionSuffix(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := trimAnonymousFunctionSuffix(tt.name); got != tt.want { got := trimAnonymousFunctionSuffix(tt.name)
t.Errorf("trimAnonymousFunctionSuffix() = %v, want %v", got, tt.want) assert.Equal(t, tt.want, got)
}
}) })
} }
} }

View File

@ -69,6 +69,10 @@ func (h HookEventType) Event() string {
return "" return ""
} }
func (h HookEventType) IsPullRequest() bool {
return h.Event() == "pull_request"
}
// HookType is the type of a webhook // HookType is the type of a webhook
type HookType = string type HookType = string

View File

@ -145,6 +145,7 @@ confirm_delete_selected=Potvrdit odstranění všech vybraných položek?
name=Název name=Název
value=Hodnota value=Hodnota
readme=Readme
filter=Filtr filter=Filtr
filter.clear=Vymazat filtr filter.clear=Vymazat filtr
@ -243,6 +244,7 @@ license_desc=Vše je na <a target="_blank" rel="noopener noreferrer" href="%[1]s
[install] [install]
install=Instalace install=Instalace
installing_desc=Probíhá instalace, čekejte prosím...
title=Výchozí konfigurace title=Výchozí konfigurace
docker_helper=Pokud spouštíte Gitea v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení. docker_helper=Pokud spouštíte Gitea v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
require_db_desc=Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol). require_db_desc=Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
@ -459,6 +461,7 @@ authorize_application=Autorizovat aplikaci
authorize_redirect_notice=Budete přesměrováni na %s, pokud autorizujete tuto aplikaci. authorize_redirect_notice=Budete přesměrováni na %s, pokud autorizujete tuto aplikaci.
authorize_application_created_by=Tuto aplikaci vytvořil %s. authorize_application_created_by=Tuto aplikaci vytvořil %s.
authorize_application_description=Pokud povolíte přístup, bude moci přistupovat a zapisovat do všech vašich informací o účtu včetně soukromých repozitářů a organizací. authorize_application_description=Pokud povolíte přístup, bude moci přistupovat a zapisovat do všech vašich informací o účtu včetně soukromých repozitářů a organizací.
authorize_application_with_scopes=S rozsahy působnosti: %s
authorize_title=Autorizovat „%s“ pro přístup k vašemu účtu? authorize_title=Autorizovat „%s“ pro přístup k vašemu účtu?
authorization_failed=Autorizace selhala authorization_failed=Autorizace selhala
authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný požadavek. Kontaktujte prosím správce aplikace, kterou jste se pokoušeli autorizovat. authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný požadavek. Kontaktujte prosím správce aplikace, kterou jste se pokoušeli autorizovat.
@ -765,6 +768,7 @@ uploaded_avatar_not_a_image=Nahraný soubor není obrázek.
uploaded_avatar_is_too_big=Nahraný soubor (%d KiB) přesahuje maximální velikost (%d KiB). uploaded_avatar_is_too_big=Nahraný soubor (%d KiB) přesahuje maximální velikost (%d KiB).
update_avatar_success=Vaše avatar byl aktualizován. update_avatar_success=Vaše avatar byl aktualizován.
update_user_avatar_success=Uživatelův avatar byl aktualizován. update_user_avatar_success=Uživatelův avatar byl aktualizován.
cropper_prompt=Před uložením můžete obrázek upravit. Upravený obrázek bude uložen jako PNG.
change_password=Aktualizovat heslo change_password=Aktualizovat heslo
old_password=Stávající heslo old_password=Stávající heslo
@ -1012,6 +1016,9 @@ new_repo_helper=Repozitář obsahuje všechny projektové soubory, včetně hist
owner=Vlastník owner=Vlastník
owner_helper=Některé organizace se nemusejí v seznamu zobrazit kvůli maximálnímu dosaženému počtu repozitářů. owner_helper=Některé organizace se nemusejí v seznamu zobrazit kvůli maximálnímu dosaženému počtu repozitářů.
repo_name=Název repozitáře repo_name=Název repozitáře
repo_name_profile_public_hint=.profile je speciální repozitář, který můžete použít k přidání souboru README.md do svého veřejného profilu organizace, který je viditelný pro každého. Ujistěte se, že je veřejný, a pro začátek jej inicializujte pomocí README v adresáři profilu.
repo_name_profile_private_hint=.profile-private je speciální repozitář, který můžete použít k přidání souboru README.md do profilu člena organizace, který je viditelný pouze pro členy organizace. Ujistěte se, že je soukromý, a pro začátek jej inicializujte pomocí README v adresáři profilu.
repo_name_helper=Dobrá jména repozitářů používají krátká, zapamatovatelná a unikátní klíčová slova. Repozitář s názvem „.profile“ nebo „.profile-private“ lze použít k přidání README.md pro uživatelský/organizační profil.
repo_size=Velikost repozitáře repo_size=Velikost repozitáře
template=Šablona template=Šablona
template_select=Vyberte šablonu. template_select=Vyberte šablonu.
@ -1030,6 +1037,8 @@ fork_to_different_account=Rozštěpit na jiný účet
fork_visibility_helper=Viditelnost rozštěpeného repozitáře nemůže být změněna. fork_visibility_helper=Viditelnost rozštěpeného repozitáře nemůže být změněna.
fork_branch=Větev, která má být klonována pro fork fork_branch=Větev, která má být klonována pro fork
all_branches=Všechny větve all_branches=Všechny větve
view_all_branches=Zobrazit všechny větve
view_all_tags=Zobrazit všechny značky
fork_no_valid_owners=Tento repozitář nemůže být rozštěpen, protože neexistují žádní platní vlastníci. fork_no_valid_owners=Tento repozitář nemůže být rozštěpen, protože neexistují žádní platní vlastníci.
fork.blocked_user=Nelze rozštěpit repozitář, protože jste blokováni majitelem repozitáře. fork.blocked_user=Nelze rozštěpit repozitář, protože jste blokováni majitelem repozitáře.
use_template=Použít tuto šablonu use_template=Použít tuto šablonu
@ -1041,6 +1050,8 @@ generate_repo=Generovat repozitář
generate_from=Generovat z generate_from=Generovat z
repo_desc=Popis repo_desc=Popis
repo_desc_helper=Zadejte krátký popis (volitelné) repo_desc_helper=Zadejte krátký popis (volitelné)
repo_no_desc=Nebyl uveden žádný popis
repo_lang=Jazyky
repo_gitignore_helper=Vyberte šablony .gitignore. repo_gitignore_helper=Vyberte šablony .gitignore.
repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore. repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore.
issue_labels=Štítky úkolů issue_labels=Štítky úkolů
@ -1102,6 +1113,7 @@ delete_preexisting_success=Smazány nepřijaté soubory v %s
blame_prior=Zobrazit blame před touto změnou blame_prior=Zobrazit blame před touto změnou
blame.ignore_revs=Ignorování revizí v <a href="%s">.git-blame-ignorerevs</a>. Klikněte zde <a href="%s">pro obejití</a> a zobrazení normálního pohledu blame. blame.ignore_revs=Ignorování revizí v <a href="%s">.git-blame-ignorerevs</a>. Klikněte zde <a href="%s">pro obejití</a> a zobrazení normálního pohledu blame.
blame.ignore_revs.failed=Nepodařilo se ignorovat revize v <a href="%s">.git-blame-ignore-revs</a>. blame.ignore_revs.failed=Nepodařilo se ignorovat revize v <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zobrazí maximálně 30 uživatelů
tree_path_not_found_commit=Cesta %[1]s v commitu %[2]s neexistuje tree_path_not_found_commit=Cesta %[1]s v commitu %[2]s neexistuje
tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje
@ -1223,6 +1235,7 @@ create_new_repo_command=Vytvořit nový repozitář na příkazové řádce
push_exist_repo=Nahrání existujícího repozitáře z příkazové řádky push_exist_repo=Nahrání existujícího repozitáře z příkazové řádky
empty_message=Tento repozitář nemá žádný obsah. empty_message=Tento repozitář nemá žádný obsah.
broken_message=Data gitu, která jsou základem tohoto repozitáře, nelze číst. Kontaktujte správce této instance nebo smažte tento repositář. broken_message=Data gitu, která jsou základem tohoto repozitáře, nelze číst. Kontaktujte správce této instance nebo smažte tento repositář.
no_branch=Tento repozitář nemá žádné větve.
code=Zdrojový kód code=Zdrojový kód
code.desc=Přístup ke zdrojovým kódům, souborům, commitům a větvím. code.desc=Přístup ke zdrojovým kódům, souborům, commitům a větvím.
@ -1520,6 +1533,8 @@ issues.filter_assignee=Zpracovatel
issues.filter_assginee_no_select=Všichni zpracovatelé issues.filter_assginee_no_select=Všichni zpracovatelé
issues.filter_assginee_no_assignee=Bez zpracovatele issues.filter_assginee_no_assignee=Bez zpracovatele
issues.filter_poster=Autor issues.filter_poster=Autor
issues.filter_user_placeholder=Hledat uživatele
issues.filter_user_no_select=Všichni uživatelé
issues.filter_type=Typ issues.filter_type=Typ
issues.filter_type.all_issues=Všechny úkoly issues.filter_type.all_issues=Všechny úkoly
issues.filter_type.assigned_to_you=Přiřazené vám issues.filter_type.assigned_to_you=Přiřazené vám
@ -1664,12 +1679,25 @@ issues.delete.title=Smazat tento úkol?
issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.) issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.)
issues.tracker=Sledování času issues.tracker=Sledování času
issues.timetracker_timer_start=Spustit časovač
issues.timetracker_timer_stop=Zastavit časovač
issues.timetracker_timer_discard=Zahodit časovač
issues.timetracker_timer_manually_add=Přidat čas
issues.time_estimate_set=Nastavit odhadovaný čas
issues.time_estimate_display=Odhad: %s
issues.change_time_estimate_at=změnil/a odhad času na <b>%s</b> %s
issues.remove_time_estimate_at=odstranil/a odhad času %s
issues.time_estimate_invalid=Formát odhadu času je neplatný
issues.start_tracking_history=započal/a práci %s
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!` issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!`
issues.stop_tracking_history=pracoval/a <b>%s</b> %s
issues.cancel_tracking_history=`zrušil/a sledování času %s` issues.cancel_tracking_history=`zrušil/a sledování času %s`
issues.del_time=Odstranit tento časový záznam issues.del_time=Odstranit tento časový záznam
issues.add_time_history=přidal/a strávený čas <b>%s</b> %s
issues.del_time_history=`odstranil/a strávený čas %s` issues.del_time_history=`odstranil/a strávený čas %s`
issues.add_time_manually=Přidat čas ručně
issues.add_time_hours=Hodiny issues.add_time_hours=Hodiny
issues.add_time_minutes=Minuty issues.add_time_minutes=Minuty
issues.add_time_sum_to_small=Čas nebyl zadán. issues.add_time_sum_to_small=Čas nebyl zadán.
@ -1922,6 +1950,9 @@ pulls.delete.title=Odstranit tento pull request?
pulls.delete.text=Opravdu chcete tento pull request smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.) pulls.delete.text=Opravdu chcete tento pull request smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.)
pulls.recently_pushed_new_branches=Nahráli jste větev <strong>%[1]s</strong> %[2]s pulls.recently_pushed_new_branches=Nahráli jste větev <strong>%[1]s</strong> %[2]s
pulls.upstream_diverging_prompt_behind_1=Tato větev je %[1]d commit pozadu za %[2]s
pulls.upstream_diverging_prompt_behind_n=Tato větev je %[1]d commitů pozadu za %[2]s
pulls.upstream_diverging_prompt_base_newer=Hlavní větev %s má nové změny
pull.deleted_branch=(odstraněno):%s pull.deleted_branch=(odstraněno):%s
pull.agit_documentation=Prohlédněte si dokumentaci o AGit pull.agit_documentation=Prohlédněte si dokumentaci o AGit
@ -2595,6 +2626,9 @@ diff.image.overlay=Překrytí
diff.has_escaped=Tento řádek má skryté znaky Unicode diff.has_escaped=Tento řádek má skryté znaky Unicode
diff.show_file_tree=Zobrazit souborový strom diff.show_file_tree=Zobrazit souborový strom
diff.hide_file_tree=Skrýt souborový strom diff.hide_file_tree=Skrýt souborový strom
diff.submodule_added=Submodul %[1]s přidán v %[2]s
diff.submodule_deleted=Submodul %[1]s odebrán z %[2]s
diff.submodule_updated=Submodul %[1]s aktualizován: %[2]s
releases.desc=Sledování verzí projektu a souborů ke stažení. releases.desc=Sledování verzí projektu a souborů ke stažení.
release.releases=Vydání release.releases=Vydání
@ -2604,6 +2638,7 @@ release.new_release=Nové vydání
release.draft=Koncept release.draft=Koncept
release.prerelease=Předběžná verze release.prerelease=Předběžná verze
release.stable=Stabilní release.stable=Stabilní
release.latest=Nejnovější
release.compare=Porovnat release.compare=Porovnat
release.edit=upravit release.edit=upravit
release.ahead.commits=<strong>%d</strong> revizí release.ahead.commits=<strong>%d</strong> revizí
@ -2832,6 +2867,9 @@ teams.invite.title=Byli jste pozváni do týmu <strong>%s</strong> v organizaci
teams.invite.by=Pozvání od %s teams.invite.by=Pozvání od %s
teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže. teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže.
view_as_role=Zobrazit jako: %s
view_as_public_hint=Prohlížíte README jako veřejný uživatel.
view_as_member_hint=Prohlížíte README jako člen této organizace.
[admin] [admin]
maintenance=Údržba maintenance=Údržba
@ -3502,6 +3540,7 @@ versions=Verze
versions.view_all=Zobrazit všechny versions.view_all=Zobrazit všechny
dependency.id=ID dependency.id=ID
dependency.version=Verze dependency.version=Verze
search_in_external_registry=Hledat v %s
alpine.registry=Nastavte tento registr přidáním URL do <code>/etc/apk/repositories</code>: alpine.registry=Nastavte tento registr přidáním URL do <code>/etc/apk/repositories</code>:
alpine.registry.key=Stáhněte si veřejný RSA klíč registru do složky <code>/etc/apk/keys/</code> pro ověření podpisu indexu: alpine.registry.key=Stáhněte si veřejný RSA klíč registru do složky <code>/etc/apk/keys/</code> pro ověření podpisu indexu:
alpine.registry.info=Vyberte $branch a $repository ze seznamu níže. alpine.registry.info=Vyberte $branch a $repository ze seznamu níže.
@ -3510,6 +3549,8 @@ alpine.repository=Informace o repozitáři
alpine.repository.branches=Větve alpine.repository.branches=Větve
alpine.repository.repositories=Repozitáře alpine.repository.repositories=Repozitáře
alpine.repository.architectures=Architektury alpine.repository.architectures=Architektury
arch.registry=Přidejte server se souvisejícím repozitářem a architekturou do <code>/etc/pacman.conf</code>:
arch.install=Synchronizovat balíček s pacman:
arch.repository=Informace o repozitáři arch.repository=Informace o repozitáři
arch.repository.repositories=Repozitáře arch.repository.repositories=Repozitáře
arch.repository.architectures=Architektury arch.repository.architectures=Architektury
@ -3692,6 +3733,7 @@ runners.status.active=Aktivní
runners.status.offline=Offline runners.status.offline=Offline
runners.version=Verze runners.version=Verze
runners.reset_registration_token=Resetovat registrační token runners.reset_registration_token=Resetovat registrační token
runners.reset_registration_token_confirm=Chcete zneplatnit stávající token a vygenerovat nový?
runners.reset_registration_token_success=Registrační token runneru byl úspěšně obnoven runners.reset_registration_token_success=Registrační token runneru byl úspěšně obnoven
runs.all_workflows=Všechny pracovní postupy runs.all_workflows=Všechny pracovní postupy
@ -3724,6 +3766,7 @@ workflow.not_found=Pracovní postup „%s“ nebyl nalezen.
workflow.run_success=Pracovní postup „%s“ proběhl úspěšně. workflow.run_success=Pracovní postup „%s“ proběhl úspěšně.
workflow.from_ref=Použít pracovní postup od workflow.from_ref=Použít pracovní postup od
workflow.has_workflow_dispatch=Tento pracovní postup má spouštěč události workflow_dispatch. workflow.has_workflow_dispatch=Tento pracovní postup má spouštěč události workflow_dispatch.
workflow.has_no_workflow_dispatch=Pracovní postup „%s“ memá workflow_dispatch spouštěč.
need_approval_desc=Potřebujete schválení pro spuštění pracovních postupů pro rozštěpený pull request. need_approval_desc=Potřebujete schválení pro spuštění pracovních postupů pro rozštěpený pull request.
@ -3743,6 +3786,8 @@ variables.creation.success=Proměnná „%s“ byla přidána.
variables.update.failed=Úprava proměnné se nezdařila. variables.update.failed=Úprava proměnné se nezdařila.
variables.update.success=Proměnná byla upravena. variables.update.success=Proměnná byla upravena.
logs.always_auto_scroll=Vždy automaticky posouvat logy
logs.always_expand_running=Vždy rozšířit běžící logy
[projects] [projects]
deleted.display_name=Odstraněný projekt deleted.display_name=Odstraněný projekt

View File

@ -1235,6 +1235,7 @@ create_new_repo_command = Creating a new repository on the command line
push_exist_repo = Pushing an existing repository from the command line push_exist_repo = Pushing an existing repository from the command line
empty_message = This repository does not contain any content. empty_message = This repository does not contain any content.
broken_message = The Git data underlying this repository cannot be read. Contact the administrator of this instance or delete this repository. broken_message = The Git data underlying this repository cannot be read. Contact the administrator of this instance or delete this repository.
no_branch = This repository doesnt have any branches.
code = Code code = Code
code.desc = Access source code, files, commits and branches. code.desc = Access source code, files, commits and branches.
@ -2627,6 +2628,9 @@ diff.image.overlay = Overlay
diff.has_escaped = This line has hidden Unicode characters diff.has_escaped = This line has hidden Unicode characters
diff.show_file_tree = Show file tree diff.show_file_tree = Show file tree
diff.hide_file_tree = Hide file tree diff.hide_file_tree = Hide file tree
diff.submodule_added = Submodule %[1]s added at %[2]s
diff.submodule_deleted = Submodule %[1]s deleted from %[2]s
diff.submodule_updated = Submodule %[1]s updated: %[2]s
releases.desc = Track project versions and downloads. releases.desc = Track project versions and downloads.
release.releases = Releases release.releases = Releases
@ -3764,6 +3768,7 @@ workflow.not_found = Workflow '%s' not found.
workflow.run_success = Workflow '%s' run successfully. workflow.run_success = Workflow '%s' run successfully.
workflow.from_ref = Use workflow from workflow.from_ref = Use workflow from
workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event trigger. workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event trigger.
workflow.has_no_workflow_dispatch = Workflow '%s' has no workflow_dispatch event trigger.
need_approval_desc = Need approval to run workflows for fork pull request. need_approval_desc = Need approval to run workflows for fork pull request.

View File

@ -244,6 +244,7 @@ license_desc=Venez récupérer <a target="_blank" rel="noopener noreferrer" href
[install] [install]
install=Installation install=Installation
installing_desc=Installation en cours, veuillez patienter…
title=Configuration initiale title=Configuration initiale
docker_helper=Si vous exécutez Gitea dans Docker, veuillez lire la <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> avant de modifier les paramètres. docker_helper=Si vous exécutez Gitea dans Docker, veuillez lire la <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> avant de modifier les paramètres.
require_db_desc=Gitea nécessite MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (avec le protocole MySQL). require_db_desc=Gitea nécessite MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (avec le protocole MySQL).
@ -1015,6 +1016,9 @@ new_repo_helper=Un dépôt contient tous les fichiers dun projet, ainsi que l
owner=Propriétaire owner=Propriétaire
owner_helper=Certaines organisations peuvent ne pas apparaître dans la liste déroulante en raison d'une limite maximale du nombre de dépôts. owner_helper=Certaines organisations peuvent ne pas apparaître dans la liste déroulante en raison d'une limite maximale du nombre de dépôts.
repo_name=Nom du dépôt repo_name=Nom du dépôt
repo_name_profile_public_hint=.profile est un dépôt spécial que vous pouvez utiliser pour ajouter un README.md à votre profil public dorganisation, visible à tout le monde. Assurez-vous quil soit public et initialisez-le avec un README dans le répertoire de profil pour commencer.
repo_name_profile_private_hint=.profile-private est un dépôt spécial que vous pouvez utiliser pour ajouter un README.md à votre profil dorganisation, visible uniquement aux membres de lorganisation. Assurez-vous quil soit privé et initialisez-le avec un README dans le répertoire de profil pour commencer.
repo_name_helper=Idéalement, le nom dun dépôt devrait être court, mémorisable et unique. Vous pouvez personnaliser votre profil ou celui de votre organisation en créant un dépôt nommé « .profile » ou « .profile-private » et contenant un README.md.
repo_size=Taille du dépôt repo_size=Taille du dépôt
template=Modèle template=Modèle
template_select=Répliquer un modèle template_select=Répliquer un modèle
@ -1231,6 +1235,7 @@ create_new_repo_command=Création d'un nouveau dépôt en ligne de commande
push_exist_repo=Soumission d'un dépôt existant par ligne de commande push_exist_repo=Soumission d'un dépôt existant par ligne de commande
empty_message=Ce dépôt n'a pas de contenu. empty_message=Ce dépôt n'a pas de contenu.
broken_message=Les données git de ce dépôt ne peuvent pas être lues. Contactez l'administrateur de cette instance ou supprimez ce dépôt. broken_message=Les données git de ce dépôt ne peuvent pas être lues. Contactez l'administrateur de cette instance ou supprimez ce dépôt.
no_branch=Ce dépôt na aucune branche.
code=Code code=Code
code.desc=Accéder au code source, fichiers, révisions et branches. code.desc=Accéder au code source, fichiers, révisions et branches.
@ -2860,6 +2865,9 @@ teams.invite.title=Vous avez été invité à rejoindre l'équipe <strong>%s</st
teams.invite.by=Invité par %s teams.invite.by=Invité par %s
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre léquipe. teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre léquipe.
view_as_role=Voir en tant que %s
view_as_public_hint=Vous visualisez le README en tant quutilisateur public.
view_as_member_hint=Vous visualisez le README en tant que membre de cette organisation.
[admin] [admin]
maintenance=Maintenance maintenance=Maintenance
@ -3530,6 +3538,7 @@ versions=Versions
versions.view_all=Voir tout versions.view_all=Voir tout
dependency.id=ID dependency.id=ID
dependency.version=Version dependency.version=Version
search_in_external_registry=Rechercher dans %s
alpine.registry=Configurez ce registre en ajoutant lURL dans votre fichier <code>/etc/apk/repositories</code> : alpine.registry=Configurez ce registre en ajoutant lURL dans votre fichier <code>/etc/apk/repositories</code> :
alpine.registry.key=Téléchargez la clé RSA publique du registre dans le dossier <code>/etc/apk/keys/</code> pour vérifier la signature de l'index : alpine.registry.key=Téléchargez la clé RSA publique du registre dans le dossier <code>/etc/apk/keys/</code> pour vérifier la signature de l'index :
alpine.registry.info=Choisissez $branch et $repository dans la liste ci-dessous. alpine.registry.info=Choisissez $branch et $repository dans la liste ci-dessous.
@ -3722,6 +3731,7 @@ runners.status.active=Actif
runners.status.offline=Hors-ligne runners.status.offline=Hors-ligne
runners.version=Version runners.version=Version
runners.reset_registration_token=Réinitialiser le jeton d'enregistrement runners.reset_registration_token=Réinitialiser le jeton d'enregistrement
runners.reset_registration_token_confirm=Voulez-vous révoquer le jeton actuel et en générer un nouveau ?
runners.reset_registration_token_success=Le jeton dinscription de lexécuteur a été réinitialisé avec succès runners.reset_registration_token_success=Le jeton dinscription de lexécuteur a été réinitialisé avec succès
runs.all_workflows=Tous les flux de travail runs.all_workflows=Tous les flux de travail
@ -3754,6 +3764,7 @@ workflow.not_found=Flux de travail « %s » introuvable.
workflow.run_success=Le flux de travail « %s » sest correctement exécuté. workflow.run_success=Le flux de travail « %s » sest correctement exécuté.
workflow.from_ref=Utiliser le flux de travail depuis workflow.from_ref=Utiliser le flux de travail depuis
workflow.has_workflow_dispatch=Ce flux de travail a un déclencheur dévénement workflow_dispatch. workflow.has_workflow_dispatch=Ce flux de travail a un déclencheur dévénement workflow_dispatch.
workflow.has_no_workflow_dispatch=Le flux de travail %s na pas de déclencheur dévénement workflow_dispatch.
need_approval_desc=Besoin dapprobation pour exécuter des flux de travail pour une demande dajout de bifurcation. need_approval_desc=Besoin dapprobation pour exécuter des flux de travail pour une demande dajout de bifurcation.
@ -3773,6 +3784,8 @@ variables.creation.success=La variable « %s » a été ajoutée.
variables.update.failed=Impossible déditer la variable. variables.update.failed=Impossible déditer la variable.
variables.update.success=La variable a bien été modifiée. variables.update.success=La variable a bien été modifiée.
logs.always_auto_scroll=Toujours faire défiler les journaux automatiquement
logs.always_expand_running=Toujours développer les journaux en cours
[projects] [projects]
deleted.display_name=Projet supprimé deleted.display_name=Projet supprimé

View File

@ -3763,6 +3763,8 @@ variables.creation.success=変数 "%s" を追加しました。
variables.update.failed=変数を更新できませんでした。 variables.update.failed=変数を更新できませんでした。
variables.update.success=変数を更新しました。 variables.update.success=変数を更新しました。
logs.always_auto_scroll=常にログを自動スクロール
logs.always_expand_running=常に実行中のログを展開
[projects] [projects]
deleted.display_name=削除されたプロジェクト deleted.display_name=削除されたプロジェクト

View File

@ -111,7 +111,7 @@ license=오픈 소스
[install] [install]
install=설치 install=설치
title=초기 설정 title=초기 설정
docker_helper="Gitea를 Docker에서 실행하려면 설정 전에 이 <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">문서</a>를 읽어보세요." docker_helper=Gitea를 Docker에서 실행하려면 설정 전에 이 <a target="_blank" rel="noopener noreferrer" href="%s">문서</a>를 읽어보세요.
db_title=데이터베이스 설정 db_title=데이터베이스 설정
db_type=데이터베이스 유형 db_type=데이터베이스 유형
host=호스트 host=호스트
@ -436,8 +436,8 @@ manage_gpg_keys=GPG 키 관리
add_key=키 추가 add_key=키 추가
ssh_desc=이러한 SSH 공용 키는 귀하의 계정과 연결되어 있습니다. 해당 개인 키는 당신의 저장소에 대한 전체 액세스를 가능하게 합니다. ssh_desc=이러한 SSH 공용 키는 귀하의 계정과 연결되어 있습니다. 해당 개인 키는 당신의 저장소에 대한 전체 액세스를 가능하게 합니다.
gpg_desc=이러한 GPG 공개키는 당신의 계정과 연결되어있습니다. 커밋이 검증될 수 있도록 당신의 개인 키를 안전하게 유지하십시오. gpg_desc=이러한 GPG 공개키는 당신의 계정과 연결되어있습니다. 커밋이 검증될 수 있도록 당신의 개인 키를 안전하게 유지하십시오.
ssh_helper="<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href=\"%s\">SSH 키 생성하기</a> 또는 SSH를 사용할 때 <a href=\"%s\">일반적인 문제</a>" ssh_helper=<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href="%s">SSH 키 생성하기</a> 또는 SSH를 사용할 때 <a href="%s">일반적인 문제</a>
gpg_helper="<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href=\"%s\">GPG키에 대하여</a>." gpg_helper=<strong>도움이 필요하세요?</strong> GitHub의 설명서를 참조하시기 바랍니다: <a href="%s">GPG키에 대하여</a>.
add_new_key=SSH 키 추가 add_new_key=SSH 키 추가
add_new_gpg_key=GPG 키 추가 add_new_gpg_key=GPG 키 추가
gpg_key_id_used=같은 ID의 GPG 공개키가 이미 존재합니다. gpg_key_id_used=같은 ID의 GPG 공개키가 이미 존재합니다.
@ -538,7 +538,7 @@ template_helper=템플릿으로 저장소 만들기
visibility=가시성 visibility=가시성
visibility_helper_forced=사이트 관리자가 새 레포지토리에 대해 비공개로만 생성되도록 하였습니다. visibility_helper_forced=사이트 관리자가 새 레포지토리에 대해 비공개로만 생성되도록 하였습니다.
visibility_fork_helper=(변경사항을 적용하는 경우 모든 포크가 영향을 받게 됩니다.) visibility_fork_helper=(변경사항을 적용하는 경우 모든 포크가 영향을 받게 됩니다.)
clone_helper="클론하는데에 도움이 필요하면 <a target=\"_blank\" href=\"%s\">Help</a>에 방문하세요." clone_helper=클론하는데에 도움이 필요하면 <a target="_blank" href="%s">Help</a>에 방문하세요.
fork_repo=저장소 포크 fork_repo=저장소 포크
fork_from=원본 프로젝트 : fork_from=원본 프로젝트 :
fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다. fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다.
@ -640,7 +640,7 @@ editor.or=혹은
editor.cancel_lower=취소 editor.cancel_lower=취소
editor.commit_changes=변경 내용을 커밋 editor.commit_changes=변경 내용을 커밋
editor.commit_message_desc=선택적 확장 설명을 추가... editor.commit_message_desc=선택적 확장 설명을 추가...
editor.commit_directly_to_this_branch="<strong class=\"branch-name\">%s</strong> 브랜치에서 직접 커밋해주세요." editor.commit_directly_to_this_branch=<strong class="branch-name">%s</strong> 브랜치에서 직접 커밋해주세요.
editor.create_new_branch=이 커밋에 대한 <strong>새로운 브랜치</strong>를 만들고 끌어오기 요청을 시작합니다. editor.create_new_branch=이 커밋에 대한 <strong>새로운 브랜치</strong>를 만들고 끌어오기 요청을 시작합니다.
editor.new_branch_name_desc=새로운 브랜치 명... editor.new_branch_name_desc=새로운 브랜치 명...
editor.cancel=취소 editor.cancel=취소
@ -667,7 +667,7 @@ ext_issues.desc=외부 이슈 트래커 연결.
projects.description_placeholder=설명 projects.description_placeholder=설명
projects.title=제목 projects.title=제목
projects.new=새 프로젝트 projects.new=새 프로젝트
projects.template.desc="템플릿" projects.template.desc=템플릿
projects.column.edit_title=이름 projects.column.edit_title=이름
projects.column.new_title=이름 projects.column.new_title=이름
@ -731,7 +731,7 @@ issues.action_milestone=마일스톤
issues.action_milestone_no_select=마일스톤 없음 issues.action_milestone_no_select=마일스톤 없음
issues.action_assignee=담당자 issues.action_assignee=담당자
issues.action_assignee_no_select=담당자 없음 issues.action_assignee_no_select=담당자 없음
issues.opened_by="<a href=\"%[2]s\"> %[3]s</a>가 %[1]s을 오픈" issues.opened_by=<a href="%[2]s"> %[3]s</a>가 %[1]s을 오픈
issues.previous=이전 issues.previous=이전
issues.next=다음 issues.next=다음
issues.open_title=오픈 issues.open_title=오픈
@ -747,7 +747,7 @@ issues.create_comment=코멘트
issues.commit_ref_at=` 커밋 <a id="%[1]s" href="#%[1]s">%[2]s</a>에서 이 이슈 언급` issues.commit_ref_at=` 커밋 <a id="%[1]s" href="#%[1]s">%[2]s</a>에서 이 이슈 언급`
issues.role.owner=소유자 issues.role.owner=소유자
issues.role.member=멤버 issues.role.member=멤버
issues.sign_in_require_desc="<a href=\"%s\">로그인</a>하여 이 대화에 참여" issues.sign_in_require_desc=<a href="%s">로그인</a>하여 이 대화에 참여
issues.edit=수정 issues.edit=수정
issues.cancel=취소 issues.cancel=취소
issues.save=저장 issues.save=저장
@ -780,9 +780,9 @@ issues.time_spent_total=총 경과된 시간
issues.time_spent_from_all_authors=`총 경과된 시간: %s` issues.time_spent_from_all_authors=`총 경과된 시간: %s`
issues.due_date=마감일 issues.due_date=마감일
issues.invalid_due_date_format="마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다." issues.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다.
issues.error_modifying_due_date="마감일 수정을 실패하였습니다." issues.error_modifying_due_date=마감일 수정을 실패하였습니다.
issues.error_removing_due_date="마감일 삭제를 실패하였습니다." issues.error_removing_due_date=마감일 삭제를 실패하였습니다.
issues.due_date_form=yyyy-mm-dd issues.due_date_form=yyyy-mm-dd
issues.due_date_form_add=마감일 추가 issues.due_date_form_add=마감일 추가
issues.due_date_form_edit=편집 issues.due_date_form_edit=편집
@ -790,8 +790,8 @@ issues.due_date_form_remove=삭제
issues.due_date_not_set=마감일이 설정되지 않았습니다. issues.due_date_not_set=마감일이 설정되지 않았습니다.
issues.due_date_added=마감일 %s 를 추가 %s issues.due_date_added=마감일 %s 를 추가 %s
issues.due_date_remove=%s %s 마감일이 삭제되었습니다. issues.due_date_remove=%s %s 마감일이 삭제되었습니다.
issues.due_date_overdue="기한 초과" issues.due_date_overdue=기한 초과
issues.due_date_invalid="기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오." issues.due_date_invalid=기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오.
issues.dependency.title=의존성 issues.dependency.title=의존성
issues.dependency.add=의존성 추가... issues.dependency.add=의존성 추가...
issues.dependency.cancel=취소 issues.dependency.cancel=취소
@ -809,7 +809,7 @@ issues.dependency.add_error_dep_exists=의존성이 이미 존재합니다.
issues.dependency.add_error_dep_not_same_repo=두 이슈는 같은 레포지토리 안에 있어야 합니다. issues.dependency.add_error_dep_not_same_repo=두 이슈는 같은 레포지토리 안에 있어야 합니다.
issues.review.self.approval=자신의 풀 리퀘스트를 승인할 수 없습니다. issues.review.self.approval=자신의 풀 리퀘스트를 승인할 수 없습니다.
issues.review.self.rejection=자신의 풀 리퀘스트에 대한 변경을 요청할 수 없습니다. issues.review.self.rejection=자신의 풀 리퀘스트에 대한 변경을 요청할 수 없습니다.
issues.review.approve="이 변경사항을 승인하였습니다. %s" issues.review.approve=이 변경사항을 승인하였습니다. %s
issues.review.pending=보류 issues.review.pending=보류
issues.review.review=검토 issues.review.review=검토
issues.review.reviewers=리뷰어 issues.review.reviewers=리뷰어
@ -824,7 +824,7 @@ pulls.compare_base=병합하기
pulls.compare_compare=다음으로부터 풀 pulls.compare_compare=다음으로부터 풀
pulls.filter_branch=Filter Branch pulls.filter_branch=Filter Branch
pulls.create=풀 리퀘스트 생성 pulls.create=풀 리퀘스트 생성
pulls.title_desc="<code>%[2]s</code> 에서 <code id=\"branch_target\">%[3]s</code> 로 %[1]d commits 를 머지하려 합니다" pulls.title_desc=<code>%[2]s</code> 에서 <code id="branch_target">%[3]s</code> 로 %[1]d commits 를 머지하려 합니다
pulls.merged_title_desc=<code>%[2]s</code> 에서 <code>%[3]s</code> 로 %[1]d commits 를 머지했습니다 %[4]s pulls.merged_title_desc=<code>%[2]s</code> 에서 <code>%[3]s</code> 로 %[1]d commits 를 머지했습니다 %[4]s
pulls.tab_conversation=대화 pulls.tab_conversation=대화
pulls.tab_commits=커밋 pulls.tab_commits=커밋
@ -855,7 +855,7 @@ milestones.title=타이틀
milestones.desc=설명 milestones.desc=설명
milestones.due_date=기한 (선택 사항) milestones.due_date=기한 (선택 사항)
milestones.clear=지우기 milestones.clear=지우기
milestones.invalid_due_date_format="마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다." milestones.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다.
milestones.edit=마일스톤 편집 milestones.edit=마일스톤 편집
milestones.cancel=취소 milestones.cancel=취소
milestones.modify=마일스톤 갱신 milestones.modify=마일스톤 갱신

View File

@ -495,7 +495,7 @@ register_notify.text_3=Se esta conta foi criada para si, <a href="%s">defina a s
reset_password=Recupere a sua conta reset_password=Recupere a sua conta
reset_password.title=%s, você pediu para recuperar a sua conta reset_password.title=%s, você pediu para recuperar a sua conta
reset_password.text=Por favor clique na seguinte ligação para recuperar a sua conta em <b>%s</b>: reset_password.text=Para recuperar a sua conta, clique na ligação seguinte (válida por <b>%s</b>):
register_success=Inscrição bem sucedida register_success=Inscrição bem sucedida
@ -1017,6 +1017,8 @@ owner=Proprietário(a)
owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios. owner_helper=Algumas organizações podem não aparecer na lista suspensa devido a um limite máximo de contagem de repositórios.
repo_name=Nome do repositório repo_name=Nome do repositório
repo_name_profile_public_hint=.profile é um repositório especial que pode usar para adicionar README.md ao seu perfil público da organização, visível para qualquer pessoa. Certifique-se que é público e inicialize-o com um README na pasta do perfil para começar. repo_name_profile_public_hint=.profile é um repositório especial que pode usar para adicionar README.md ao seu perfil público da organização, visível para qualquer pessoa. Certifique-se que é público e inicialize-o com um README na pasta do perfil para começar.
repo_name_profile_private_hint=.profile-private é um repositório especial que pode usar para adicionar um README.md ao seu perfil de membro da organização, visível apenas para membros da organização. Certifique-se que é privado e inicialize-o com um README na pasta de perfil para começar.
repo_name_helper=Bons nomes de repositórios usam palavras-chave curtas, memorizáveis e únicas. Um repositório chamado ".profile" ou ".profile-private" pode ser usado para adicionar um README.md ao perfil do utilizador ou da organização.
repo_size=Tamanho do repositório repo_size=Tamanho do repositório
template=Modelo template=Modelo
template_select=Escolha um modelo. template_select=Escolha um modelo.
@ -1233,6 +1235,7 @@ create_new_repo_command=Criando um novo repositório na linha de comandos
push_exist_repo=Enviando, pela linha de comandos, um repositório existente push_exist_repo=Enviando, pela linha de comandos, um repositório existente
empty_message=Este repositório não contém qualquer conteúdo. empty_message=Este repositório não contém qualquer conteúdo.
broken_message=Os dados Git subjacentes a este repositório não podem ser lidos. Contacte o administrador desta instância ou elimine este repositório. broken_message=Os dados Git subjacentes a este repositório não podem ser lidos. Contacte o administrador desta instância ou elimine este repositório.
no_branch=Este repositório não tem quaisquer ramos.
code=Código code=Código
code.desc=Aceder ao código fonte, ficheiros, cometimentos e ramos. code.desc=Aceder ao código fonte, ficheiros, cometimentos e ramos.
@ -2624,6 +2627,9 @@ diff.image.overlay=Sobrepor
diff.has_escaped=Esta linha tem caracteres unicode escondidos diff.has_escaped=Esta linha tem caracteres unicode escondidos
diff.show_file_tree=Mostrar árvore de ficheiros diff.show_file_tree=Mostrar árvore de ficheiros
diff.hide_file_tree=Esconder árvore de ficheiros diff.hide_file_tree=Esconder árvore de ficheiros
diff.submodule_added=Submódulo %[1]s adicionado em %[2]s
diff.submodule_deleted=Submódulo %[1]s eliminado de %[2]s
diff.submodule_updated=Submódulo %[1]s modificado: %[2]s
releases.desc=Acompanhe as versões e as descargas do repositório. releases.desc=Acompanhe as versões e as descargas do repositório.
release.releases=Lançamentos release.releases=Lançamentos
@ -3761,6 +3767,7 @@ workflow.not_found=A sequência de trabalho '%s' não foi encontrada.
workflow.run_success=A sequência de trabalho '%s' foi executada com sucesso. workflow.run_success=A sequência de trabalho '%s' foi executada com sucesso.
workflow.from_ref=Usar sequência de trabalho de workflow.from_ref=Usar sequência de trabalho de
workflow.has_workflow_dispatch=Esta sequência de trabalho tem um despoletador de eventos workflow_dispatch. workflow.has_workflow_dispatch=Esta sequência de trabalho tem um despoletador de eventos workflow_dispatch.
workflow.has_no_workflow_dispatch=A sequência de trabalho '%s' não tem nenhum despoletador de eventos workflow_dispatch.
need_approval_desc=É necessária aprovação para executar sequências de trabalho para a derivação do pedido de integração. need_approval_desc=É necessária aprovação para executar sequências de trabalho para a derivação do pedido de integração.

View File

@ -114,7 +114,7 @@ func UploadPackageFile(ctx *context.Context) {
pck, err := alpine_module.ParsePackage(buf) pck, err := alpine_module.ParsePackage(buf)
if err != nil { if err != nil {
if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF { if errors.Is(err, util.ErrInvalidArgument) || errors.Is(err, io.EOF) {
apiError(ctx, http.StatusBadRequest, err) apiError(ctx, http.StatusBadRequest, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -138,7 +138,8 @@ func CommonRoutes() *web.Router {
}, reqPackageAccess(perm.AccessModeRead)) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/arch", func() { r.Group("/arch", func() {
r.Methods("HEAD,GET", "/repository.key", arch.GetRepositoryKey) r.Methods("HEAD,GET", "/repository.key", arch.GetRepositoryKey)
r.PathGroup("*", func(g *web.RouterPathGroup) { r.Methods("PUT", "" /* no repository */, reqPackageAccess(perm.AccessModeWrite), arch.UploadPackageFile)
r.PathGroup("/*", func(g *web.RouterPathGroup) {
g.MatchPath("PUT", "/<repository:*>", reqPackageAccess(perm.AccessModeWrite), arch.UploadPackageFile) g.MatchPath("PUT", "/<repository:*>", reqPackageAccess(perm.AccessModeWrite), arch.UploadPackageFile)
g.MatchPath("HEAD,GET", "/<repository:*>/<architecture>/<filename>", arch.GetPackageOrRepositoryFile) g.MatchPath("HEAD,GET", "/<repository:*>/<architecture>/<filename>", arch.GetPackageOrRepositoryFile)
g.MatchPath("DELETE", "/<repository:*>/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), arch.DeletePackageVersion) g.MatchPath("DELETE", "/<repository:*>/<name>/<version>/<architecture>", reqPackageAccess(perm.AccessModeWrite), arch.DeletePackageVersion)
@ -698,150 +699,28 @@ func ContainerRoutes() *web.Router {
}) })
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList) r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() { r.Group("/{username}", func() {
r.Group("/{image}", func() { r.PathGroup("/*", func(g *web.RouterPathGroup) {
r.Group("/blobs/uploads", func() { g.MatchPath("POST", "/<image:*>/blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.InitiateUploadBlob)
r.Post("", container.InitiateUploadBlob) g.MatchPath("GET", "/<image:*>/tags/list", container.VerifyImageName, container.GetTagList)
r.Group("/{uuid}", func() { g.MatchPath("GET,PATCH,PUT,DELETE", `/<image:*>/blobs/uploads/<uuid:[-.=\w]+>`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) {
r.Get("", container.GetUploadBlob) if ctx.Req.Method == http.MethodGet {
r.Patch("", container.UploadBlob)
r.Put("", container.EndUploadBlob)
r.Delete("", container.CancelUploadBlob)
})
}, reqPackageAccess(perm.AccessModeWrite))
r.Group("/blobs/{digest}", func() {
r.Head("", container.HeadBlob)
r.Get("", container.GetBlob)
r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
})
r.Group("/manifests/{reference}", func() {
r.Put("", reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
r.Head("", container.HeadManifest)
r.Get("", container.GetManifest)
r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
})
r.Get("/tags/list", container.GetTagList)
}, container.VerifyImageName)
var (
blobsUploadsPattern = regexp.MustCompile(`\A(.+)/blobs/uploads/([a-zA-Z0-9-_.=]+)\z`)
blobsPattern = regexp.MustCompile(`\A(.+)/blobs/([^/]+)\z`)
manifestsPattern = regexp.MustCompile(`\A(.+)/manifests/([^/]+)\z`)
)
// Manual mapping of routes because {image} can contain slashes which chi does not support
r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "/*", func(ctx *context.Context) {
path := ctx.PathParam("*")
isHead := ctx.Req.Method == "HEAD"
isGet := ctx.Req.Method == "GET"
isPost := ctx.Req.Method == "POST"
isPut := ctx.Req.Method == "PUT"
isPatch := ctx.Req.Method == "PATCH"
isDelete := ctx.Req.Method == "DELETE"
if isPost && strings.HasSuffix(path, "/blobs/uploads") {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
ctx.SetPathParam("image", path[:len(path)-14])
container.VerifyImageName(ctx)
if ctx.Written() {
return
}
container.InitiateUploadBlob(ctx)
return
}
if isGet && strings.HasSuffix(path, "/tags/list") {
ctx.SetPathParam("image", path[:len(path)-10])
container.VerifyImageName(ctx)
if ctx.Written() {
return
}
container.GetTagList(ctx)
return
}
m := blobsUploadsPattern.FindStringSubmatch(path)
if len(m) == 3 && (isGet || isPut || isPatch || isDelete) {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
ctx.SetPathParam("image", m[1])
container.VerifyImageName(ctx)
if ctx.Written() {
return
}
ctx.SetPathParam("uuid", m[2])
if isGet {
container.GetUploadBlob(ctx) container.GetUploadBlob(ctx)
} else if isPatch { } else if ctx.Req.Method == http.MethodPatch {
container.UploadBlob(ctx) container.UploadBlob(ctx)
} else if isPut { } else if ctx.Req.Method == http.MethodPut {
container.EndUploadBlob(ctx) container.EndUploadBlob(ctx)
} else { } else /* DELETE */ {
container.CancelUploadBlob(ctx) container.CancelUploadBlob(ctx)
} }
return })
} g.MatchPath("HEAD", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.HeadBlob)
m = blobsPattern.FindStringSubmatch(path) g.MatchPath("GET", `/<image:*>/blobs/<digest>`, container.VerifyImageName, container.GetBlob)
if len(m) == 3 && (isHead || isGet || isDelete) { g.MatchPath("DELETE", `/<image:*>/blobs/<digest>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
ctx.SetPathParam("image", m[1])
container.VerifyImageName(ctx)
if ctx.Written() {
return
}
ctx.SetPathParam("digest", m[2]) g.MatchPath("HEAD", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.HeadManifest)
g.MatchPath("GET", `/<image:*>/manifests/<reference>`, container.VerifyImageName, container.GetManifest)
if isHead { g.MatchPath("PUT", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
container.HeadBlob(ctx) g.MatchPath("DELETE", `/<image:*>/manifests/<reference>`, container.VerifyImageName, reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
} else if isGet {
container.GetBlob(ctx)
} else {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
container.DeleteBlob(ctx)
}
return
}
m = manifestsPattern.FindStringSubmatch(path)
if len(m) == 3 && (isHead || isGet || isPut || isDelete) {
ctx.SetPathParam("image", m[1])
container.VerifyImageName(ctx)
if ctx.Written() {
return
}
ctx.SetPathParam("reference", m[2])
if isHead {
container.HeadManifest(ctx)
} else if isGet {
container.GetManifest(ctx)
} else {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
if isPut {
container.UploadManifest(ctx)
} else {
container.DeleteManifest(ctx)
}
}
return
}
ctx.Status(http.StatusNotFound)
}) })
}, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) }, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))

View File

@ -62,7 +62,7 @@ func UploadPackageFile(ctx *context.Context) {
pck, err := arch_module.ParsePackage(buf) pck, err := arch_module.ParsePackage(buf)
if err != nil { if err != nil {
if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF { if errors.Is(err, util.ErrInvalidArgument) || errors.Is(err, io.EOF) {
apiError(ctx, http.StatusBadRequest, err) apiError(ctx, http.StatusBadRequest, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -181,7 +181,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -276,7 +276,7 @@ func UnyankPackage(ctx *context.Context) {
func yankPackage(ctx *context.Context, yank bool) { func yankPackage(ctx *context.Context, yank bool) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.PathParam("package"), ctx.PathParam("version")) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.PathParam("package"), ctx.PathParam("version"))
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -216,7 +216,7 @@ func PackageVersionMetadata(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeChef, packageName, packageVersion) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeChef, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -327,7 +327,7 @@ func UploadPackage(ctx *context.Context) {
func DownloadPackage(ctx *context.Context) { func DownloadPackage(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeChef, ctx.PathParam("name"), ctx.PathParam("version")) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeChef, ctx.PathParam("name"), ctx.PathParam("version"))
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -368,7 +368,7 @@ func DeletePackageVersion(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -176,7 +176,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -5,6 +5,7 @@ package conan
import ( import (
std_ctx "context" std_ctx "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -183,7 +184,7 @@ func serveSnapshot(ctx *context.Context, fileKey string) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -244,7 +245,7 @@ func serveDownloadURLs(ctx *context.Context, fileKey, downloadURL string) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -493,7 +494,7 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -509,7 +510,7 @@ func DeleteRecipeV1(ctx *context.Context) {
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference) rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
if err := deleteRecipeOrPackage(ctx, rref, true, nil, false); err != nil { if err := deleteRecipeOrPackage(ctx, rref, true, nil, false); err != nil {
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -524,7 +525,7 @@ func DeleteRecipeV2(ctx *context.Context) {
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference) rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
if err := deleteRecipeOrPackage(ctx, rref, rref.Revision == "", nil, false); err != nil { if err := deleteRecipeOrPackage(ctx, rref, rref.Revision == "", nil, false); err != nil {
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -571,7 +572,7 @@ func DeletePackageV1(ctx *context.Context) {
for _, reference := range references { for _, reference := range references {
pref, _ := conan_module.NewPackageReference(currentRref, reference.Value, conan_module.DefaultRevision) pref, _ := conan_module.NewPackageReference(currentRref, reference.Value, conan_module.DefaultRevision)
if err := deleteRecipeOrPackage(ctx, currentRref, true, pref, true); err != nil { if err := deleteRecipeOrPackage(ctx, currentRref, true, pref, true); err != nil {
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -590,7 +591,7 @@ func DeletePackageV2(ctx *context.Context) {
if pref != nil { // has package reference if pref != nil { // has package reference
if err := deleteRecipeOrPackage(ctx, rref, false, pref, pref.Revision == ""); err != nil { if err := deleteRecipeOrPackage(ctx, rref, false, pref, pref.Revision == ""); err != nil {
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -615,7 +616,7 @@ func DeletePackageV2(ctx *context.Context) {
pref, _ := conan_module.NewPackageReference(rref, reference.Value, conan_module.DefaultRevision) pref, _ := conan_module.NewPackageReference(rref, reference.Value, conan_module.DefaultRevision)
if err := deleteRecipeOrPackage(ctx, rref, false, pref, true); err != nil { if err := deleteRecipeOrPackage(ctx, rref, false, pref, true); err != nil {
if err == packages_model.ErrPackageNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -749,7 +750,7 @@ func LatestRecipeRevision(ctx *context.Context) {
revision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref) revision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref)
if err != nil { if err != nil {
if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -766,7 +767,7 @@ func LatestPackageRevision(ctx *context.Context) {
revision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref) revision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref)
if err != nil { if err != nil {
if err == conan_model.ErrRecipeReferenceNotExist || err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) || errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -796,7 +797,7 @@ func listRevisionFiles(ctx *context.Context, fileKey string) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeConan, rref.Name, rref.Version)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -4,6 +4,7 @@
package conan package conan
import ( import (
"errors"
"net/http" "net/http"
"strings" "strings"
@ -76,7 +77,7 @@ func searchPackages(ctx *context.Context, searchAllRevisions bool) {
if !searchAllRevisions && rref.Revision == "" { if !searchAllRevisions && rref.Revision == "" {
lastRevision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref) lastRevision, err := conan_model.GetLastRecipeRevision(ctx, ctx.Package.Owner.ID, rref)
if err != nil { if err != nil {
if err == conan_model.ErrRecipeReferenceNotExist { if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -87,7 +88,7 @@ func searchPackages(ctx *context.Context, searchAllRevisions bool) {
} else { } else {
has, err := conan_model.RecipeExists(ctx, ctx.Package.Owner.ID, rref) has, err := conan_model.RecipeExists(ctx, ctx.Package.Owner.ID, rref)
if err != nil { if err != nil {
if err == conan_model.ErrRecipeReferenceNotExist { if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -119,7 +120,7 @@ func searchPackages(ctx *context.Context, searchAllRevisions bool) {
} }
packageReferences, err := conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, currentRef) packageReferences, err := conan_model.GetPackageReferences(ctx, ctx.Package.Owner.ID, currentRef)
if err != nil { if err != nil {
if err == conan_model.ErrRecipeReferenceNotExist { if errors.Is(err, conan_model.ErrRecipeReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -133,7 +134,7 @@ func searchPackages(ctx *context.Context, searchAllRevisions bool) {
pref, _ := conan_module.NewPackageReference(currentRef, packageReference.Value, "") pref, _ := conan_module.NewPackageReference(currentRef, packageReference.Value, "")
lastPackageRevision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref) lastPackageRevision, err := conan_model.GetLastPackageRevision(ctx, ctx.Package.Owner.ID, pref)
if err != nil { if err != nil {
if err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -143,7 +144,7 @@ func searchPackages(ctx *context.Context, searchAllRevisions bool) {
pref = pref.WithRevision(lastPackageRevision.Value) pref = pref.WithRevision(lastPackageRevision.Value)
infoRaw, err := conan_model.GetPackageInfo(ctx, ctx.Package.Owner.ID, pref) infoRaw, err := conan_model.GetPackageInfo(ctx, ctx.Package.Owner.ID, pref)
if err != nil { if err != nil {
if err == conan_model.ErrPackageReferenceNotExist { if errors.Is(err, conan_model.ErrPackageReferenceNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -111,12 +111,11 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
} }
var err error var err error
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if err == packages_model.ErrDuplicatePackage { if !errors.Is(err, packages_model.ErrDuplicatePackage) {
created = false
} else {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return err return err
} }
created = false
} }
if created { if created {
@ -135,7 +134,7 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
MetadataJSON: "null", MetadataJSON: "null",
} }
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil { if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
if err != packages_model.ErrDuplicatePackageVersion { if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return err return err
} }
@ -161,7 +160,7 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
} }
var err error var err error
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil { if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
if err == packages_model.ErrDuplicatePackageFile { if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
return nil return nil
} }
log.Error("Error inserting package file: %v", err) log.Error("Error inserting package file: %v", err)

View File

@ -324,7 +324,7 @@ func GetUploadBlob(ctx *context.Context) {
upload, err := packages_model.GetBlobUploadByID(ctx, uuid) upload, err := packages_model.GetBlobUploadByID(ctx, uuid)
if err != nil { if err != nil {
if err == packages_model.ErrPackageBlobUploadNotExist { if errors.Is(err, packages_model.ErrPackageBlobUploadNotExist) {
apiErrorDefined(ctx, errBlobUploadUnknown) apiErrorDefined(ctx, errBlobUploadUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -345,7 +345,7 @@ func UploadBlob(ctx *context.Context) {
uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid")) uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid"))
if err != nil { if err != nil {
if err == packages_model.ErrPackageBlobUploadNotExist { if errors.Is(err, packages_model.ErrPackageBlobUploadNotExist) {
apiErrorDefined(ctx, errBlobUploadUnknown) apiErrorDefined(ctx, errBlobUploadUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -396,7 +396,7 @@ func EndUploadBlob(ctx *context.Context) {
uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid")) uploader, err := container_service.NewBlobUploader(ctx, ctx.PathParam("uuid"))
if err != nil { if err != nil {
if err == packages_model.ErrPackageBlobUploadNotExist { if errors.Is(err, packages_model.ErrPackageBlobUploadNotExist) {
apiErrorDefined(ctx, errBlobUploadUnknown) apiErrorDefined(ctx, errBlobUploadUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -465,7 +465,7 @@ func CancelUploadBlob(ctx *context.Context) {
_, err := packages_model.GetBlobUploadByID(ctx, uuid) _, err := packages_model.GetBlobUploadByID(ctx, uuid)
if err != nil { if err != nil {
if err == packages_model.ErrPackageBlobUploadNotExist { if errors.Is(err, packages_model.ErrPackageBlobUploadNotExist) {
apiErrorDefined(ctx, errBlobUploadUnknown) apiErrorDefined(ctx, errBlobUploadUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -501,7 +501,7 @@ func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescri
func HeadBlob(ctx *context.Context) { func HeadBlob(ctx *context.Context) {
blob, err := getBlobFromContext(ctx) blob, err := getBlobFromContext(ctx)
if err != nil { if err != nil {
if err == container_model.ErrContainerBlobNotExist { if errors.Is(err, container_model.ErrContainerBlobNotExist) {
apiErrorDefined(ctx, errBlobUnknown) apiErrorDefined(ctx, errBlobUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -520,7 +520,7 @@ func HeadBlob(ctx *context.Context) {
func GetBlob(ctx *context.Context) { func GetBlob(ctx *context.Context) {
blob, err := getBlobFromContext(ctx) blob, err := getBlobFromContext(ctx)
if err != nil { if err != nil {
if err == container_model.ErrContainerBlobNotExist { if errors.Is(err, container_model.ErrContainerBlobNotExist) {
apiErrorDefined(ctx, errBlobUnknown) apiErrorDefined(ctx, errBlobUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -639,7 +639,7 @@ func getManifestFromContext(ctx *context.Context) (*packages_model.PackageFileDe
func HeadManifest(ctx *context.Context) { func HeadManifest(ctx *context.Context) {
manifest, err := getManifestFromContext(ctx) manifest, err := getManifestFromContext(ctx)
if err != nil { if err != nil {
if err == container_model.ErrContainerBlobNotExist { if errors.Is(err, container_model.ErrContainerBlobNotExist) {
apiErrorDefined(ctx, errManifestUnknown) apiErrorDefined(ctx, errManifestUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -659,7 +659,7 @@ func HeadManifest(ctx *context.Context) {
func GetManifest(ctx *context.Context) { func GetManifest(ctx *context.Context) {
manifest, err := getManifestFromContext(ctx) manifest, err := getManifestFromContext(ctx)
if err != nil { if err != nil {
if err == container_model.ErrContainerBlobNotExist { if errors.Is(err, container_model.ErrContainerBlobNotExist) {
apiErrorDefined(ctx, errManifestUnknown) apiErrorDefined(ctx, errManifestUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -739,7 +739,7 @@ func GetTagList(ctx *context.Context) {
image := ctx.PathParam("image") image := ctx.PathParam("image")
if _, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeContainer, image); err != nil { if _, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeContainer, image); err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiErrorDefined(ctx, errNameUnknown) apiErrorDefined(ctx, errNameUnknown)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -240,7 +240,7 @@ func processImageManifestIndex(ctx context.Context, mci *manifestCreationInfo, b
IsManifest: true, IsManifest: true,
}) })
if err != nil { if err != nil {
if err == container_model.ErrContainerBlobNotExist { if errors.Is(err, container_model.ErrContainerBlobNotExist) {
return errManifestBlobUnknown return errManifestBlobUnknown
} }
return err return err
@ -321,12 +321,11 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
} }
var err error var err error
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if err == packages_model.ErrDuplicatePackage { if !errors.Is(err, packages_model.ErrDuplicatePackage) {
created = false
} else {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return nil, err return nil, err
} }
created = false
} }
if created { if created {
@ -352,8 +351,12 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
} }
var pv *packages_model.PackageVersion var pv *packages_model.PackageVersion
if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
if err == packages_model.ErrDuplicatePackageVersion { if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { log.Error("Error inserting package: %v", err)
return nil, err
}
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
return nil, err return nil, err
} }
@ -361,12 +364,10 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
_pv.DownloadCount = pv.DownloadCount _pv.DownloadCount = pv.DownloadCount
if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return nil, err return nil, err
} }
} else {
log.Error("Error inserting package: %v", err)
return nil, err
} }
} }
@ -417,7 +418,7 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package
} }
var err error var err error
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil { if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
if err == packages_model.ErrDuplicatePackageFile { if errors.Is(err, packages_model.ErrDuplicatePackageFile) {
// Skip this blob because the manifest contains the same filesystem layer multiple times. // Skip this blob because the manifest contains the same filesystem layer multiple times.
return nil return nil
} }

View File

@ -68,7 +68,7 @@ func GetRepositoryFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
} else { } else {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -44,7 +44,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -155,7 +155,7 @@ func DeletePackage(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -182,7 +182,7 @@ func DeletePackageFile(ctx *context.Context) {
return pv, pf, nil return pv, pf, nil
}() }()
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -130,7 +130,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -67,6 +67,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
BundleDependencies: metadata.BundleDependencies, BundleDependencies: metadata.BundleDependencies,
DevDependencies: metadata.DevelopmentDependencies, DevDependencies: metadata.DevelopmentDependencies,
PeerDependencies: metadata.PeerDependencies, PeerDependencies: metadata.PeerDependencies,
PeerDependenciesMeta: metadata.PeerDependenciesMeta,
OptionalDependencies: metadata.OptionalDependencies, OptionalDependencies: metadata.OptionalDependencies,
Readme: metadata.Readme, Readme: metadata.Readme,
Bin: metadata.Bin, Bin: metadata.Bin,

View File

@ -98,7 +98,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -140,7 +140,7 @@ func DownloadPackageFileByName(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -163,7 +163,7 @@ func UploadPackage(ctx *context.Context) {
return return
} }
repo, err := repo_model.GetRepositoryByURL(ctx, npmPackage.Metadata.Repository.URL) repo, err := repo_model.GetRepositoryByURLRelax(ctx, npmPackage.Metadata.Repository.URL)
if err == nil { if err == nil {
canWrite := repo.OwnerID == ctx.Doer.ID canWrite := repo.OwnerID == ctx.Doer.ID
@ -267,7 +267,7 @@ func DeletePackageVersion(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -341,7 +341,7 @@ func AddPackageTag(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName, version) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName, version)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -259,7 +259,7 @@ func RegistrationLeafV2(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -288,7 +288,7 @@ func RegistrationLeafV3(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -418,7 +418,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -671,7 +671,7 @@ func DownloadSymbolFile(ctx *context.Context) {
s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -699,7 +699,7 @@ func DeletePackage(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -124,7 +124,7 @@ func PackageVersionMetadata(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -233,7 +233,7 @@ func FinalizePackage(ctx *context.Context) {
_, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) _, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -258,7 +258,7 @@ func DownloadPackageFile(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion)
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }

View File

@ -5,11 +5,13 @@ package pypi
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"unicode"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -93,7 +95,7 @@ func DownloadPackageFile(ctx *context.Context) {
}, },
) )
if err != nil { if err != nil {
if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err) apiError(ctx, http.StatusNotFound, err)
return return
} }
@ -139,9 +141,30 @@ func UploadPackageFile(ctx *context.Context) {
return return
} }
projectURL := ctx.Req.FormValue("home_page") // Ensure ctx.Req.Form exists.
if !validation.IsValidURL(projectURL) { _ = ctx.Req.ParseForm()
projectURL = ""
var homepageURL string
projectURLs := ctx.Req.Form["project_urls"]
for _, purl := range projectURLs {
label, url, found := strings.Cut(purl, ",")
if !found {
continue
}
if normalizeLabel(label) != "homepage" {
continue
}
homepageURL = strings.TrimSpace(url)
break
}
if len(homepageURL) == 0 {
// TODO: Home-page is a deprecated metadata field. Remove this branch once it's no longer apart of the spec.
homepageURL = ctx.Req.FormValue("home_page")
}
if !validation.IsValidURL(homepageURL) {
homepageURL = ""
} }
_, _, err = packages_service.CreatePackageOrAddFileToExisting( _, _, err = packages_service.CreatePackageOrAddFileToExisting(
@ -160,7 +183,7 @@ func UploadPackageFile(ctx *context.Context) {
Description: ctx.Req.FormValue("description"), Description: ctx.Req.FormValue("description"),
LongDescription: ctx.Req.FormValue("long_description"), LongDescription: ctx.Req.FormValue("long_description"),
Summary: ctx.Req.FormValue("summary"), Summary: ctx.Req.FormValue("summary"),
ProjectURL: projectURL, ProjectURL: homepageURL,
License: ctx.Req.FormValue("license"), License: ctx.Req.FormValue("license"),
RequiresPython: ctx.Req.FormValue("requires_python"), RequiresPython: ctx.Req.FormValue("requires_python"),
}, },
@ -189,6 +212,23 @@ func UploadPackageFile(ctx *context.Context) {
ctx.Status(http.StatusCreated) ctx.Status(http.StatusCreated)
} }
// Normalizes a Project-URL label.
// See https://packaging.python.org/en/latest/specifications/well-known-project-urls/#label-normalization.
func normalizeLabel(label string) string {
var builder strings.Builder
// "A label is normalized by deleting all ASCII punctuation and whitespace, and then converting the result
// to lowercase."
for _, r := range label {
if unicode.IsPunct(r) || unicode.IsSpace(r) {
continue
}
builder.WriteRune(unicode.ToLower(r))
}
return builder.String()
}
func isValidNameAndVersion(packageName, packageVersion string) bool { func isValidNameAndVersion(packageName, packageVersion string) bool {
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion) return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
} }

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