Merge pull request 'merge: upstream Gitea v1.26.1 into MokoGitea' (#80) from merge/v1.26.1 into main
This commit was merged in pull request #80.
This commit is contained in:
+5
-5
@@ -24,7 +24,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Fix button layout shift when collapsing file tree in editor (#37363) #37375
|
||||
* Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391
|
||||
* Fix repo init README EOL (#37388) #37399
|
||||
* Fix: dump with default zip type produces uncompressed zip (#37401) #37402
|
||||
* Fix: dump with default zip type produces uncompressed zip (#37401)#37402
|
||||
|
||||
## [1.26.0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0) - 2026-04-17
|
||||
|
||||
@@ -1383,7 +1383,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Fix mCaptcha bug (#33659) (#33661)
|
||||
* Git graph: don't show detached commits (#33645) (#33650)
|
||||
* Use MatchPhraseQuery for bleve code search (#33628)
|
||||
* Adjust appearance of commit status webhook (#33778) #33789
|
||||
* Adjust appearence of commit status webhook (#33778) #33789
|
||||
* Upgrade golang net from 0.35.0 -> 0.36.0 (#33795) #33796
|
||||
|
||||
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
|
||||
@@ -2114,7 +2114,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Optimize repo-list layout to enhance visual experience (#31272) (#31276)
|
||||
* fixed the dropdown menu for the top New button to expand to the left (#31273) (#31275)
|
||||
* Fix Activity Page Contributors dropdown (#31264) (#31269)
|
||||
* fix: allow actions artifacts storage migration to complete successfully (#31251) (#31257)
|
||||
* fix: allow actions artifacts storage migration to complete succesfully (#31251) (#31257)
|
||||
* Make blockquote attention recognize more syntaxes (#31240) (#31250)
|
||||
* Remove .segment from .project-column (#31204) (#31239)
|
||||
* Ignore FindRecentlyPushedNewBranches err (#31164) (#31171)
|
||||
@@ -2298,7 +2298,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Performance optimization for git push and check permissions for push options (#30104) (#30354)
|
||||
* BUGFIXES
|
||||
* Fix close file in the Upload func (#30262) (#30269)
|
||||
* Fix inline math blocks can't be preceded/followed by alphanumerical characters (#30175) (#30250)
|
||||
* Fix inline math blocks can't be preceeded/followed by alphanumerical characters (#30175) (#30250)
|
||||
* Fix missing 0 prefix of GPG key id (#30245) (#30247)
|
||||
* Include encoding in signature payload (#30174) (#30181)
|
||||
* Move from `max( id )` to `max( index )` for latest commit statuses (#30076) (#30155)
|
||||
@@ -5590,7 +5590,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Fix navbar on project view (#17749)
|
||||
* More pleasantly handle broken or missing git repositories (#17747)
|
||||
* Use `*PushUpdateOptions` as receiver (#17724)
|
||||
* Remove unused `user` parameter (#17723)
|
||||
* Remove unused `user` paramater (#17723)
|
||||
* Better builtin avatar generator (#17707)
|
||||
* Cleanup and use global style on popups (#17674)
|
||||
* Move user/org deletion to services (#17673)
|
||||
|
||||
@@ -11,8 +11,7 @@ require (
|
||||
code.gitea.io/actions-proto-go v0.4.1
|
||||
code.gitea.io/sdk/gitea v0.24.1
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.19.2
|
||||
gitea.com/gitea/runner v1.0.0
|
||||
connectrpc.com/connect v1.19.1
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
@@ -21,24 +20,24 @@ require (
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/httpsig v1.2.4
|
||||
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3
|
||||
github.com/Azure/go-ntlmssp v0.1.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
|
||||
github.com/Azure/go-ntlmssp v0.1.0
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/PuerkitoBio/goquery v1.12.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
|
||||
github.com/alecthomas/chroma/v2 v2.24.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.13
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.6.0
|
||||
github.com/blevesearch/bleve/v2 v2.5.7
|
||||
github.com/bohde/codel v0.2.0
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8
|
||||
github.com/caddyserver/certmagic v0.25.3
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260309112543-12416315a635
|
||||
github.com/caddyserver/certmagic v0.25.2
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
|
||||
github.com/dlclark/regexp2 v1.12.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.4
|
||||
@@ -46,53 +45,54 @@ require (
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/getkin/kin-openapi v0.137.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-co-op/gocron/v2 v2.21.1
|
||||
github.com/go-enry/go-enry/v2 v2.9.6
|
||||
github.com/go-co-op/gocron/v2 v2.19.1
|
||||
github.com/go-enry/go-enry/v2 v2.9.5
|
||||
github.com/go-git/go-billy/v5 v5.8.0
|
||||
github.com/go-git/go-git/v5 v5.18.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.13
|
||||
github.com/go-redsync/redsync/v4 v4.16.0
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/go-webauthn/webauthn v0.17.2
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.16.1
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/google/go-github/v84 v84.0.0
|
||||
github.com/google/licenseclassifier/v2 v2.0.0
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/hashicorp/go-version v1.9.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20260303211410-1a4bdc82ecec
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
github.com/jhillyerd/enmime/v2 v2.3.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/lib/pq v1.12.1
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/mattn/go-isatty v0.0.22
|
||||
github.com/mattn/go-sqlite3 v1.14.44
|
||||
github.com/meilisearch/meilisearch-go v0.36.2
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.38
|
||||
github.com/meilisearch/meilisearch-go v0.36.1
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/microsoft/go-mssqldb v1.9.7
|
||||
github.com/minio/minio-go/v7 v7.1.0
|
||||
github.com/microsoft/go-mssqldb v1.9.6
|
||||
github.com/minio/minio-go/v7 v7.0.99
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/nektos/act v0.2.63
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
github.com/olivere/elastic/v7 v7.0.32
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
@@ -102,7 +102,7 @@ require (
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli-docs/v3 v3.1.0
|
||||
github.com/urfave/cli/v3 v3.6.1
|
||||
github.com/urfave/cli/v3 v3.4.1
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
@@ -110,18 +110,17 @@ require (
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/image v0.39.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/image v0.38.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/text v0.36.0
|
||||
google.golang.org/grpc v1.81.0
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/text v0.35.0
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/ini.v1 v1.67.2
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.50.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
|
||||
xorm.io/builder v0.3.13
|
||||
@@ -130,6 +129,7 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
code.gitea.io/gitea-vet v0.2.3 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
@@ -140,20 +140,20 @@ require (
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
||||
github.com/aws/smithy-go v1.25.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.7 // indirect
|
||||
github.com/blevesearch/geo v0.2.5 // indirect
|
||||
github.com/blevesearch/go-faiss v1.1.0 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.30 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.5 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
@@ -163,8 +163,7 @@ require (
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||
github.com/blevesearch/zapx/v17 v17.1.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.2 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
@@ -184,6 +183,7 @@ require (
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
@@ -193,10 +193,8 @@ require (
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.3 // indirect
|
||||
github.com/go-webauthn/x v0.2.2 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -222,7 +220,7 @@ require (
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mailru/easyjson v0.9.2 // indirect
|
||||
github.com/markbates/going v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.21 // indirect
|
||||
@@ -235,20 +233,15 @@ require (
|
||||
github.com/minio/minlz v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/oasdiff/yaml v0.0.9 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.12 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
@@ -257,7 +250,6 @@ require (
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rhysd/actionlint v1.7.12 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
@@ -267,16 +259,15 @@ require (
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/zeebo/assert v1.3.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -286,14 +277,11 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
ignore (
|
||||
@@ -307,6 +295,10 @@ ignore (
|
||||
|
||||
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 // jaytaylor/html2text is unmaintained
|
||||
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.10 // gitea maintains its own package
|
||||
|
||||
replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.4.1 // v3.6.2 breaks -c flag parsing in help commands
|
||||
|
||||
replace go.yaml.in/yaml/v4 => go.yaml.in/yaml/v4 v4.0.0-rc.3 // rc.4 changes block scalar serialization, wait for stable release
|
||||
|
||||
replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // v1.21.0+ uses API version unsupported by Azurite in CI
|
||||
@@ -314,3 +306,5 @@ replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-s
|
||||
replace github.com/Azure/azure-sdk-for-go/sdk/storage/azblob => github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 // v1.6.4+ uses API version unsupported by Azurite in CI
|
||||
|
||||
replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.9.7 // downgraded with Azure SDK
|
||||
|
||||
tool code.gitea.io/gitea-vet
|
||||
|
||||
@@ -2,20 +2,22 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls=
|
||||
code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
||||
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.24.1 h1:hpaqcdGcBmfMpV7JSbBJVwE99qo+WqGreJYKrDKEyW8=
|
||||
code.gitea.io/sdk/gitea v0.24.1/go.mod h1:5/77BL3sHneCMEiZaMT9lfTvnnibsYxyO48mceCF3qA=
|
||||
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
|
||||
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
gitea.com/gitea/runner v1.0.0 h1:s3AS5u8r+B5W+Gy69sYYvCVTb0f23fRM6H+CbrFrobE=
|
||||
gitea.com/gitea/runner v1.0.0/go.mod h1:bn+8Qt3KyvdVQHD3OR2yRoKgEXs8dXqwBVtmfYVrNIE=
|
||||
gitea.com/gitea/act v0.261.10 h1:ndwbtuMXXz1dpYF2iwY1/PkgKNETo4jmPXfinTZt8cs=
|
||||
gitea.com/gitea/act v0.261.10/go.mod h1:oIkqQHvU0lfuIWwcpqa4FmU+t3prA89tgkuHUTsrI2c=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a h1:JHoBrfuTSF9Ke9aNfSYj1XRPBHjKPgCApVprnt2Am0M=
|
||||
gitea.com/go-chi/binding v0.0.0-20260414111559-654cea7ac60a/go.mod h1:FOsLJIMdpiHzBp3Vby6Wfkdw2ppGscrjgU1IC7E4/zQ=
|
||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
@@ -48,8 +50,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuo
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
|
||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -76,8 +78,8 @@ github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0/go.mod h1:1HmmMEVsr+0R
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
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.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
@@ -92,18 +94,18 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14 h1:3N664oayz66ttIkc8B9/OLntMWhoGhKqPudRRfsZQ20=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14/go.mod h1:M+6j5lOmtDMjLlFMO8lfTs0gI3qBsSjM8L7F1cQF5Ng=
|
||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.13 h1:mA59E3fokBvyEGHKFdnpNNrvaR351cqiHgRg+JzOSRI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.13/go.mod h1:yoTXOQKea18nrM69wGF9jBdG4WocSZA1h38A+t/MAsk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12 h1:yv3mfWt/eiDTTry6fkN5hh8wHJfU5ygnw+DJp10C0/c=
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12/go.mod h1:voO3LP/dZ4CTERiNWCz3DFLbK/8hbfeC1OJkLW+sang=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -115,15 +117,15 @@ github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM=
|
||||
github.com/blevesearch/bleve/v2 v2.6.0 h1:Cyd3dd4q5tCbOV8MnKUVRUDYMHOir9xn12NZzXVSEd4=
|
||||
github.com/blevesearch/bleve/v2 v2.6.0/go.mod h1:gLmI8lWgHgrIYf7UpUX7JISI1CaqC6VScu46mHThuAY=
|
||||
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
|
||||
github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/bleve_index_api v1.3.7 h1:x3C4pl6o3MlSJ3rNKLQpZMRF0uR9+BDvmF3t8LYCOpk=
|
||||
github.com/blevesearch/bleve_index_api v1.3.7/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
|
||||
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
|
||||
github.com/blevesearch/go-faiss v1.1.0 h1:xM7Jc0ZUCv5lssG9Ohj3Jv0SdTpxcUABU1dDt9XVsc4=
|
||||
github.com/blevesearch/go-faiss v1.1.0/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-faiss v1.0.30 h1:pWX3/Si4Z7GlwsD2eRXoF3SfVaDkg8plBlPdUKuhGts=
|
||||
github.com/blevesearch/go-faiss v1.0.30/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
@@ -132,8 +134,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
|
||||
github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
|
||||
github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 h1:GlMzW08hcsM3DnLUxhyF/1PcDal1qtvvIuytuph5djw=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7/go.mod h1://IJ7tG3QCf0cWW/aVSXqy77tc1AvLu3fcJLYEvOAFs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.5 h1:Q7Bzpyk86xS22TgTd4VQfSvzZAybDEJ90hNOGqyNqlI=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.5/go.mod h1:xWYn3EwRM7zBFAPt/J136OugUNzftpYLvPBBx31IpCw=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
@@ -161,10 +163,8 @@ github.com/blevesearch/zapx/v14 v14.4.3/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXm
|
||||
github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2lc/0/CU=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2 h1:avbOk2igaASNoiy0BE/jPgcxAnRI2PGeydeP4hg7Ikk=
|
||||
github.com/blevesearch/zapx/v17 v17.1.2/go.mod h1:WQObxKrqUX7cd0G1GMvDfc/bmZzQvoy7APOPimx7DiI=
|
||||
github.com/blevesearch/zapx/v16 v16.3.2 h1:R8alnAeYxzKO4JjQY4FBqJduJwcPE3YjcgevWgI+XT0=
|
||||
github.com/blevesearch/zapx/v16 v16.3.2/go.mod h1:zCFjv7McXWm1C8rROL+3mUoD5WYe2RKsZP3ufqcYpLY=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||
@@ -187,16 +187,16 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM=
|
||||
github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A=
|
||||
github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||
github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=
|
||||
github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260309112543-12416315a635 h1:RwCfD5XyO8jAermEy6pauh5Q5o6mCvbeNcCPOZlIA5o=
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260309112543-12416315a635/go.mod h1:R+SetERD4+IL7QH0WHp9MLifvITvh9gL3g8vX1j2Fcs=
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 h1:2d64+4Jek9vjYwhY93AjbleiVH+AeWvPwPmDi1mfKFQ=
|
||||
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21/go.mod h1:fNlYtCHWTRC8MofQERZkVUNUWaOvZeTBqHn/amSbKZI=
|
||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
|
||||
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
@@ -241,8 +241,8 @@ github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2r
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
@@ -269,14 +269,14 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/getkin/kin-openapi v0.137.0 h1:Q3HhawNQV0GfvO2mIYMUBUSEFrDsVlzcYz4VydL9YEo=
|
||||
github.com/getkin/kin-openapi v0.137.0/go.mod h1:vUYWaKyMqj7PfTybelXtLuLN9tReS12vxnzMRK+z2GY=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -290,10 +290,10 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron/v2 v2.21.1 h1:QYOK6iOQVCut+jDcs4zRdWRTBHRxRCEeeFi1TnAmgbU=
|
||||
github.com/go-co-op/gocron/v2 v2.21.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||
github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs=
|
||||
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-co-op/gocron/v2 v2.19.1 h1:B4iLeA0NB/2iO3EKQ7NfKn5KsQgZfjb2fkvoZJU3yBI=
|
||||
github.com/go-co-op/gocron/v2 v2.19.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||
github.com/go-enry/go-enry/v2 v2.9.5 h1:HPhAQQHYwJgihL2PxBZiUMFWiROsGwOBdB6/D8zCUhY=
|
||||
github.com/go-enry/go-enry/v2 v2.9.5/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
|
||||
@@ -308,14 +308,10 @@ github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5
|
||||
github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
|
||||
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-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
||||
@@ -324,17 +320,17 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-redsync/redsync/v4 v4.16.0 h1:bNcOzeHH9d3s6pghU9NJFMPrQa41f5Nx3L4YKr3BdEU=
|
||||
github.com/go-redsync/redsync/v4 v4.16.0/go.mod h1:V4gagqgyASWBZuwx4xGzu72aZNb/6Mo05byUa3mVmKQ=
|
||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.17.2 h1:e9YtSZTVnxnMWFezXi6JvnqOSxmH4Er8QDHK2a/mM40=
|
||||
github.com/go-webauthn/webauthn v0.17.2/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
|
||||
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
|
||||
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
|
||||
github.com/go-webauthn/webauthn v0.16.1 h1:x5/SSki5/aIfogaRukqvbg/RXa3Sgxy/9vU7UfFPHKU=
|
||||
github.com/go-webauthn/webauthn v0.16.1/go.mod h1:RBS+rtQJMkE5VfMQ4diDA2VNrEL8OeUhp4Srz37FHbQ=
|
||||
github.com/go-webauthn/x v0.2.2 h1:zIiipvMbr48CXi5RG0XdBJR94kd8I5LfzHPb/q+YYmk=
|
||||
github.com/go-webauthn/x v0.2.2/go.mod h1:IpJ5qyWB9NRhLX3C7gIfjTU7RZLXEP6kzFkoVSE7Fz4=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
@@ -395,8 +391,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA=
|
||||
github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -477,8 +473,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -502,29 +498,30 @@ github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh
|
||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/lib/pq v1.12.1 h1:x1nbl/338GLqeDJ/FAiILallhAsqubLzEZu/pXtHUow=
|
||||
github.com/lib/pq v1.12.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
|
||||
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE=
|
||||
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
|
||||
github.com/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ=
|
||||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
||||
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4=
|
||||
github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.36.1 h1:mJTCJE5g7tRvaqKco6DfqOuJEjX+rRltDEnkEC02Y0M=
|
||||
github.com/meilisearch/meilisearch-go v0.36.1/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
@@ -541,8 +538,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
|
||||
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
|
||||
github.com/minio/minio-go/v7 v7.0.99 h1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=
|
||||
github.com/minio/minio-go/v7 v7.0.99/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||
github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ=
|
||||
github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -552,8 +549,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
@@ -563,8 +558,8 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG
|
||||
github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
|
||||
@@ -572,10 +567,6 @@ github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsR
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48=
|
||||
github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM=
|
||||
github.com/oasdiff/yaml3 v0.0.12 h1:75urAtPeDg2/iDEWwzNrLOWxI9N/dCh81nTTJtokt2M=
|
||||
github.com/oasdiff/yaml3 v0.0.12/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
@@ -584,6 +575,8 @@ github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
|
||||
github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
@@ -600,8 +593,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
@@ -629,8 +620,8 @@ github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4Ul
|
||||
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
|
||||
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/redis/rueidis v1.0.71 h1:pODtnAR5GAB7j4ekhldZ29HKOxe4Hph0GTDGk1ayEQY=
|
||||
github.com/redis/rueidis v1.0.71/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ=
|
||||
github.com/redis/rueidis/rueidiscompat v1.0.71 h1:wNZ//kEjMZgBM0KCk7ncOX8KmAgROU2kDdDNpwheG4w=
|
||||
@@ -664,8 +655,9 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
|
||||
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
@@ -686,9 +678,8 @@ github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -706,13 +697,11 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKN
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
|
||||
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
@@ -720,13 +709,11 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
|
||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
|
||||
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -745,6 +732,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -758,8 +746,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/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24=
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@@ -797,25 +785,27 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
|
||||
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
|
||||
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
|
||||
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@@ -829,12 +819,13 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -877,8 +868,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -889,8 +880,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -901,29 +892,30 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 h1:YedBIttDguBl/zy2wJauEUm+DZZg4UXseWj0g/3N+yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -937,8 +929,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
|
||||
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
@@ -952,32 +944,24 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
|
||||
@@ -35,11 +35,11 @@ func (r *RepoFile) ResolveLink(link, preferLinkType string) (finalLink string) {
|
||||
case markup.LinkTypeRoot:
|
||||
finalLink = r.ctx.ResolveLinkRoot(link)
|
||||
case markup.LinkTypeRaw:
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||
case markup.LinkTypeMedia:
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||
default:
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
||||
}
|
||||
return finalLink
|
||||
}
|
||||
@@ -50,8 +50,8 @@ type RepoFileOptions struct {
|
||||
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||
|
||||
CurrentRefSubURL string // eg: "branch/main" or "branch/my%20branch", it is a sub URL path escaped by callers
|
||||
CurrentTreePath string // eg: "path/to/file" in the repo, it is the tree path without URL path escaping
|
||||
CurrentRefPath string // eg: "branch/main", it is a sub URL path escaped by callers, TODO: rename to CurrentRefSubURL
|
||||
CurrentTreePath string // eg: "path/to/file" in the repo, it is the tree path without URL path escaping
|
||||
}
|
||||
|
||||
func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, opts ...RepoFileOptions) *markup.RenderContext {
|
||||
@@ -71,8 +71,9 @@ func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository,
|
||||
})
|
||||
}
|
||||
// External render's iframe needs this to generate correct links
|
||||
// TODO: maybe need to make it access "CurrentRefSubURL" directly (but impossible at the moment due to cycle-import)
|
||||
rctx.RenderOptions.Metas["RefTypeNameSubURL"] = helper.opts.CurrentRefSubURL
|
||||
// TODO: maybe need to make it access "CurrentRefPath" directly (but impossible at the moment due to cycle-import)
|
||||
// CurrentRefPath is already path-escaped by callers
|
||||
rctx.RenderOptions.Metas["RefTypeNameSubURL"] = helper.opts.CurrentRefPath
|
||||
rctx = rctx.WithHelper(helper).WithEnableHeadingIDGeneration(true)
|
||||
return rctx
|
||||
}
|
||||
|
||||
+14
-60
@@ -6,16 +6,11 @@ package util
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
rand2 "math/rand/v2"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -63,60 +58,37 @@ func NormalizeEOL(input []byte) []byte {
|
||||
}
|
||||
|
||||
// CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive
|
||||
func CryptoRandomInt(limit int64) int64 {
|
||||
func CryptoRandomInt(limit int64) (int64, error) {
|
||||
rInt, err := rand.Int(rand.Reader, big.NewInt(limit))
|
||||
if err != nil {
|
||||
panic(err) // this should never happen
|
||||
return 0, err
|
||||
}
|
||||
return rInt.Int64()
|
||||
return rInt.Int64(), nil
|
||||
}
|
||||
|
||||
const alphanumericalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// CryptoRandomString generates a crypto random alphanumerical string, each byte is generated by [0,61] range
|
||||
func CryptoRandomString(length int64) string {
|
||||
const alphanumericalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
func CryptoRandomString(length int64) (string, error) {
|
||||
buf := make([]byte, length)
|
||||
limit := int64(len(alphanumericalChars))
|
||||
for i := range buf {
|
||||
num := CryptoRandomInt(limit)
|
||||
num, err := CryptoRandomInt(limit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf[i] = alphanumericalChars[num]
|
||||
}
|
||||
return string(buf)
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// CryptoRandomBytes generates `length` crypto bytes
|
||||
// This differs from CryptoRandomString, as each byte in CryptoRandomString is generated by [0,61] range
|
||||
// This function generates totally random bytes, each byte is generated by [0,255] range
|
||||
func CryptoRandomBytes(length int64) []byte {
|
||||
func CryptoRandomBytes(length int64) ([]byte, error) {
|
||||
buf := make([]byte, length)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
panic(err) // this should never happen, "rand.Read" never fails
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
var chaCha8RandPool = sync.OnceValue(func() *sync.Pool {
|
||||
return &sync.Pool{
|
||||
New: func() any {
|
||||
seed := CryptoRandomBytes(32)
|
||||
return rand2.NewChaCha8([32]byte(seed))
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
func FastCryptoRandomBytes(length int) []byte {
|
||||
// ChaCha8 is about 20x times faster than system's crypto/rand.
|
||||
// It is suitable for UUIDs, session IDs, etc
|
||||
pool := chaCha8RandPool()
|
||||
chaCha8Rand := pool.Get().(*rand2.ChaCha8)
|
||||
defer pool.Put(chaCha8Rand)
|
||||
buf := make([]byte, length)
|
||||
_, _ = chaCha8Rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func FastCryptoRandomHex(length int) string {
|
||||
buf := FastCryptoRandomBytes(length / 2)
|
||||
return hex.EncodeToString(buf)
|
||||
_, err := rand.Read(buf)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// ToLowerASCII returns s with all ASCII letters mapped to their lower case.
|
||||
@@ -293,21 +265,3 @@ func NormalizeStringEOL(input string) string {
|
||||
// Other than this, we should respect the original content, even leading or trailing spaces.
|
||||
return UnsafeBytesToString(NormalizeEOL(UnsafeStringToBytes(input)))
|
||||
}
|
||||
|
||||
func DiffSlice[T comparable](oldSlice, newSlice []T) (added, removed []T) {
|
||||
oldSet := container.SetOf(oldSlice...)
|
||||
newSet := container.SetOf(newSlice...)
|
||||
|
||||
addedSet, removedSet := container.Set[T]{}, container.Set[T]{}
|
||||
for _, v := range newSlice {
|
||||
if !oldSet.Contains(v) && addedSet.Add(v) {
|
||||
added = append(added, v)
|
||||
}
|
||||
}
|
||||
for _, v := range oldSlice {
|
||||
if !newSet.Contains(v) && removedSet.Add(v) {
|
||||
removed = append(removed, v)
|
||||
}
|
||||
}
|
||||
return added, removed
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
@@ -50,6 +51,7 @@ func (j jsonProvider) NewEncoder(writer io.Writer) binding.JSONEncoder {
|
||||
func AddBindingRules() {
|
||||
binding.JSONProvider = jsonProvider{}
|
||||
addGitRefNameBindingRule()
|
||||
addValidURLListBindingRule()
|
||||
addValidURLBindingRule()
|
||||
addValidSiteURLBindingRule()
|
||||
addGlobPatternRule()
|
||||
@@ -78,6 +80,33 @@ func addGitRefNameBindingRule() {
|
||||
})
|
||||
}
|
||||
|
||||
func addValidURLListBindingRule() {
|
||||
// URL validation rule
|
||||
binding.AddRule(&binding.Rule{
|
||||
IsMatch: func(rule string) bool {
|
||||
return rule == "ValidUrlList"
|
||||
},
|
||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||
str := fmt.Sprintf("%v", val)
|
||||
if len(str) == 0 {
|
||||
errs.Add([]string{name}, binding.ERR_URL, "Url")
|
||||
return false, errs
|
||||
}
|
||||
|
||||
ok := true
|
||||
urls := util.SplitTrimSpace(str, "\n")
|
||||
for _, u := range urls {
|
||||
if !IsValidURL(u) {
|
||||
ok = false
|
||||
errs.Add([]string{name}, binding.ERR_URL, u)
|
||||
}
|
||||
}
|
||||
|
||||
return ok, errs
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func addValidURLBindingRule() {
|
||||
// URL validation rule
|
||||
binding.AddRule(&binding.Rule{
|
||||
|
||||
+40
-41
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.33.2",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"engines": {
|
||||
"node": ">= 22.18.0",
|
||||
"pnpm": ">= 10.0.0"
|
||||
@@ -18,48 +18,50 @@
|
||||
"@codemirror/language-data": "6.5.2",
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/lint": "6.9.5",
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.41.1",
|
||||
"@deltablot/dropzone": "7.4.3",
|
||||
"@codemirror/view": "6.41.0",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/paste-markdown": "1.5.3",
|
||||
"@github/text-expander-element": "2.9.4",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@mermaid-js/layout-elk": "0.2.1",
|
||||
"@primer/octicons": "19.25.0",
|
||||
"@primer/octicons": "19.23.1",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@replit/codemirror-lang-nix": "6.0.1",
|
||||
"@replit/codemirror-lang-svelte": "6.0.0",
|
||||
"@replit/codemirror-vscode-keymap": "6.0.2",
|
||||
"@resvg/resvg-wasm": "2.6.2",
|
||||
"@vitejs/plugin-vue": "6.0.6",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.1.1",
|
||||
"@vitejs/plugin-vue": "6.0.5",
|
||||
"ansi_up": "6.0.6",
|
||||
"asciinema-player": "3.15.1",
|
||||
"chart.js": "4.5.1",
|
||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"clippie": "4.1.15",
|
||||
"clippie": "4.1.10",
|
||||
"codemirror-lang-elixir": "4.0.1",
|
||||
"colord": "2.9.3",
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "1.6.2",
|
||||
"dayjs": "1.11.20",
|
||||
"easymde": "2.21.0",
|
||||
"esbuild": "0.28.0",
|
||||
"dropzone": "6.0.0-beta.2",
|
||||
"easymde": "2.20.0",
|
||||
"esbuild": "0.27.4",
|
||||
"htmx.org": "2.0.8",
|
||||
"idiomorph": "0.7.4",
|
||||
"jquery": "4.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"katex": "0.16.45",
|
||||
"katex": "0.16.44",
|
||||
"mermaid": "11.14.0",
|
||||
"online-3d-viewer": "0.18.0",
|
||||
"pdfobject": "2.3.1",
|
||||
"perfect-debounce": "2.1.0",
|
||||
"postcss": "8.5.13",
|
||||
"rolldown-license-plugin": "3.0.4",
|
||||
"postcss": "8.5.8",
|
||||
"rolldown-license-plugin": "2.2.0",
|
||||
"sortablejs": "1.15.7",
|
||||
"swagger-ui-dist": "5.32.5",
|
||||
"swagger-ui-dist": "5.32.1",
|
||||
"tailwindcss": "3.4.19",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tippy.js": "6.3.7",
|
||||
@@ -67,63 +69,64 @@
|
||||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.1",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vite": "8.0.10",
|
||||
"vite-string-plugin": "2.0.4",
|
||||
"vue": "3.5.33",
|
||||
"vite": "8.0.5",
|
||||
"vite-string-plugin": "2.0.2",
|
||||
"vue": "3.5.31",
|
||||
"vue-bar-graph": "2.2.0",
|
||||
"vue-chartjs": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.7.1",
|
||||
"@eslint/json": "1.2.0",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@playwright/test": "1.59.0",
|
||||
"@stylistic/eslint-plugin": "5.10.0",
|
||||
"@stylistic/stylelint-plugin": "5.1.0",
|
||||
"@types/codemirror": "5.60.17",
|
||||
"@types/dropzone": "5.7.9",
|
||||
"@types/jquery": "4.0.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/katex": "0.16.8",
|
||||
"@types/node": "25.6.0",
|
||||
"@types/node": "25.5.0",
|
||||
"@types/pdfobject": "2.2.5",
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/swagger-ui-dist": "3.30.6",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/toastify-js": "1.12.4",
|
||||
"@typescript-eslint/parser": "8.59.1",
|
||||
"@vitejs/plugin-vue": "6.0.6",
|
||||
"@vitest/eslint-plugin": "1.6.16",
|
||||
"eslint": "10.3.0",
|
||||
"@typescript-eslint/parser": "8.58.0",
|
||||
"@vitejs/plugin-vue": "6.0.5",
|
||||
"@vitest/eslint-plugin": "1.6.14",
|
||||
"eslint": "10.1.0",
|
||||
"eslint-import-resolver-typescript": "4.4.4",
|
||||
"eslint-plugin-array-func": "5.1.1",
|
||||
"eslint-plugin-de-morgan": "2.1.1",
|
||||
"eslint-plugin-github": "6.0.0",
|
||||
"eslint-plugin-import-x": "4.16.2",
|
||||
"eslint-plugin-playwright": "2.10.2",
|
||||
"eslint-plugin-playwright": "2.10.1",
|
||||
"eslint-plugin-regexp": "3.1.0",
|
||||
"eslint-plugin-sonarjs": "4.0.3",
|
||||
"eslint-plugin-sonarjs": "4.0.2",
|
||||
"eslint-plugin-unicorn": "64.0.0",
|
||||
"eslint-plugin-vue": "10.9.0",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"eslint-plugin-vue-scoped-css": "3.0.0",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"globals": "17.6.0",
|
||||
"happy-dom": "20.9.0",
|
||||
"globals": "17.4.0",
|
||||
"happy-dom": "20.8.9",
|
||||
"jiti": "2.6.1",
|
||||
"markdownlint-cli": "0.48.0",
|
||||
"material-icon-theme": "5.34.0",
|
||||
"material-icon-theme": "5.33.1",
|
||||
"nolyfill": "1.0.44",
|
||||
"postcss-html": "1.8.1",
|
||||
"spectral-cli-bundle": "1.0.8",
|
||||
"stylelint": "17.10.0",
|
||||
"spectral-cli-bundle": "1.0.7",
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-recommended": "18.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
|
||||
"stylelint-declaration-strict-value": "1.11.1",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.1.1",
|
||||
"svgo": "4.0.1",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.1",
|
||||
"updates": "17.16.8",
|
||||
"vitest": "4.1.5",
|
||||
"vue-tsc": "3.2.8"
|
||||
"typescript": "6.0.2",
|
||||
"typescript-eslint": "8.58.0",
|
||||
"updates": "17.13.1",
|
||||
"vitest": "4.1.2",
|
||||
"vue-tsc": "3.2.6"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
@@ -147,11 +150,7 @@
|
||||
"safe-regex-test": "npm:@nolyfill/safe-regex-test@^1",
|
||||
"safer-buffer": "npm:@nolyfill/safer-buffer@^1",
|
||||
"string.prototype.includes": "npm:@nolyfill/string.prototype.includes@^1",
|
||||
"string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1",
|
||||
"object-keys": "npm:@nolyfill/object-keys@^1",
|
||||
"object.entries": "npm:@nolyfill/object.entries@^1",
|
||||
"abab": "npm:@nolyfill/abab@^1",
|
||||
"es-set-tostringtag": "npm:@nolyfill/es-set-tostringtag@^1"
|
||||
"string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1153
-1804
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ import (
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
act_model "gitea.com/gitea/runner/act/model"
|
||||
act_model "github.com/nektos/act/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -26,20 +26,17 @@ func TestEvaluateRunConcurrency_RunIDFallback(t *testing.T) {
|
||||
runA := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 791})
|
||||
runB := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 792})
|
||||
|
||||
attemptA := &actions_model.ActionRunAttempt{RepoID: runA.RepoID, RunID: runA.ID, Attempt: 1}
|
||||
attemptB := &actions_model.ActionRunAttempt{RepoID: runB.RepoID, RunID: runB.ID, Attempt: 1}
|
||||
|
||||
expr := &act_model.RawConcurrency{
|
||||
Group: "${{ github.workflow }}-${{ github.head_ref || github.run_id }}",
|
||||
CancelInProgress: "true",
|
||||
}
|
||||
|
||||
assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runA, attemptA, expr, nil, nil))
|
||||
assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runB, attemptB, expr, nil, nil))
|
||||
assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runA, expr, nil, nil))
|
||||
assert.NoError(t, EvaluateRunConcurrencyFillModel(ctx, runB, expr, nil, nil))
|
||||
|
||||
assert.Contains(t, attemptA.ConcurrencyGroup, "791")
|
||||
assert.Contains(t, attemptB.ConcurrencyGroup, "792")
|
||||
assert.NotEqual(t, attemptA.ConcurrencyGroup, attemptB.ConcurrencyGroup)
|
||||
assert.Contains(t, runA.ConcurrencyGroup, "791")
|
||||
assert.Contains(t, runB.ConcurrencyGroup, "792")
|
||||
assert.NotEqual(t, runA.ConcurrencyGroup, runB.ConcurrencyGroup)
|
||||
}
|
||||
|
||||
func TestPrepareRunAndInsert_ExpressionsSeeRunID(t *testing.T) {
|
||||
@@ -81,10 +78,7 @@ jobs:
|
||||
persisted := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run.ID})
|
||||
runIDStr := strconv.FormatInt(run.ID, 10)
|
||||
assert.Equal(t, "Run "+runIDStr, persisted.Title)
|
||||
// ConcurrencyGroup lives on the latest attempt after migration v331.
|
||||
require.Positive(t, persisted.LatestAttemptID)
|
||||
attempt := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunAttempt{ID: persisted.LatestAttemptID})
|
||||
assert.Equal(t, "group-"+runIDStr, attempt.ConcurrencyGroup)
|
||||
assert.Equal(t, "group-"+runIDStr, persisted.ConcurrencyGroup)
|
||||
// Rerun reads raw_concurrency from the DB to re-evaluate the group;
|
||||
// see services/actions/rerun.go. Must survive the insert.
|
||||
assert.NotEmpty(t, persisted.RawConcurrency)
|
||||
|
||||
+62
-113
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -69,33 +70,30 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("get action run: %w", err)
|
||||
}
|
||||
var jobs, updatedJobs, cancelledJobs []*actions_model.ActionRunJob
|
||||
var jobs, updatedJobs []*actions_model.ActionRunJob
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// check jobs of the current run
|
||||
if js, ujs, cjs, err := checkJobsOfCurrentRunAttempt(ctx, run); err != nil {
|
||||
if js, ujs, err := checkJobsOfRun(ctx, run); err != nil {
|
||||
return err
|
||||
} else {
|
||||
jobs = append(jobs, js...)
|
||||
updatedJobs = append(updatedJobs, ujs...)
|
||||
cancelledJobs = append(cancelledJobs, cjs...)
|
||||
}
|
||||
if js, ujs, cjs, err := checkRunConcurrency(ctx, run); err != nil {
|
||||
if js, ujs, err := checkRunConcurrency(ctx, run); err != nil {
|
||||
return err
|
||||
} else {
|
||||
jobs = append(jobs, js...)
|
||||
updatedJobs = append(updatedJobs, ujs...)
|
||||
cancelledJobs = append(cancelledJobs, cjs...)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
NotifyWorkflowJobsAndRunsStatusUpdate(ctx, cancelledJobs)
|
||||
EmitJobsIfReadyByJobs(cancelledJobs)
|
||||
if err := createCommitStatusesForJobsByRun(ctx, jobs); err != nil {
|
||||
return err
|
||||
CreateCommitStatusForRunJobs(ctx, run, jobs...)
|
||||
for _, job := range updatedJobs {
|
||||
_ = job.LoadAttributes(ctx)
|
||||
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
|
||||
}
|
||||
NotifyWorkflowJobsStatusUpdate(ctx, updatedJobs...)
|
||||
runJobs := make(map[int64][]*actions_model.ActionRunJob)
|
||||
for _, job := range jobs {
|
||||
runJobs[job.RunID] = append(runJobs[job.RunID], job)
|
||||
@@ -116,97 +114,71 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
|
||||
}
|
||||
}
|
||||
if runUpdated {
|
||||
NotifyWorkflowRunStatusUpdateWithReload(ctx, js[0].RepoID, js[0].RunID)
|
||||
NotifyWorkflowRunStatusUpdateWithReload(ctx, js[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createCommitStatusesForJobsByRun(ctx context.Context, jobs []*actions_model.ActionRunJob) error {
|
||||
runJobs := make(map[int64][]*actions_model.ActionRunJob)
|
||||
for _, job := range jobs {
|
||||
runJobs[job.RunID] = append(runJobs[job.RunID], job)
|
||||
}
|
||||
|
||||
for jobRunID, jobList := range runJobs {
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, jobList[0].RepoID, jobRunID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get action run %d: %w", jobRunID, err)
|
||||
}
|
||||
CreateCommitStatusForRunJobs(ctx, run, jobList...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findBlockedRunIDByConcurrency finds a blocked concurrent run in a repo and returns 0 when there is no blocked run.
|
||||
func findBlockedRunIDByConcurrency(ctx context.Context, repoID int64, concurrencyGroup string) (int64, error) {
|
||||
// findBlockedRunByConcurrency finds the blocked concurrent run in a repo and returns `nil, nil` when there is no blocked run.
|
||||
func findBlockedRunByConcurrency(ctx context.Context, repoID int64, concurrencyGroup string) (*actions_model.ActionRun, error) {
|
||||
if concurrencyGroup == "" {
|
||||
return 0, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that no blocked run exists
|
||||
}
|
||||
cAttempts, cJobs, err := actions_model.GetConcurrentRunAttemptsAndJobs(ctx, repoID, concurrencyGroup, []actions_model.Status{actions_model.StatusBlocked})
|
||||
cRuns, cJobs, err := actions_model.GetConcurrentRunsAndJobs(ctx, repoID, concurrencyGroup, []actions_model.Status{actions_model.StatusBlocked})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
||||
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
|
||||
}
|
||||
|
||||
if len(cAttempts) > 0 {
|
||||
return cAttempts[0].RunID, nil
|
||||
}
|
||||
if len(cJobs) > 0 {
|
||||
return cJobs[0].RunID, nil
|
||||
// There can be at most one blocked run or job
|
||||
var concurrentRun *actions_model.ActionRun
|
||||
if len(cRuns) > 0 {
|
||||
concurrentRun = cRuns[0]
|
||||
} else if len(cJobs) > 0 {
|
||||
jobRun, exist, err := db.GetByID[actions_model.ActionRun](ctx, cJobs[0].RunID)
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("run %d does not exist", cJobs[0].RunID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get run by job %d: %w", cJobs[0].ID, err)
|
||||
}
|
||||
concurrentRun = jobRun
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
return concurrentRun, nil
|
||||
}
|
||||
|
||||
func checkBlockedConcurrentRun(ctx context.Context, repoID, runID int64) (jobs, updatedJobs, cancelledJobs []*actions_model.ActionRunJob, err error) {
|
||||
concurrentRun, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("get run %d: %w", runID, err)
|
||||
}
|
||||
if concurrentRun.NeedApproval {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
return checkJobsOfCurrentRunAttempt(ctx, concurrentRun)
|
||||
}
|
||||
|
||||
// checkRunConcurrency rechecks runs blocked by concurrency that may become unblocked after the current run releases a workflow-level or job-level concurrency group.
|
||||
func checkRunConcurrency(ctx context.Context, run *actions_model.ActionRun) (jobs, updatedJobs, cancelledJobs []*actions_model.ActionRunJob, err error) {
|
||||
func checkRunConcurrency(ctx context.Context, run *actions_model.ActionRun) (jobs, updatedJobs []*actions_model.ActionRunJob, err error) {
|
||||
checkedConcurrencyGroup := make(container.Set[string])
|
||||
|
||||
collect := func(concurrencyGroup string) error {
|
||||
concurrentRunID, err := findBlockedRunIDByConcurrency(ctx, run.RepoID, concurrencyGroup)
|
||||
concurrentRun, err := findBlockedRunByConcurrency(ctx, run.RepoID, concurrencyGroup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find blocked run by concurrency: %w", err)
|
||||
}
|
||||
if concurrentRunID > 0 {
|
||||
js, ujs, cjs, err := checkBlockedConcurrentRun(ctx, run.RepoID, concurrentRunID)
|
||||
if concurrentRun != nil && !concurrentRun.NeedApproval {
|
||||
js, ujs, err := checkJobsOfRun(ctx, concurrentRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobs = append(jobs, js...)
|
||||
updatedJobs = append(updatedJobs, ujs...)
|
||||
cancelledJobs = append(cancelledJobs, cjs...)
|
||||
}
|
||||
checkedConcurrencyGroup.Add(concurrencyGroup)
|
||||
return nil
|
||||
}
|
||||
|
||||
// check run (workflow-level) concurrency
|
||||
runConcurrencyGroup, _, err := run.GetEffectiveConcurrency(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("GetEffectiveConcurrency: %w", err)
|
||||
}
|
||||
if runConcurrencyGroup != "" {
|
||||
if err := collect(runConcurrencyGroup); err != nil {
|
||||
return nil, nil, nil, err
|
||||
if run.ConcurrencyGroup != "" {
|
||||
if err := collect(run.ConcurrencyGroup); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// check job concurrency
|
||||
runJobs, err := actions_model.GetLatestAttemptJobsByRepoAndRunID(ctx, run.RepoID, run.ID)
|
||||
runJobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
|
||||
return nil, nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
|
||||
}
|
||||
for _, job := range runJobs {
|
||||
if !job.Status.IsDone() {
|
||||
@@ -216,48 +188,28 @@ func checkRunConcurrency(ctx context.Context, run *actions_model.ActionRun) (job
|
||||
continue
|
||||
}
|
||||
if err := collect(job.ConcurrencyGroup); err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return jobs, updatedJobs, cancelledJobs, nil
|
||||
return jobs, updatedJobs, nil
|
||||
}
|
||||
|
||||
// checkJobsOfCurrentRunAttempt resolves blocked jobs of the run's latest attempt.
|
||||
func checkJobsOfCurrentRunAttempt(ctx context.Context, run *actions_model.ActionRun) (jobs, updatedJobs, cancelledJobs []*actions_model.ActionRunJob, err error) {
|
||||
jobs, err = actions_model.GetRunJobsByRunAndAttemptID(ctx, run.ID, run.LatestAttemptID)
|
||||
func checkJobsOfRun(ctx context.Context, run *actions_model.ActionRun) (jobs, updatedJobs []*actions_model.ActionRunJob, err error) {
|
||||
jobs, err = db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// The resolver below only considers needs and job-level concurrency, so a run blocked
|
||||
// solely by run-level concurrency would have its jobs unblocked here. checkRunConcurrency
|
||||
// re-evaluates when the holding run finishes.
|
||||
if run.Status.IsBlocked() {
|
||||
attempt, has, err := run.GetLatestAttempt(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("GetLatestAttempt: %w", err)
|
||||
}
|
||||
if has {
|
||||
shouldBlock, err := shouldBlockRunByConcurrency(ctx, attempt)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("shouldBlockRunByConcurrency: %w", err)
|
||||
}
|
||||
if shouldBlock {
|
||||
return jobs, nil, nil, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
vars, err := actions_model.GetVariablesOfRun(ctx, run)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
resolver := newJobStatusResolver(jobs, vars)
|
||||
|
||||
if err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for _, job := range jobs {
|
||||
job.Run = run
|
||||
}
|
||||
|
||||
updates := resolver.Resolve(ctx)
|
||||
updates := newJobStatusResolver(jobs, vars).Resolve(ctx)
|
||||
for _, job := range jobs {
|
||||
if status, ok := updates[job.ID]; ok {
|
||||
job.Status = status
|
||||
@@ -271,18 +223,26 @@ func checkJobsOfCurrentRunAttempt(ctx context.Context, run *actions_model.Action
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return jobs, updatedJobs, resolver.cancelledJobs, nil
|
||||
return jobs, updatedJobs, nil
|
||||
}
|
||||
|
||||
func NotifyWorkflowRunStatusUpdateWithReload(ctx context.Context, job *actions_model.ActionRunJob) {
|
||||
job.Run = nil
|
||||
if err := job.LoadAttributes(ctx); err != nil {
|
||||
log.Error("LoadAttributes: %v", err)
|
||||
return
|
||||
}
|
||||
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
|
||||
}
|
||||
|
||||
type jobStatusResolver struct {
|
||||
statuses map[int64]actions_model.Status
|
||||
needs map[int64][]int64
|
||||
jobMap map[int64]*actions_model.ActionRunJob
|
||||
vars map[string]string
|
||||
cancelledJobs []*actions_model.ActionRunJob
|
||||
statuses map[int64]actions_model.Status
|
||||
needs map[int64][]int64
|
||||
jobMap map[int64]*actions_model.ActionRunJob
|
||||
vars map[string]string
|
||||
}
|
||||
|
||||
func newJobStatusResolver(jobs actions_model.ActionJobList, vars map[string]string) *jobStatusResolver {
|
||||
@@ -381,12 +341,9 @@ func (r *jobStatusResolver) resolve(ctx context.Context) map[int64]actions_model
|
||||
|
||||
newStatus := util.Iif(shouldStartJob, actions_model.StatusWaiting, actions_model.StatusSkipped)
|
||||
if newStatus == actions_model.StatusWaiting {
|
||||
var cancelledJobs []*actions_model.ActionRunJob
|
||||
newStatus, cancelledJobs, err = PrepareToStartJobWithConcurrency(ctx, actionRunJob)
|
||||
newStatus, err = PrepareToStartJobWithConcurrency(ctx, actionRunJob)
|
||||
if err != nil {
|
||||
log.Error("ShouldBlockJobByConcurrency failed, this job will stay blocked: job: %d, err: %v", id, err)
|
||||
} else {
|
||||
r.cancelledJobs = append(r.cancelledJobs, cancelledJobs...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,16 +359,8 @@ func updateConcurrencyEvaluationForJobWithNeeds(ctx context.Context, actionRunJo
|
||||
return nil // for testing purpose only, no repo, no evaluation
|
||||
}
|
||||
|
||||
// Legacy jobs (created before migration v331) have RunAttemptID=0 and no attempt record.
|
||||
var attempt *actions_model.ActionRunAttempt
|
||||
if actionRunJob.RunAttemptID > 0 {
|
||||
var err error
|
||||
attempt, err = actions_model.GetRunAttemptByRepoAndID(ctx, actionRunJob.RepoID, actionRunJob.RunAttemptID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRunAttemptByRepoAndID: %w", err)
|
||||
}
|
||||
}
|
||||
if err := EvaluateJobConcurrencyFillModel(ctx, actionRunJob.Run, attempt, actionRunJob, vars, nil); err != nil {
|
||||
err := EvaluateJobConcurrencyFillModel(ctx, actionRunJob.Run, actionRunJob, vars, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("evaluate job concurrency: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -144,36 +144,23 @@ func Test_checkRunConcurrency_NoDuplicateConcurrencyGroupCheck(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
// Run A: the triggering run of attempt A
|
||||
// Run A: the triggering run with a concurrency group.
|
||||
runA := &actions_model.ActionRun{
|
||||
RepoID: 4,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: "test.yml",
|
||||
Index: 9901,
|
||||
Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, runA))
|
||||
|
||||
// Attempt A: an attempt of run A with concurrency group "test-cg"
|
||||
runAAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4,
|
||||
RunID: runA.ID,
|
||||
Attempt: 1,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: "test.yml",
|
||||
Index: 9901,
|
||||
Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusRunning,
|
||||
ConcurrencyGroup: "test-cg",
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, runAAttempt))
|
||||
_, err := db.Exec(t.Context(), "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runAAttempt.ID, runA.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, db.Insert(ctx, runA))
|
||||
|
||||
// A done job for run A with the same ConcurrencyGroup.
|
||||
// This triggers the job-level concurrency check in checkRunConcurrency.
|
||||
jobADone := &actions_model.ActionRunJob{
|
||||
RunID: runA.ID,
|
||||
RunAttemptID: runAAttempt.ID,
|
||||
AttemptJobID: 1,
|
||||
RepoID: 4,
|
||||
OwnerID: 1,
|
||||
JobID: "job1",
|
||||
@@ -183,113 +170,34 @@ func Test_checkRunConcurrency_NoDuplicateConcurrencyGroupCheck(t *testing.T) {
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, jobADone))
|
||||
|
||||
// Run B: a run blocked by concurrency
|
||||
// Blocked run B competing for the same concurrency group.
|
||||
runB := &actions_model.ActionRun{
|
||||
RepoID: 4,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: "test.yml",
|
||||
Index: 9902,
|
||||
Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusBlocked,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, runB))
|
||||
|
||||
// Attempt B: an blocked attempt of run B
|
||||
runBAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4,
|
||||
RunID: runB.ID,
|
||||
Attempt: 1,
|
||||
OwnerID: 1,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: "test.yml",
|
||||
Index: 9902,
|
||||
Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusBlocked,
|
||||
ConcurrencyGroup: "test-cg",
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, runBAttempt))
|
||||
_, err = db.Exec(t.Context(), "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runBAttempt.ID, runB.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, db.Insert(ctx, runB))
|
||||
|
||||
// A blocked job belonging to run B (no job-level concurrency group).
|
||||
jobBBlocked := &actions_model.ActionRunJob{
|
||||
RunID: runB.ID,
|
||||
RunAttemptID: runBAttempt.ID,
|
||||
AttemptJobID: 1,
|
||||
RepoID: 4,
|
||||
OwnerID: 1,
|
||||
JobID: "job1",
|
||||
Name: "job1",
|
||||
Status: actions_model.StatusBlocked,
|
||||
RunID: runB.ID,
|
||||
RepoID: 4,
|
||||
OwnerID: 1,
|
||||
JobID: "job1",
|
||||
Name: "job1",
|
||||
Status: actions_model.StatusBlocked,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, jobBBlocked))
|
||||
|
||||
runA, _, _ = db.GetByID[actions_model.ActionRun](t.Context(), runA.ID)
|
||||
jobs, _, _, err := checkRunConcurrency(ctx, runA)
|
||||
jobs, _, err := checkRunConcurrency(ctx, runA)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if assert.Len(t, jobs, 1) {
|
||||
assert.Equal(t, jobBBlocked.ID, jobs[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test_checkJobsOfCurrentRunAttempt_RunLevelConcurrencyKeepsJobsBlocked verifies that
|
||||
// the resolver does not transition a job out of Blocked while another run still holds
|
||||
// the workflow-level concurrency group. Regression for #37446.
|
||||
func Test_checkJobsOfCurrentRunAttempt_RunLevelConcurrencyKeepsJobsBlocked(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
const group = "test-run-level-concurrency-keeps-blocked"
|
||||
|
||||
// Holder run: Running attempt in the concurrency group.
|
||||
holderRun := &actions_model.ActionRun{
|
||||
RepoID: 4, OwnerID: 1, TriggerUserID: 1,
|
||||
WorkflowID: "test.yml", Index: 9911, Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, holderRun))
|
||||
holderAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4, RunID: holderRun.ID, Attempt: 1,
|
||||
Status: actions_model.StatusRunning, ConcurrencyGroup: group,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, holderAttempt))
|
||||
_, err := db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", holderAttempt.ID, holderRun.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Blocked run: Blocked attempt in the same group, with one Blocked job that has
|
||||
// no needs and no job-level concurrency. Without the run-level guard in
|
||||
// checkJobsOfCurrentRunAttempt, the resolver would transition this job to Waiting.
|
||||
blockedRun := &actions_model.ActionRun{
|
||||
RepoID: 4, OwnerID: 1, TriggerUserID: 1,
|
||||
WorkflowID: "test.yml", Index: 9912, Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusBlocked,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedRun))
|
||||
blockedAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4, RunID: blockedRun.ID, Attempt: 1,
|
||||
Status: actions_model.StatusBlocked, ConcurrencyGroup: group,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedAttempt))
|
||||
_, err = db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", blockedAttempt.ID, blockedRun.ID)
|
||||
assert.NoError(t, err)
|
||||
blockedRun.LatestAttemptID = blockedAttempt.ID
|
||||
blockedJob := &actions_model.ActionRunJob{
|
||||
RunID: blockedRun.ID, RunAttemptID: blockedAttempt.ID, AttemptJobID: 1,
|
||||
RepoID: 4, OwnerID: 1, JobID: "job1", Name: "job1",
|
||||
Status: actions_model.StatusBlocked,
|
||||
WorkflowPayload: []byte(`
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo
|
||||
`),
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedJob))
|
||||
|
||||
_, updated, _, err := checkJobsOfCurrentRunAttempt(ctx, blockedRun)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, updated)
|
||||
|
||||
refreshed := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: blockedJob.ID})
|
||||
assert.Equal(t, actions_model.StatusBlocked, refreshed.Status)
|
||||
}
|
||||
|
||||
+31
-64
@@ -11,8 +11,9 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/actions/jobparser"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
|
||||
act_model "gitea.com/gitea/runner/act/model"
|
||||
act_model "github.com/nektos/act/pkg/model"
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
@@ -46,7 +47,10 @@ func PrepareRunAndInsert(ctx context.Context, content []byte, run *actions_model
|
||||
|
||||
CreateCommitStatusForRunJobs(ctx, run, allJobs...)
|
||||
|
||||
NotifyWorkflowJobsAndRunsStatusUpdate(ctx, allJobs)
|
||||
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
|
||||
for _, job := range allJobs {
|
||||
notify_service.WorkflowJobStatusUpdate(ctx, run.Repo, run.TriggerUser, job, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -54,8 +58,7 @@ func PrepareRunAndInsert(ctx context.Context, content []byte, run *actions_model
|
||||
// InsertRun inserts a run
|
||||
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||
func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte, vars map[string]string, inputs map[string]any, wfRawConcurrency *act_model.RawConcurrency) error {
|
||||
var cancelledConcurrencyJobs []*actions_model.ActionRunJob
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,14 +67,6 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
run.Title = util.EllipsisDisplayString(run.Title, 255)
|
||||
run.Status = actions_model.StatusWaiting
|
||||
|
||||
if wfRawConcurrency != nil {
|
||||
rawConcurrency, err := yaml.Marshal(wfRawConcurrency)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal raw concurrency: %w", err)
|
||||
}
|
||||
run.RawConcurrency = string(rawConcurrency)
|
||||
}
|
||||
|
||||
// Insert before parsing jobs or evaluating workflow-level concurrency
|
||||
// so that run.ID is populated. Expressions referencing github.run_id —
|
||||
// in run-name, job names, runs-on, or a workflow-level concurrency
|
||||
@@ -81,54 +76,31 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
return err
|
||||
}
|
||||
|
||||
runAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: run.RepoID,
|
||||
RunID: run.ID,
|
||||
Attempt: 1,
|
||||
TriggerUserID: run.TriggerUserID,
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
|
||||
if wfRawConcurrency != nil {
|
||||
if err := EvaluateRunConcurrencyFillModel(ctx, run, runAttempt, wfRawConcurrency, vars, inputs); err != nil {
|
||||
return fmt.Errorf("EvaluateRunConcurrencyFillModel: %w", err)
|
||||
}
|
||||
// check run (workflow-level) concurrency
|
||||
var jobsToCancel []*actions_model.ActionRunJob
|
||||
runAttempt.Status, jobsToCancel, err = PrepareToStartRunWithConcurrency(ctx, runAttempt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cancelledConcurrencyJobs = append(cancelledConcurrencyJobs, jobsToCancel...)
|
||||
}
|
||||
|
||||
if err := db.Insert(ctx, runAttempt); err != nil {
|
||||
return err
|
||||
}
|
||||
run.LatestAttemptID = runAttempt.ID
|
||||
|
||||
giteaCtx := GenerateGiteaContext(ctx, run, runAttempt, nil)
|
||||
giteaCtx := GenerateGiteaContext(run, nil)
|
||||
jobs, err := jobparser.Parse(content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()), jobparser.WithInputs(inputs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse workflow: %w", err)
|
||||
}
|
||||
|
||||
titleChanged := len(jobs) > 0 && jobs[0].RunName != ""
|
||||
if titleChanged {
|
||||
run.Title = util.EllipsisDisplayString(jobs[0].RunName, 255)
|
||||
}
|
||||
|
||||
cols := []string{"latest_attempt_id"}
|
||||
if titleChanged {
|
||||
cols = append(cols, "title")
|
||||
}
|
||||
if err := actions_model.UpdateRun(ctx, run, cols...); err != nil {
|
||||
return err
|
||||
if wfRawConcurrency != nil {
|
||||
if err := EvaluateRunConcurrencyFillModel(ctx, run, wfRawConcurrency, vars, inputs); err != nil {
|
||||
return fmt.Errorf("EvaluateRunConcurrencyFillModel: %w", err)
|
||||
}
|
||||
run.Status, err = PrepareToStartRunWithConcurrency(ctx, run)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
runJobs := make([]*actions_model.ActionRunJob, 0, len(jobs))
|
||||
var hasWaitingJobs bool
|
||||
|
||||
for i, v := range jobs {
|
||||
for _, v := range jobs {
|
||||
id, job := v.Job()
|
||||
needs := job.Needs()
|
||||
if err := v.SetJob(id, job.EraseNeeds()); err != nil {
|
||||
@@ -136,21 +108,18 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
}
|
||||
payload, _ := v.Marshal()
|
||||
|
||||
shouldBlockJob := runAttempt.Status == actions_model.StatusBlocked || len(needs) > 0 || run.NeedApproval
|
||||
shouldBlockJob := len(needs) > 0 || run.NeedApproval || run.Status == actions_model.StatusBlocked
|
||||
|
||||
job.Name = util.EllipsisDisplayString(job.Name, 255)
|
||||
runJob := &actions_model.ActionRunJob{
|
||||
RunID: run.ID,
|
||||
RunAttemptID: runAttempt.ID,
|
||||
RepoID: run.RepoID,
|
||||
OwnerID: run.OwnerID,
|
||||
CommitSHA: run.CommitSHA,
|
||||
IsForkPullRequest: run.IsForkPullRequest,
|
||||
Name: job.Name,
|
||||
Attempt: runAttempt.Attempt,
|
||||
WorkflowPayload: payload,
|
||||
JobID: id,
|
||||
AttemptJobID: int64(i + 1),
|
||||
Needs: needs,
|
||||
RunsOn: job.RunsOn(),
|
||||
Status: util.Iif(shouldBlockJob, actions_model.StatusBlocked, actions_model.StatusWaiting),
|
||||
@@ -170,7 +139,7 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
|
||||
// do not evaluate job concurrency when it requires `needs`, the jobs with `needs` will be evaluated later by job emitter
|
||||
if len(needs) == 0 {
|
||||
err = EvaluateJobConcurrencyFillModel(ctx, run, runAttempt, runJob, vars, inputs)
|
||||
err = EvaluateJobConcurrencyFillModel(ctx, run, runJob, vars, inputs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("evaluate job concurrency: %w", err)
|
||||
}
|
||||
@@ -179,12 +148,10 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
// If a job needs other jobs ("needs" is not empty), its status is set to StatusBlocked at the entry of the loop
|
||||
// No need to check job concurrency for a blocked job (it will be checked by job emitter later)
|
||||
if runJob.Status == actions_model.StatusWaiting {
|
||||
var jobsToCancel []*actions_model.ActionRunJob
|
||||
runJob.Status, jobsToCancel, err = PrepareToStartJobWithConcurrency(ctx, runJob)
|
||||
runJob.Status, err = PrepareToStartJobWithConcurrency(ctx, runJob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare to start job with concurrency: %w", err)
|
||||
}
|
||||
cancelledConcurrencyJobs = append(cancelledConcurrencyJobs, jobsToCancel...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +163,15 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
runJobs = append(runJobs, runJob)
|
||||
}
|
||||
|
||||
runAttempt.Status = actions_model.AggregateJobStatus(runJobs)
|
||||
if err := actions_model.UpdateRunAttempt(ctx, runAttempt, "status"); err != nil {
|
||||
run.Status = actions_model.AggregateJobStatus(runJobs)
|
||||
cols := []string{"status"}
|
||||
if titleChanged {
|
||||
cols = append(cols, "title")
|
||||
}
|
||||
if wfRawConcurrency != nil {
|
||||
cols = append(cols, "raw_concurrency", "concurrency_group", "concurrency_cancel")
|
||||
}
|
||||
if err := actions_model.UpdateRun(ctx, run, cols...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -209,12 +183,5 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
NotifyWorkflowJobsAndRunsStatusUpdate(ctx, cancelledConcurrencyJobs)
|
||||
EmitJobsIfReadyByJobs(cancelledConcurrencyJobs)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,22 +132,14 @@ func CreateScheduleTask(ctx context.Context, spec *actions_model.ActionScheduleS
|
||||
}
|
||||
|
||||
func withScheduleInEventPayload(eventPayload, schedule string) string {
|
||||
if schedule == "" {
|
||||
if schedule == "" || eventPayload == "" {
|
||||
return eventPayload
|
||||
}
|
||||
|
||||
// eventPayload originates from json.Marshal(input.Payload) in handleSchedules,
|
||||
// so a nil payload is stored as the literal "null" and pre-existing rows may be
|
||||
// empty. Both cases start from a fresh map so the schedule field can still be set.
|
||||
var event map[string]any
|
||||
if eventPayload != "" {
|
||||
if err := json.Unmarshal([]byte(eventPayload), &event); err != nil {
|
||||
log.Error("withScheduleInEventPayload: unmarshal: %v", err)
|
||||
return eventPayload
|
||||
}
|
||||
}
|
||||
if event == nil {
|
||||
event = map[string]any{}
|
||||
event := map[string]any{}
|
||||
if err := json.Unmarshal([]byte(eventPayload), &event); err != nil {
|
||||
log.Error("withScheduleInEventPayload: unmarshal: %v", err)
|
||||
return eventPayload
|
||||
}
|
||||
|
||||
event["schedule"] = schedule
|
||||
|
||||
@@ -22,20 +22,9 @@ func TestWithScheduleInEventPayload(t *testing.T) {
|
||||
assert.Equal(t, "refs/heads/main", event["ref"])
|
||||
})
|
||||
|
||||
t.Run("adds schedule to null payload", func(t *testing.T) {
|
||||
updated := withScheduleInEventPayload("null", "37 12 5 1 2")
|
||||
|
||||
event := map[string]any{}
|
||||
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
|
||||
assert.Equal(t, "37 12 5 1 2", event["schedule"])
|
||||
})
|
||||
|
||||
t.Run("adds schedule to empty payload", func(t *testing.T) {
|
||||
t.Run("keeps empty payload", func(t *testing.T) {
|
||||
updated := withScheduleInEventPayload("", "37 12 5 1 2")
|
||||
|
||||
event := map[string]any{}
|
||||
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
|
||||
assert.Equal(t, "37 12 5 1 2", event["schedule"])
|
||||
assert.Empty(t, updated)
|
||||
})
|
||||
|
||||
t.Run("keeps payload when schedule empty", func(t *testing.T) {
|
||||
|
||||
@@ -5,7 +5,6 @@ package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -13,15 +12,11 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/public"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/webtheme"
|
||||
)
|
||||
|
||||
type TemplateContext map[string]any
|
||||
|
||||
var _ context.Context = TemplateContext(nil)
|
||||
|
||||
func NewTemplateContext(ctx context.Context, req *http.Request) TemplateContext {
|
||||
@@ -88,54 +83,3 @@ func (c TemplateContext) AppFullLink(link ...string) template.URL {
|
||||
}
|
||||
return template.URL(s + "/" + strings.TrimPrefix(link[0], "/"))
|
||||
}
|
||||
|
||||
func (c TemplateContext) ScriptImport(path string, typ ...string) template.HTML {
|
||||
if len(typ) > 0 {
|
||||
if typ[0] == "module" {
|
||||
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" type="module" src="` + html.EscapeString(public.AssetURI(path)) + `"></script>`)
|
||||
}
|
||||
panic("unsupported script type: " + typ[0])
|
||||
}
|
||||
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" src="` + html.EscapeString(public.AssetURI(path)) + `"></script>`)
|
||||
}
|
||||
|
||||
func (c TemplateContext) CspScriptNonce() (ret string) {
|
||||
// Generate a random nonce for each request and cache it in the context to make it usable during the whole rendering process.
|
||||
//
|
||||
// Some "<script>" tags are not in the CSP context, so they don't need nonce,
|
||||
// these tags are written as "<script nonce>" to help developers to know that "no script nonce attribute is missing"
|
||||
// (e.g.: when they grep the codebase for "script" tags)
|
||||
|
||||
ret, _ = c["_cspScriptNonce"].(string)
|
||||
if ret == "" {
|
||||
ret = util.FastCryptoRandomHex(32) // 16 bytes / 128 bits entropy
|
||||
c["_cspScriptNonce"] = ret
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c TemplateContext) HeadMetaContentSecurityPolicy() template.HTML {
|
||||
// The CSP problem is more complicated than it looks.
|
||||
// Gitea was designed to support various "customizations", including:
|
||||
// * custom themes (custom CSS and JS)
|
||||
// * custom assets URL (CDN)
|
||||
// * custom plugins and external renders (e.g.: PlantUML render, and the renders might also load some JS/CSS assets)
|
||||
// There is no easy way for end users to make the CSP "source" completely right.
|
||||
//
|
||||
// There can be 2 approaches in the future:
|
||||
// A. Let end users to configure their reverse proxy to add CSP header
|
||||
// * Browsers will merge and use the stricter rules between Gitea and reverse proxy
|
||||
// B. Introduce some config options in "app.ini"
|
||||
// * Maybe this approach should be avoided, don't make the config system too complex, just let users use A
|
||||
return template.HTML(`<meta http-equiv="Content-Security-Policy" content="` +
|
||||
// allow all by default (the same as old releases with no CSP)
|
||||
// maybe some images or markup (external) renders need "data:", need to investigate
|
||||
`default-src * data:;` +
|
||||
|
||||
// enforce nonce for all scripts, disallow inline scripts
|
||||
`script-src * 'nonce-` + c.CspScriptNonce() + `';` +
|
||||
|
||||
// it seems that Vue needs the unsafe-inline, and our custom colors (e.g.: label) also need it
|
||||
`style-src * 'unsafe-inline';` +
|
||||
`">`)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
db "code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
@@ -116,64 +115,12 @@ func TestToActionWorkflowRun_UsesTriggerEvent(t *testing.T) {
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 803})
|
||||
run.Repo = repo
|
||||
|
||||
// Scheduled runs keep Event as the registration event (push) and use TriggerEvent as the real trigger.
|
||||
run.Event = "push"
|
||||
run.TriggerEvent = "schedule"
|
||||
|
||||
apiRun, err := ToActionWorkflowRun(t.Context(), run, nil)
|
||||
apiRun, err := ToActionWorkflowRun(t.Context(), repo, run)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "schedule", apiRun.Event)
|
||||
}
|
||||
|
||||
func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
run := &actions_model.ActionRun{
|
||||
ID: 9001,
|
||||
RepoID: 2,
|
||||
TriggerUserID: 1,
|
||||
WorkflowID: "test.yaml",
|
||||
Index: 12345,
|
||||
Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusFailure,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, run))
|
||||
|
||||
task := &actions_model.ActionTask{
|
||||
ID: 900102,
|
||||
JobID: 9001,
|
||||
RepoID: 2,
|
||||
Status: actions_model.StatusFailure,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, task))
|
||||
|
||||
job := &actions_model.ActionRunJob{
|
||||
ID: 90010203,
|
||||
RunID: 9001,
|
||||
TaskID: 900102,
|
||||
RepoID: 2,
|
||||
Name: "test-job-name",
|
||||
Attempt: 1,
|
||||
JobID: "test-job-id",
|
||||
Status: actions_model.StatusFailure,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, job))
|
||||
|
||||
require.NoError(t, db.Insert(ctx,
|
||||
&actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 0, Name: "step-success", Status: actions_model.StatusSuccess},
|
||||
&actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 1, Name: "step-failure", Status: actions_model.StatusFailure},
|
||||
))
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
|
||||
apiJob, err := ToActionWorkflowJob(ctx, repo, task, job)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, apiJob.Steps, 2)
|
||||
|
||||
assert.Equal(t, "completed", apiJob.Steps[0].Status, "step 0 status")
|
||||
assert.Equal(t, "success", apiJob.Steps[0].Conclusion, "step 0 conclusion (succeeded before the failure)")
|
||||
assert.Equal(t, "completed", apiJob.Steps[1].Status, "step 1 status")
|
||||
assert.Equal(t, "failure", apiJob.Steps[1].Conclusion, "step 1 conclusion")
|
||||
}
|
||||
|
||||
+52
-92
@@ -11,6 +11,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"gitea.com/gitea/runner/act/model"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
// ToEmail convert models.EmailAddress to api.Email
|
||||
@@ -214,7 +214,7 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
|
||||
|
||||
return &api.Tag{
|
||||
Name: t.Name,
|
||||
Message: t.MessageUTF8(),
|
||||
Message: strings.TrimSpace(t.Message),
|
||||
ID: t.ID.String(),
|
||||
Commit: ToCommitMeta(repo, t),
|
||||
ZipballURL: zipballURL,
|
||||
@@ -222,18 +222,14 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
|
||||
}
|
||||
}
|
||||
|
||||
// ToActionTask convert an actions_model.ActionTask to an api.ActionTask
|
||||
// ToActionTask convert a actions_model.ActionTask to an api.ActionTask
|
||||
func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) {
|
||||
// don't need Steps here, only need to load job and its run
|
||||
if err := t.LoadJob(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.Job.LoadRun(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.Job.Run.LoadRepo(ctx); err != nil {
|
||||
if err := t.LoadAttributes(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink()
|
||||
|
||||
return &api.ActionTask{
|
||||
ID: t.ID,
|
||||
Name: t.Job.Name,
|
||||
@@ -244,101 +240,67 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
|
||||
DisplayTitle: t.Job.Run.Title,
|
||||
Status: t.Status.String(),
|
||||
WorkflowID: t.Job.Run.WorkflowID,
|
||||
URL: httplib.MakeAbsoluteURL(ctx, t.Job.Run.Link()),
|
||||
URL: url,
|
||||
CreatedAt: t.Created.AsLocalTime(),
|
||||
UpdatedAt: t.Updated.AsLocalTime(),
|
||||
RunStartedAt: t.Started.AsLocalTime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, attempt *actions_model.ActionRunAttempt) (_ *api.ActionWorkflowRun, err error) {
|
||||
if err := run.LoadRepo(ctx); err != nil {
|
||||
func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflowRun, error) {
|
||||
err := run.LoadAttributes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := run.LoadTriggerUser(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if attempt == nil {
|
||||
attempt, _, err = run.GetLatestAttempt(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
runAttempt := int64(0)
|
||||
status, conclusion := ToActionsStatus(run.Status)
|
||||
startedAt := run.Started.AsLocalTime()
|
||||
completedAt := run.Stopped.AsLocalTime()
|
||||
actor := run.TriggerUser // The username of the user that triggered the initial workflow run.
|
||||
triggerUser := run.TriggerUser // The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from actor.
|
||||
|
||||
// previousAttemptURL is the value of ActionWorkflowRun.PreviousAttemptURL, which is declared as *string without `omitempty` on purpose:
|
||||
// a nil value must still appear in the JSON body as `"previous_attempt_url": null`, matching GitHub's Actions API.
|
||||
var previousAttemptURL *string
|
||||
|
||||
if attempt != nil {
|
||||
attempt.Run = run
|
||||
if err := attempt.LoadAttributes(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runAttempt = attempt.Attempt
|
||||
status, conclusion = ToActionsStatus(attempt.Status)
|
||||
startedAt = attempt.Started.AsLocalTime()
|
||||
completedAt = attempt.Stopped.AsLocalTime()
|
||||
triggerUser = attempt.TriggerUser
|
||||
if attempt.Attempt > 1 {
|
||||
previousAttemptURL = new(fmt.Sprintf("%s/actions/runs/%d/attempts/%d", run.Repo.APIURL(ctx), run.ID, attempt.Attempt-1))
|
||||
}
|
||||
}
|
||||
|
||||
return &api.ActionWorkflowRun{
|
||||
ID: run.ID,
|
||||
URL: fmt.Sprintf("%s/actions/runs/%d", run.Repo.APIURL(ctx), run.ID),
|
||||
PreviousAttemptURL: previousAttemptURL,
|
||||
HTMLURL: run.HTMLURL(ctx),
|
||||
RunNumber: run.Index,
|
||||
RunAttempt: runAttempt,
|
||||
StartedAt: startedAt,
|
||||
CompletedAt: completedAt,
|
||||
Event: run.TriggerEvent,
|
||||
DisplayTitle: run.Title,
|
||||
HeadBranch: git.RefName(run.Ref).BranchName(),
|
||||
HeadSha: run.CommitSHA,
|
||||
Status: status,
|
||||
Conclusion: conclusion,
|
||||
Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref),
|
||||
Repository: ToRepo(ctx, run.Repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||
TriggerActor: ToUser(ctx, triggerUser, nil),
|
||||
Actor: ToUser(ctx, actor, nil),
|
||||
ID: run.ID,
|
||||
URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID),
|
||||
HTMLURL: run.HTMLURL(),
|
||||
RunNumber: run.Index,
|
||||
StartedAt: run.Started.AsLocalTime(),
|
||||
CompletedAt: run.Stopped.AsLocalTime(),
|
||||
Event: run.TriggerEvent,
|
||||
DisplayTitle: run.Title,
|
||||
HeadBranch: git.RefName(run.Ref).BranchName(),
|
||||
HeadSha: run.CommitSHA,
|
||||
Status: status,
|
||||
Conclusion: conclusion,
|
||||
Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref),
|
||||
Repository: ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||
TriggerActor: ToUser(ctx, run.TriggerUser, nil),
|
||||
// We do not have a way to get a different User for the actor than the trigger user
|
||||
Actor: ToUser(ctx, run.TriggerUser, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ToWorkflowRunAction(status actions_model.Status) (action string) {
|
||||
func ToWorkflowRunAction(status actions_model.Status) string {
|
||||
var action string
|
||||
switch status {
|
||||
case actions_model.StatusWaiting, actions_model.StatusBlocked:
|
||||
action = "requested"
|
||||
case actions_model.StatusRunning:
|
||||
action = "in_progress"
|
||||
default:
|
||||
if status.IsDone() {
|
||||
action = "completed"
|
||||
} else {
|
||||
setting.PanicInDevOrTesting("unknown action status: %v", status)
|
||||
}
|
||||
}
|
||||
if status.IsDone() {
|
||||
action = "completed"
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func ToActionsStatus(status actions_model.Status) (action, conclusion string) {
|
||||
func ToActionsStatus(status actions_model.Status) (string, string) {
|
||||
var action string
|
||||
var conclusion string
|
||||
switch status {
|
||||
// This is a naming conflict of the webhook between Gitea and GitHub Actions
|
||||
case actions_model.StatusWaiting:
|
||||
action = "queued" // "waiting" is a naming conflict of the webhook between Gitea and GitHub Actions
|
||||
action = "queued"
|
||||
case actions_model.StatusBlocked:
|
||||
action = "waiting" // naming conflict (as above)
|
||||
action = "waiting"
|
||||
case actions_model.StatusRunning:
|
||||
action = "in_progress"
|
||||
default:
|
||||
}
|
||||
if status.IsDone() {
|
||||
action = "completed"
|
||||
switch status {
|
||||
case actions_model.StatusSuccess:
|
||||
@@ -349,8 +311,6 @@ func ToActionsStatus(status actions_model.Status) (action, conclusion string) {
|
||||
conclusion = "failure"
|
||||
case actions_model.StatusSkipped:
|
||||
conclusion = "skipped"
|
||||
default:
|
||||
setting.PanicInDevOrTesting("unknown action status: %v", status)
|
||||
}
|
||||
}
|
||||
return action, conclusion
|
||||
@@ -369,9 +329,9 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
var runnerName string
|
||||
var steps []*api.ActionWorkflowStep
|
||||
|
||||
if effectiveTaskID := job.EffectiveTaskID(); effectiveTaskID != 0 {
|
||||
if job.TaskID != 0 {
|
||||
if task == nil {
|
||||
task, _, err = db.GetByID[actions_model.ActionTask](ctx, effectiveTaskID)
|
||||
task, _, err = db.GetByID[actions_model.ActionTask](ctx, job.TaskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -390,7 +350,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
runnerName = runner.Name
|
||||
}
|
||||
for i, step := range task.Steps {
|
||||
stepStatus, stepConclusion := ToActionsStatus(step.Status)
|
||||
stepStatus, stepConclusion := ToActionsStatus(job.Status)
|
||||
steps = append(steps, &api.ActionWorkflowStep{
|
||||
Name: step.Name,
|
||||
Number: int64(i),
|
||||
@@ -406,11 +366,11 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
return &api.ActionWorkflowJob{
|
||||
ID: job.ID,
|
||||
// missing api endpoint for this location
|
||||
URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(ctx), job.ID),
|
||||
HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(ctx), job.ID),
|
||||
URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID),
|
||||
HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), job.ID),
|
||||
RunID: job.RunID,
|
||||
// Missing api endpoint for this location, artifacts are available under a nested url
|
||||
RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(ctx), job.RunID),
|
||||
RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
|
||||
Name: job.Name,
|
||||
Labels: job.RunsOn,
|
||||
RunAttempt: job.Attempt,
|
||||
@@ -710,7 +670,7 @@ func ToOrganization(ctx context.Context, org *organization.Organization) *api.Or
|
||||
Description: org.Description,
|
||||
Website: org.Website,
|
||||
Location: org.Location,
|
||||
Visibility: api.UserVisibility(org.Visibility.String()),
|
||||
Visibility: org.Visibility.String(),
|
||||
RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
|
||||
}
|
||||
}
|
||||
@@ -739,7 +699,7 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]
|
||||
Description: t.Description,
|
||||
IncludesAllRepositories: t.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: t.CanCreateOrgRepo,
|
||||
Permission: api.AccessLevelName(t.AccessMode.ToString()),
|
||||
Permission: t.AccessMode.ToString(),
|
||||
Units: t.GetUnitNames(),
|
||||
UnitsMap: t.GetUnitsMap(),
|
||||
}
|
||||
@@ -768,7 +728,7 @@ func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag
|
||||
Tag: t.Name,
|
||||
SHA: t.ID.String(),
|
||||
Object: ToAnnotatedTagObject(repo, c),
|
||||
Message: t.MessageUTF8(),
|
||||
Message: t.Message,
|
||||
URL: repo.APIURL() + "/git/tags/" + t.ID.String(),
|
||||
Tagger: ToCommitUser(t.Tagger),
|
||||
Verification: ToVerification(ctx, c),
|
||||
@@ -837,7 +797,7 @@ func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
|
||||
|
||||
// ToLFSLock convert a LFSLock to api.LFSLock
|
||||
func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
|
||||
_, u, err := user_model.GetPossibleUserByID(ctx, l.OwnerID)
|
||||
u, err := user_model.GetPossibleUserByID(ctx, l.OwnerID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
+13
-15
@@ -55,36 +55,34 @@
|
||||
{{end}}
|
||||
|
||||
{{if .NumMembers}}
|
||||
<h4 class="ui top attached header flex-left-right">
|
||||
<strong>{{ctx.Locale.Tr "org.members"}}</strong>
|
||||
<h4 class="ui top attached header tw-flex">
|
||||
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.members"}}</strong>
|
||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/members"><span>{{.NumMembers}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||
</h4>
|
||||
{{/* gap 8px below is specifically chosen to make sure a full line of avatars can exactly fit the segment width */}}
|
||||
<div class="ui attached segment flex-text-block tw-flex-wrap tw-gap-[8px]">
|
||||
{{range $memberUser := .OrgOverviewMembers}}
|
||||
{{if or $.IsOrganizationMember (call $.IsPublicMember $memberUser.ID)}}
|
||||
{{template "shared/user/avatarlink" dict "user" $memberUser "size" 32 "tooltip" true}}
|
||||
<div class="ui attached segment members">
|
||||
{{$isMember := .IsOrganizationMember}}
|
||||
{{range .Members}}
|
||||
{{if or $isMember (call $.IsPublicMember .ID)}}
|
||||
{{template "shared/user/avatarlink" dict "user" . "size" 32 "tooltip" true}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .IsOrganizationMember}}
|
||||
<div class="ui top attached header flex-left-right">
|
||||
<strong>{{ctx.Locale.Tr "org.teams"}}</strong>
|
||||
<div class="ui top attached header tw-flex">
|
||||
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.teams"}}</strong>
|
||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||
</div>
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-relaxed-list">
|
||||
{{range .OrgOverviewTeams}}
|
||||
<div>
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||
<div class="ui attached table segment teams">
|
||||
{{range .Teams}}
|
||||
<div class="item">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong class="team-name">{{.Name}}</strong></a>
|
||||
<p class="tw-text-text-light">
|
||||
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{ctx.Locale.Tr "org.lower_members"}}</a> ·
|
||||
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{ctx.Locale.Tr "org.lower_repositories"}}</a>
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if .IsOrganizationOwner}}
|
||||
<div class="ui bottom attached segment">
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<div class="ui six wide column">
|
||||
<h4 class="ui top attached header flex-left-right">
|
||||
<h4 class="ui top attached header">
|
||||
<strong>{{.Team.Name}}</strong>
|
||||
<div class="flex-text-block">
|
||||
<div class="ui right">
|
||||
{{if .Team.IsMember ctx $.SignedUser.ID}}
|
||||
<button class="ui red mini compact button show-modal" data-modal="#org-member-leave-team"
|
||||
data-modal-form.action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/leave?uid={{$.SignedUser.ID}}"
|
||||
data-modal-to-leave-team-name="{{$.Team.Name}}"
|
||||
>{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
<form>
|
||||
<button class="ui red mini compact button delete-button" data-modal-id="leave-team-sidebar"
|
||||
data-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
|
||||
data-name="{{.Team.Name}}">{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
</form>
|
||||
{{else if .IsOrganizationOwner}}
|
||||
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/join">
|
||||
<input type="hidden" name="page" value="team">
|
||||
@@ -15,21 +16,21 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<div class="ui attached segment">
|
||||
<div class="ui attached table segment detail">
|
||||
<div class="item">
|
||||
{{if .Team.Description}}
|
||||
{{.Team.Description}}
|
||||
{{else}}
|
||||
<span class="tw-text-text-light tw-italic">{{ctx.Locale.Tr "org.teams.no_desc"}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="ui attached segment">
|
||||
{{/* TODO: old indent is kept to make diff changes minimal, can be reformatted in the future */}}
|
||||
{{if eq .Team.LowerName "owners"}}
|
||||
{{if eq .Team.LowerName "owners"}}
|
||||
<div class="item">
|
||||
<p>{{ctx.Locale.Tr "org.teams.owners_permission_desc"}}</p>
|
||||
<p>{{ctx.Locale.Tr "org.teams.owners_permission_suggestion"}}</p>
|
||||
{{else}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item">
|
||||
<h3>{{ctx.Locale.Tr "org.team_access_desc"}}</h3>
|
||||
<ul>
|
||||
{{if .Team.IncludesAllRepositories}}
|
||||
@@ -75,21 +76,21 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .IsOrganizationOwner}}
|
||||
<div class="ui bottom attached segment">
|
||||
<a class="ui small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{ctx.Locale.Tr "org.teams.settings"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui mini modal" id="org-member-leave-team">
|
||||
<div class="ui g-modal-confirm delete modal" id="leave-team-sidebar">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<form class="content ui form form-fetch-action" method="post">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "to-leave-team-name")}}</p>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
|
||||
@@ -8,59 +8,49 @@
|
||||
<div class="tw-flex-1">{{ctx.Locale.Tr "org.teams.manage_team_member_prompt"}}</div>
|
||||
<a class="ui primary button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{ctx.Locale.Tr "org.create_new_team"}}</a>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
|
||||
<form class="ui form ignore-dirty tw-my-4" method="get" action="{{$.Link}}">
|
||||
<div class="ui fluid action input">
|
||||
<input type="search" name="q" value="{{$.Keyword}}" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" maxlength="255" spellcheck="false">
|
||||
<button class="ui button" type="submit">{{svg "octicon-search"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="ui two column stackable grid">
|
||||
{{range $team := $.OrgListTeams}}
|
||||
<div class="column team-item-box">
|
||||
<div class="ui top attached header muted-links flex-left-right team-item-header">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||
<div class="flex-text-block tw-flex-wrap">
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{.NumMembers}} {{ctx.Locale.Tr "org.lower_members"}}</a>
|
||||
·
|
||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories">{{.NumRepos}} {{ctx.Locale.Tr "org.lower_repositories"}}</a>
|
||||
{{range .Teams}}
|
||||
<div class="column">
|
||||
<div class="ui top attached header">
|
||||
<a class="tw-text-text" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||
<div class="ui right">
|
||||
<a class="ui primary mini compact button" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{ctx.Locale.Tr "view"}}</a>
|
||||
{{if .IsMember ctx $.SignedUser.ID}}
|
||||
<button class="ui red mini compact button show-modal" data-modal="#org-member-leave-team"
|
||||
data-modal-form.action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave?uid={{$.SignedUser.ID}}"
|
||||
data-modal-to-leave-team-name="{{.Name}}"
|
||||
>{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
<form>
|
||||
<button class="ui red mini compact button delete-button" data-modal-id="leave-team"
|
||||
data-url="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
|
||||
data-name="{{.Name}}">{{ctx.Locale.Tr "org.teams.leave"}}</button>
|
||||
</form>
|
||||
{{else if $.IsOrganizationOwner}}
|
||||
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
|
||||
<button type="submit" class="ui primary mini compact button" name="uid" value="{{$.SignedUser.ID}}">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if $team.Description}}
|
||||
<div class="ui attached header team-item-description">
|
||||
{{if $team.Description}}{{$team.Description}}{{end}}
|
||||
<div class="ui attached segment members">
|
||||
{{range .Members}}
|
||||
{{template "shared/user/avatarlink" dict "user" . "size" 32 "tooltip" true}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-text-block tw-flex-wrap">
|
||||
{{range .Members}}
|
||||
{{template "shared/user/avatarlink" dict "user" . "size" 32 "tooltip" true}}
|
||||
{{else}}
|
||||
<a class="flex-text-inline tw-h-[32px]" href="{{$.OrgLink}}/teams/{{$team.LowerName | PathEscape}}">{{ctx.Locale.Tr "org.teams.add_team_member"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui bottom attached header">
|
||||
<p class="team-meta"><a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}">{{.NumMembers}} {{ctx.Locale.Tr "org.lower_members"}}</a> · <a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories">{{.NumRepos}} {{ctx.Locale.Tr "org.lower_repositories"}}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "base/paginate" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui mini modal" id="org-member-leave-team">
|
||||
<div class="ui g-modal-confirm delete modal" id="leave-team">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "org.teams.leave"}}
|
||||
</div>
|
||||
<form class="content ui form form-fetch-action" method="post">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "to-leave-team-name")}}</p>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "org.teams.leave.detail" (HTMLFormat `<span class="%s"></span>` "name")}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container-main">
|
||||
<div class="ui top attached header flex-left-right">
|
||||
<div class="ui top attached header flex-text-block tw-flex-wrap tw-justify-between">
|
||||
<strong>{{ctx.Locale.TrN .Page.Paginater.Total "actions.runs.workflow_run_count_1" "actions.runs.workflow_run_count_n" .Page.Paginater.Total}}</strong>
|
||||
<div class="ui secondary filter menu flex-text-block tw-m-0">
|
||||
<!-- Actor -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="flex-divided-list items-with-main run-list">
|
||||
<div class="flex-list run-list">
|
||||
{{if not .Runs}}
|
||||
<div class="empty-placeholder">
|
||||
{{svg "octicon-no-entry" 48}}
|
||||
@@ -6,19 +6,19 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{range $run := .Runs}}
|
||||
<div class="item tw-items-center">
|
||||
<div class="item-leading">
|
||||
<div class="flex-item tw-items-center">
|
||||
<div class="flex-item-leading">
|
||||
{{template "repo/actions/status" (dict "status" $run.Status.String)}}
|
||||
</div>
|
||||
<div class="item-main">
|
||||
<span class="item-title" title="{{$run.Title}}">
|
||||
<div class="flex-item-main">
|
||||
<span class="flex-item-title" title="{{$run.Title}}">
|
||||
{{if $run.Title}}
|
||||
{{ctx.RenderUtils.RenderCommitMessageLinkSubject $run.Title $run.Link $.Repository}}
|
||||
{{else}}
|
||||
<a href="{{$run.Link}}">{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}</a>
|
||||
{{end}}
|
||||
</span>
|
||||
<div class="item-body">
|
||||
<div class="flex-item-body">
|
||||
<span><b>{{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}</b>:</span>
|
||||
|
||||
{{- if $run.ScheduleID -}}
|
||||
@@ -38,7 +38,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-trailing">
|
||||
<div class="flex-item-trailing">
|
||||
{{if $run.IsRefDeleted}}
|
||||
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{$run.RefTooltip}}">{{$run.PrettyRef}}</span>
|
||||
{{else}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{/* "z-index" is used to maintain continuous attached styling and keep the colored border-bottom visible (pre-existing fomantic issue with negative margins) */}}
|
||||
<div class="ui blue info attached message flex-left-right tw-z-1">
|
||||
<div class="ui blue info attached message flex-text-block tw-flex-wrap tw-justify-between tw-z-1">
|
||||
<span>{{ctx.Locale.Tr "actions.workflow.has_workflow_dispatch"}}</span>
|
||||
<div class="flex-text-block tw-bg-box-body tw-rounded">{{/*make the button have correct hovered color */}}
|
||||
<button class="ui mini button show-modal" data-modal="#runWorkflowDispatchModal">{{ctx.Locale.Tr "actions.workflow.run"}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}</button>
|
||||
@@ -7,16 +7,13 @@
|
||||
</div>
|
||||
<div id="runWorkflowDispatchModal" class="ui tiny modal">
|
||||
<div class="content">
|
||||
<form id="runWorkflowDispatchForm" class="ui form ignore-dirty" action="{{$.Link}}/run?workflow={{$.CurWorkflow}}&actor={{$.CurActor}}&status={{.Status}}" method="post">
|
||||
<form id="runWorkflowDispatchForm" class="ui form" action="{{$.Link}}/run?workflow={{$.CurWorkflow}}&actor={{$.CurActor}}&status={{.Status}}" method="post">
|
||||
<div class="ui inline field required tw-flex tw-items-center">
|
||||
<span class="ui inline required field">
|
||||
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label>
|
||||
</span>
|
||||
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-text-items">
|
||||
<input type="hidden" name="ref" value="refs/heads/{{index .Branches 0}}"
|
||||
data-fetch-trigger="change" data-fetch-sync="$body #runWorkflowDispatchModalInputs"
|
||||
data-fetch-url="{{$.Link}}/workflow-dispatch-inputs?workflow={{$.CurWorkflow}}"
|
||||
>
|
||||
<input type="hidden" name="ref" hx-sync="this:replace" hx-target="#runWorkflowDispatchModalInputs" hx-swap="innerHTML" hx-get="{{$.Link}}/workflow-dispatch-inputs?workflow={{$.CurWorkflow}}" hx-trigger="change" value="refs/heads/{{index .Branches 0}}">
|
||||
{{svg "octicon-git-branch" 14}}
|
||||
<div class="default text">{{index .Branches 0}}</div>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
@@ -51,8 +48,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
{{template "repo/actions/workflow_dispatch_inputs" .}}
|
||||
|
||||
<div id="runWorkflowDispatchModalInputs">
|
||||
{{template "repo/actions/workflow_dispatch_inputs" .}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
{{ctx.Locale.Tr "action.compare_commits_general"}}
|
||||
{{end}}
|
||||
</h2>
|
||||
{{template "base/alert" .}}
|
||||
{{$BaseCompareName := $.Repository.FullName -}}
|
||||
{{$HeadCompareName := $.HeadRepo.FullName -}}
|
||||
{{$OwnForkCompareName := "" -}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {env} from 'node:process';
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {login, apiCreateRepo, apiCreateFile, assertFlushWithParent, assertNoJsError, randomString} from './utils.ts';
|
||||
import {login, apiCreateRepo, apiCreateFile, apiDeleteRepo, assertFlushWithParent, assertNoJsError, randomString} from './utils.ts';
|
||||
|
||||
test('external file', async ({page, request}) => {
|
||||
const repoName = `e2e-external-render-${randomString(8)}`;
|
||||
@@ -9,15 +9,19 @@ test('external file', async ({page, request}) => {
|
||||
apiCreateRepo(request, {name: repoName}),
|
||||
login(page),
|
||||
]);
|
||||
await apiCreateFile(request, owner, repoName, 'test.external', '<p>rendered content</p>');
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.external`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
await expect(iframe).toHaveAttribute('data-src', new RegExp(`/${owner}/${repoName}/render/branch/main/test\\.external`));
|
||||
const frame = page.frameLocator('iframe.external-render-iframe');
|
||||
await expect(frame.locator('p')).toContainText('rendered content');
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
await assertNoJsError(page);
|
||||
try {
|
||||
await apiCreateFile(request, owner, repoName, 'test.external', '<p>rendered content</p>');
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.external`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
await expect(iframe).toHaveAttribute('data-src', new RegExp(`/${owner}/${repoName}/render/branch/main/test\\.external`));
|
||||
const frame = page.frameLocator('iframe.external-render-iframe');
|
||||
await expect(frame.locator('p')).toContainText('rendered content');
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
await assertNoJsError(page);
|
||||
} finally {
|
||||
await apiDeleteRepo(request, owner, repoName);
|
||||
}
|
||||
});
|
||||
|
||||
test('openapi file', async ({page, request}) => {
|
||||
@@ -27,27 +31,31 @@ test('openapi file', async ({page, request}) => {
|
||||
apiCreateRepo(request, {name: repoName}),
|
||||
login(page),
|
||||
]);
|
||||
const title = 'Test <API> & "quoted"';
|
||||
const spec = JSON.stringify({
|
||||
openapi: '3.0.0',
|
||||
info: {title, version: '1.0'},
|
||||
paths: {'/pets': {get: {responses: {'200': {description: 'OK', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}}}}},
|
||||
components: {schemas: {Pet: {type: 'object', properties: {children: {type: 'array', items: {$ref: '#/components/schemas/Pet'}}}}}},
|
||||
});
|
||||
await apiCreateFile(request, owner, repoName, 'openapi.json', spec);
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/openapi.json`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
const viewer = page.frameLocator('iframe.external-render-iframe').locator('#frontend-render-viewer');
|
||||
await expect(viewer.locator('.swagger-ui')).toBeVisible();
|
||||
await expect(viewer.locator('.info .title')).toContainText(title);
|
||||
// expanding the operation triggers swagger-ui's $ref resolver, which fetches window.location
|
||||
// (about:srcdoc since the iframe is loaded via srcdoc); failure surfaces as "Could not resolve reference"
|
||||
await viewer.locator('.opblock-tag').first().click();
|
||||
await viewer.locator('.opblock').first().click();
|
||||
await expect(viewer.getByText('Could not resolve reference')).toHaveCount(0);
|
||||
// poll: postMessage resize may not have settled yet when the visibility checks pass
|
||||
await expect.poll(async () => (await iframe.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
await assertNoJsError(page);
|
||||
try {
|
||||
const title = 'Test <API> & "quoted"';
|
||||
const spec = JSON.stringify({
|
||||
openapi: '3.0.0',
|
||||
info: {title, version: '1.0'},
|
||||
paths: {'/pets': {get: {responses: {'200': {description: 'OK', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}}}}},
|
||||
components: {schemas: {Pet: {type: 'object', properties: {children: {type: 'array', items: {$ref: '#/components/schemas/Pet'}}}}}},
|
||||
});
|
||||
await apiCreateFile(request, owner, repoName, 'openapi.json', spec);
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/openapi.json`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
const viewer = page.frameLocator('iframe.external-render-iframe').locator('#frontend-render-viewer');
|
||||
await expect(viewer.locator('.swagger-ui')).toBeVisible();
|
||||
await expect(viewer.locator('.info .title')).toContainText(title);
|
||||
// expanding the operation triggers swagger-ui's $ref resolver, which fetches window.location
|
||||
// (about:srcdoc since the iframe is loaded via srcdoc); failure surfaces as "Could not resolve reference"
|
||||
await viewer.locator('.opblock-tag').first().click();
|
||||
await viewer.locator('.opblock').first().click();
|
||||
await expect(viewer.getByText('Could not resolve reference')).toHaveCount(0);
|
||||
// poll: postMessage resize may not have settled yet when the visibility checks pass
|
||||
await expect.poll(async () => (await iframe.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
await assertNoJsError(page);
|
||||
} finally {
|
||||
await apiDeleteRepo(request, owner, repoName);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
import {env} from 'node:process';
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {apiCreateRepo, apiCreateFile, assertFlushWithParent, assertNoJsError, login, randomString} from './utils.ts';
|
||||
import {apiCreateBranch, apiCreateRepo, apiCreateFile, apiDeleteRepo, assertFlushWithParent, assertNoJsError, login, randomString} from './utils.ts';
|
||||
|
||||
test('3d model file', async ({page, request}) => {
|
||||
const repoName = `e2e-3d-render-${randomString(8)}`;
|
||||
const owner = env.GITEA_TEST_E2E_USER;
|
||||
await apiCreateRepo(request, {name: repoName});
|
||||
const stl = 'solid test\nfacet normal 0 0 1\nouter loop\nvertex 0 0 0\nvertex 1 0 0\nvertex 0 1 0\nendloop\nendfacet\nendsolid test\n';
|
||||
await apiCreateFile(request, owner, repoName, 'test.stl', stl);
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.stl?display=rendered`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
const frame = page.frameLocator('iframe.external-render-iframe');
|
||||
const viewer = frame.locator('#frontend-render-viewer');
|
||||
await expect(viewer.locator('canvas')).toBeVisible();
|
||||
expect((await viewer.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
// bgcolor passed via gitea-iframe-bgcolor; 3D viewer reads it from body bgcolor — must match parent
|
||||
const [parentBg, iframeBg] = await Promise.all([
|
||||
page.evaluate(() => getComputedStyle(document.body).backgroundColor),
|
||||
frame.locator('body').evaluate((el) => getComputedStyle(el).backgroundColor),
|
||||
]);
|
||||
expect(iframeBg).toBe(parentBg);
|
||||
await assertNoJsError(page);
|
||||
try {
|
||||
const stl = 'solid test\nfacet normal 0 0 1\nouter loop\nvertex 0 0 0\nvertex 1 0 0\nvertex 0 1 0\nendloop\nendfacet\nendsolid test\n';
|
||||
await apiCreateFile(request, owner, repoName, 'test.stl', stl);
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.stl?display=rendered`);
|
||||
const iframe = page.locator('iframe.external-render-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
const frame = page.frameLocator('iframe.external-render-iframe');
|
||||
const viewer = frame.locator('#frontend-render-viewer');
|
||||
await expect(viewer.locator('canvas')).toBeVisible();
|
||||
expect((await viewer.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(iframe, page.locator('.file-view'));
|
||||
// bgcolor passed via gitea-iframe-bgcolor; 3D viewer reads it from body bgcolor — must match parent
|
||||
const [parentBg, iframeBg] = await Promise.all([
|
||||
page.evaluate(() => getComputedStyle(document.body).backgroundColor),
|
||||
frame.locator('body').evaluate((el) => getComputedStyle(el).backgroundColor),
|
||||
]);
|
||||
expect(iframeBg).toBe(parentBg);
|
||||
await assertNoJsError(page);
|
||||
} finally {
|
||||
await apiDeleteRepo(request, owner, repoName);
|
||||
}
|
||||
});
|
||||
|
||||
test('pdf file', async ({page, request}) => {
|
||||
@@ -30,12 +34,16 @@ test('pdf file', async ({page, request}) => {
|
||||
const repoName = `e2e-pdf-render-${randomString(8)}`;
|
||||
const owner = env.GITEA_TEST_E2E_USER;
|
||||
await apiCreateRepo(request, {name: repoName});
|
||||
await apiCreateFile(request, owner, repoName, 'test.pdf', '%PDF-1.0\n%%EOF\n');
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.pdf`);
|
||||
const container = page.locator('.file-view-render-container');
|
||||
await expect(container).toHaveAttribute('data-render-name', 'pdf-viewer');
|
||||
expect((await container.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(container, page.locator('.file-view'));
|
||||
try {
|
||||
await apiCreateFile(request, owner, repoName, 'test.pdf', '%PDF-1.0\n%%EOF\n');
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/main/test.pdf`);
|
||||
const container = page.locator('.file-view-render-container');
|
||||
await expect(container).toHaveAttribute('data-render-name', 'pdf-viewer');
|
||||
expect((await container.boundingBox())!.height).toBeGreaterThan(300);
|
||||
await assertFlushWithParent(container, page.locator('.file-view'));
|
||||
} finally {
|
||||
await apiDeleteRepo(request, owner, repoName);
|
||||
}
|
||||
});
|
||||
|
||||
test('asciicast file', async ({page, request}) => {
|
||||
@@ -46,12 +54,16 @@ test('asciicast file', async ({page, request}) => {
|
||||
const branch = '日本語-branch';
|
||||
const branchEnc = encodeURIComponent(branch);
|
||||
await Promise.all([apiCreateRepo(request, {name: repoName, autoInit: false}), login(page)]);
|
||||
const cast = '{"version": 2, "width": 80, "height": 24}\n[0.0, "o", "hi"]\n';
|
||||
// on an empty repo, apiCreateFile with newBranch creates that branch as the initial commit
|
||||
await apiCreateFile(request, owner, repoName, 'readme.cast', cast, {newBranch: branch});
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/${branchEnc}`);
|
||||
const container = page.locator('.asciinema-player-container');
|
||||
await expect(container).toHaveAttribute('data-asciinema-player-src', `/${owner}/${repoName}/raw/branch/${branchEnc}/readme.cast`);
|
||||
await expect(container.locator('.ap-wrapper')).toBeVisible();
|
||||
expect((await container.boundingBox())!.height).toBeGreaterThan(300);
|
||||
try {
|
||||
const cast = '{"version": 2, "width": 80, "height": 24}\n[0.0, "o", "hi"]\n';
|
||||
await apiCreateFile(request, owner, repoName, 'readme.cast', cast);
|
||||
await apiCreateBranch(request, owner, repoName, branch);
|
||||
await page.goto(`/${owner}/${repoName}/src/branch/${branchEnc}`);
|
||||
const container = page.locator('.asciinema-player-container');
|
||||
await expect(container).toHaveAttribute('data-asciinema-player-src', `/${owner}/${repoName}/raw/branch/${branchEnc}/readme.cast`);
|
||||
await expect(container.locator('.ap-wrapper')).toBeVisible();
|
||||
expect((await container.boundingBox())!.height).toBeGreaterThan(300);
|
||||
} finally {
|
||||
await apiDeleteRepo(request, owner, repoName);
|
||||
}
|
||||
});
|
||||
|
||||
+14
-94
@@ -47,16 +47,23 @@ export async function apiCreateRepo(requestContext: APIRequestContext, {name, au
|
||||
}), 'apiCreateRepo');
|
||||
}
|
||||
|
||||
export async function apiCreateIssue(requestContext: APIRequestContext, owner: string, repo: string, {title, headers}: {title: string; headers?: Record<string, string>}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {title},
|
||||
}), 'apiCreateIssue');
|
||||
}
|
||||
|
||||
export async function apiStartStopwatch(requestContext: APIRequestContext, owner: string, repo: string, issueIndex: number, {headers}: {headers?: Record<string, string>} = {}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues/${issueIndex}/stopwatch/start`, {
|
||||
headers: headers || apiHeaders(),
|
||||
}), 'apiStartStopwatch');
|
||||
}
|
||||
|
||||
export async function apiCreateFile(requestContext: APIRequestContext, owner: string, repo: string, filepath: string, content: string, {branch, newBranch, message}: {branch?: string; newBranch?: string; message?: string} = {}) {
|
||||
export async function apiCreateFile(requestContext: APIRequestContext, owner: string, repo: string, filepath: string, content: string) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/contents/${filepath}`, {
|
||||
headers: apiHeaders(),
|
||||
data: {content: Buffer.from(content, 'utf8').toString('base64'), branch, new_branch: newBranch, message},
|
||||
data: {content: globalThis.btoa(content)},
|
||||
}), 'apiCreateFile');
|
||||
}
|
||||
|
||||
@@ -67,35 +74,6 @@ export async function apiCreateBranch(requestContext: APIRequestContext, owner:
|
||||
}), 'apiCreateBranch');
|
||||
}
|
||||
|
||||
/** Create a PR via API. Returns the PR index for subsequent operations. */
|
||||
export async function apiCreatePR(requestContext: APIRequestContext, owner: string, repo: string, head: string, base: string, title: string, {headers}: {headers?: Record<string, string>} = {}): Promise<number> {
|
||||
let prIndex = 0;
|
||||
await apiRetry(async () => {
|
||||
const response = await requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/pulls`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {head, base, title},
|
||||
});
|
||||
if (response.ok()) prIndex = (await response.json()).number;
|
||||
return response;
|
||||
}, 'apiCreatePR');
|
||||
return prIndex;
|
||||
}
|
||||
|
||||
/** Create a review on a PR. `event: "COMMENT"` submits immediately without a pending review. */
|
||||
export async function apiCreateReview(requestContext: APIRequestContext, owner: string, repo: string, index: number, {event = 'COMMENT', body, comments = [], headers}: {event?: string; body?: string; comments?: Array<{path: string; body: string; new_position?: number; old_position?: number}>; headers?: Record<string, string>} = {}) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/pulls/${index}/reviews`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {event, body, comments},
|
||||
}), 'apiCreateReview');
|
||||
}
|
||||
|
||||
export async function createProjectColumn(requestContext: APIRequestContext, owner: string, repo: string, projectID: string, title: string) {
|
||||
await apiRetry(() => requestContext.post(`${baseUrl()}/${owner}/${repo}/projects/${projectID}/columns/new`, {
|
||||
headers: apiHeaders(),
|
||||
form: {title},
|
||||
}), 'createProjectColumn');
|
||||
}
|
||||
|
||||
export async function apiDeleteRepo(requestContext: APIRequestContext, owner: string, name: string) {
|
||||
await apiRetry(() => requestContext.delete(`${baseUrl()}/api/v1/repos/${owner}/${name}`, {
|
||||
headers: apiHeaders(),
|
||||
@@ -128,74 +106,16 @@ export async function apiDeleteUser(requestContext: APIRequestContext, username:
|
||||
}), 'apiDeleteUser');
|
||||
}
|
||||
|
||||
export async function createProject(
|
||||
page: Page,
|
||||
{owner, repo, title}: {owner: string; repo: string; title: string},
|
||||
): Promise<{id: number}> {
|
||||
// Navigate to new project page
|
||||
await page.goto(`/${owner}/${repo}/projects/new`);
|
||||
|
||||
// Fill in project details
|
||||
await page.getByLabel('Title').fill(title);
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', {name: 'Create Project'}).click();
|
||||
|
||||
// Wait for redirect to projects list
|
||||
await page.waitForURL(new RegExp(`/${owner}/${repo}/projects$`));
|
||||
|
||||
// Extract the project ID from the project link in the list
|
||||
const projectLink = page.locator('.milestone-list .milestone-card').filter({hasText: title}).locator('a').first();
|
||||
const href = await projectLink.getAttribute('href');
|
||||
const match = /\/projects\/(\d+)/.exec(href || '');
|
||||
const id = match ? parseInt(match[1]) : 0;
|
||||
|
||||
return {id};
|
||||
}
|
||||
|
||||
export async function apiCreateIssue(
|
||||
requestContext: APIRequestContext,
|
||||
{owner, repo, title, body, projects, headers}: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
title: string;
|
||||
body?: string;
|
||||
projects?: number[];
|
||||
headers?: Record<string, string>;
|
||||
},
|
||||
): Promise<{index: number}> {
|
||||
let result: {index: number} = {index: 0};
|
||||
await apiRetry(async () => {
|
||||
const response = await requestContext.post(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues`, {
|
||||
headers: headers || apiHeaders(),
|
||||
data: {title, body: body || '', projects: projects || []},
|
||||
});
|
||||
if (response.ok()) {
|
||||
const json = await response.json();
|
||||
// API returns "number" field for the issue index
|
||||
result = {index: json.number};
|
||||
}
|
||||
return response;
|
||||
}, 'apiCreateIssue');
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function clickDropdownItem(page: Page, trigger: Locator, itemText: string) {
|
||||
await trigger.click();
|
||||
await page.getByText(itemText).click();
|
||||
}
|
||||
|
||||
export async function loginUser(page: Page, username: string) {
|
||||
return login(page, username, testUserPassword);
|
||||
}
|
||||
|
||||
export async function login(page: Page, username = env.GITEA_TEST_E2E_USER, password = env.GITEA_TEST_E2E_PASSWORD) {
|
||||
const response = await page.request.post('/user/login', {
|
||||
form: {user_name: username, password},
|
||||
maxRedirects: 0,
|
||||
});
|
||||
const status = response.status();
|
||||
if (status !== 302 && status !== 303) throw new Error(`login as ${username} failed: HTTP ${status}`);
|
||||
await page.goto('/user/login');
|
||||
await page.getByLabel('Username or Email Address').fill(username);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', {name: 'Sign In'}).click();
|
||||
await expect(page.getByRole('link', {name: 'Sign In'})).toBeHidden();
|
||||
}
|
||||
|
||||
export async function assertNoJsError(page: Page) {
|
||||
|
||||
+148
-202
@@ -23,7 +23,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/oauth2_provider"
|
||||
@@ -36,58 +35,17 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testOAuth2PrepareTestCode(t *testing.T) {
|
||||
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.OAuth2AuthorizationCode{}))
|
||||
err := db.Insert(t.Context(), &auth_model.OAuth2AuthorizationCode{
|
||||
GrantID: 1,
|
||||
Code: "authcode",
|
||||
CodeChallenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", // Code Verifier: N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt
|
||||
CodeChallengeMethod: "S256",
|
||||
RedirectURI: "https://example.com",
|
||||
ValidUntil: timeutil.TimeStampNow() + 86400,
|
||||
}, &auth_model.OAuth2AuthorizationCode{
|
||||
GrantID: 4,
|
||||
Code: "authcodepublic",
|
||||
CodeChallenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", //# Code Verifier: N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt
|
||||
CodeChallengeMethod: "S256",
|
||||
RedirectURI: "http://127.0.0.1/",
|
||||
ValidUntil: timeutil.TimeStampNow() + 86400,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOAuth2(t *testing.T) {
|
||||
func TestOAuth2Provider(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("Provider", func(t *testing.T) {
|
||||
t.Run("AuthorizeNoClientID", testAuthorizeNoClientID)
|
||||
t.Run("AuthorizeUnregisteredRedirect", testAuthorizeUnregisteredRedirect)
|
||||
t.Run("AuthorizeUnsupportedResponseType", testAuthorizeUnsupportedResponseType)
|
||||
t.Run("AuthorizeUnsupportedCodeChallengeMethod", testAuthorizeUnsupportedCodeChallengeMethod)
|
||||
t.Run("AuthorizeLoginRedirect", testAuthorizeLoginRedirect)
|
||||
t.Run("AuthorizeShow", testAuthorizeShow)
|
||||
t.Run("AuthorizeGrantS256RequiresVerifier", testAuthorizeGrantS256RequiresVerifier)
|
||||
t.Run("AuthorizeRedirectWithExistingGrant", testAuthorizeRedirectWithExistingGrant)
|
||||
t.Run("AuthorizePKCERequiredForPublicClient", testAuthorizePKCERequiredForPublicClient)
|
||||
t.Run("AccessTokenExchange", testAccessTokenExchange)
|
||||
t.Run("AccessTokenExchangeWithPublicClient", testAccessTokenExchangeWithPublicClient)
|
||||
t.Run("AccessTokenExchangeJSON", testAccessTokenExchangeJSON)
|
||||
t.Run("AccessTokenExchangeWithoutPKCE", testAccessTokenExchangeWithoutPKCE)
|
||||
t.Run("AccessTokenExchangeWithInvalidCredentials", testAccessTokenExchangeWithInvalidCredentials)
|
||||
t.Run("AccessTokenExchangeWithBasicAuth", testAccessTokenExchangeWithBasicAuth)
|
||||
t.Run("RefreshTokenInvalidation", testRefreshTokenInvalidation)
|
||||
t.Run("OAuthIntrospection", testOAuthIntrospection)
|
||||
t.Run("OAuthGrantScopesReadUserFailRepos", testOAuthGrantScopesReadUserFailRepos)
|
||||
t.Run("OAuthGrantScopesBasicRespectsWriteUser", testOAuthGrantScopesBasicRespectsWriteUser)
|
||||
t.Run("OAuthGrantScopesReadRepositoryFailOrganization", testOAuthGrantScopesReadRepositoryFailOrganization)
|
||||
t.Run("OAuthGrantScopesClaimPublicOnlyGroups", testOAuthGrantScopesClaimPublicOnlyGroups)
|
||||
t.Run("OAuthGrantScopesClaimAllGroups", testOAuthGrantScopesClaimAllGroups)
|
||||
t.Run("OAuth2WellKnown", testOAuth2WellKnown)
|
||||
})
|
||||
t.Run("Client", func(t *testing.T) {
|
||||
t.Run("OAuthSourceSpecialChars", testOAuthSourceSpecialChars)
|
||||
t.Run("SignInOauthCallbackSyncSSHKeys", testSignInOauthCallbackSyncSSHKeys)
|
||||
})
|
||||
t.Run("AuthorizeNoClientID", testAuthorizeNoClientID)
|
||||
t.Run("AuthorizeUnregisteredRedirect", testAuthorizeUnregisteredRedirect)
|
||||
t.Run("AuthorizeUnsupportedResponseType", testAuthorizeUnsupportedResponseType)
|
||||
t.Run("AuthorizeUnsupportedCodeChallengeMethod", testAuthorizeUnsupportedCodeChallengeMethod)
|
||||
t.Run("AuthorizeLoginRedirect", testAuthorizeLoginRedirect)
|
||||
|
||||
t.Run("OAuth2WellKnown", testOAuth2WellKnown)
|
||||
t.Run("OAuthSourceSpecialChars", testOAuthSourceSpecialChars)
|
||||
// TODO: move more tests as sub-tests here, avoid unnecessary PrepareTestEnv
|
||||
}
|
||||
|
||||
@@ -106,7 +64,7 @@ func testAuthorizeUnregisteredRedirect(t *testing.T) {
|
||||
}
|
||||
|
||||
func testAuthorizeUnsupportedResponseType(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=UNEXPECTED&state=thestate")
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
|
||||
ctx := loginUser(t, "user1")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
||||
u, err := resp.Result().Location()
|
||||
@@ -116,7 +74,7 @@ func testAuthorizeUnsupportedResponseType(t *testing.T) {
|
||||
}
|
||||
|
||||
func testAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
|
||||
ctx := loginUser(t, "user1")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
||||
u, err := resp.Result().Location()
|
||||
@@ -130,8 +88,9 @@ func testAuthorizeLoginRedirect(t *testing.T) {
|
||||
assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
|
||||
}
|
||||
|
||||
func testAuthorizeShow(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=code&state=thestate")
|
||||
func TestAuthorizeShow(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
|
||||
ctx := loginUser(t, "user4")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -139,10 +98,11 @@ func testAuthorizeShow(t *testing.T) {
|
||||
AssertHTMLElement(t, htmlDoc, "#authorize-app", true)
|
||||
}
|
||||
|
||||
func testAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
func TestAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
ctx := loginUser(t, "user4")
|
||||
codeChallenge := "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg"
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com&response_type=code&state=thestate&code_challenge_method=S256&code_challenge="+url.QueryEscape(codeChallenge))
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=S256&code_challenge="+url.QueryEscape(codeChallenge))
|
||||
resp := ctx.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
@@ -153,7 +113,7 @@ func testAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
"state": "thestate",
|
||||
"scope": "",
|
||||
"nonce": "",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"granted": "true",
|
||||
})
|
||||
grantResp := ctx.MakeRequest(t, grantReq, http.StatusSeeOther)
|
||||
@@ -166,7 +126,7 @@ func testAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": code,
|
||||
})
|
||||
accessResp := MakeRequest(t, accessReq, http.StatusBadRequest)
|
||||
@@ -176,8 +136,9 @@ func testAuthorizeGrantS256RequiresVerifier(t *testing.T) {
|
||||
assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testAuthorizeRedirectWithExistingGrant(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https://example.com/&response_type=code&state=thestate")
|
||||
func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate")
|
||||
ctx := loginUser(t, "user1")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
||||
u, err := resp.Result().Location()
|
||||
@@ -185,11 +146,11 @@ func testAuthorizeRedirectWithExistingGrant(t *testing.T) {
|
||||
assert.Equal(t, "thestate", u.Query().Get("state"))
|
||||
assert.Greaterf(t, len(u.Query().Get("code")), 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
|
||||
u.RawQuery = ""
|
||||
assert.Equal(t, "https://example.com/", u.String())
|
||||
assert.Equal(t, "https://example.com/xyzzy", u.String())
|
||||
}
|
||||
|
||||
func testAuthorizePKCERequiredForPublicClient(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAuthorizePKCERequiredForPublicClient(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&state=thestate")
|
||||
ctx := loginUser(t, "user1")
|
||||
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
||||
@@ -199,13 +160,13 @@ func testAuthorizePKCERequiredForPublicClient(t *testing.T) {
|
||||
assert.Equal(t, "PKCE is required for public clients", u.Query().Get("error_description"))
|
||||
}
|
||||
|
||||
func testAccessTokenExchange(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchange(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -223,8 +184,8 @@ func testAccessTokenExchange(t *testing.T) {
|
||||
assert.Greater(t, len(parsed.RefreshToken), 10)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeWithPublicClient(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchangeWithPublicClient(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "ce5a1322-42a7-11ed-b878-0242ac120002",
|
||||
@@ -246,13 +207,13 @@ func testAccessTokenExchangeWithPublicClient(t *testing.T) {
|
||||
assert.Greater(t, len(parsed.RefreshToken), 10)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeJSON(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchangeJSON(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -270,13 +231,13 @@ func testAccessTokenExchangeJSON(t *testing.T) {
|
||||
assert.Greater(t, len(parsed.RefreshToken), 10)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeWithoutPKCE(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
})
|
||||
resp := MakeRequest(t, req, http.StatusBadRequest)
|
||||
@@ -286,14 +247,14 @@ func testAccessTokenExchangeWithoutPKCE(t *testing.T) {
|
||||
assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// invalid client id
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "???",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -308,7 +269,7 @@ func testAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "???",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -338,7 +299,7 @@ func testAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "???",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -353,7 +314,7 @@ func testAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
"grant_type": "???",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -364,11 +325,11 @@ func testAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
||||
assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -389,7 +350,7 @@ func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
// use wrong client_secret
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -403,7 +364,7 @@ func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
// missing header
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -416,7 +377,7 @@ func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
// client_id inconsistent with Authorization header
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"client_id": "inconsistent",
|
||||
})
|
||||
@@ -430,7 +391,7 @@ func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
// client_secret inconsistent with Authorization header
|
||||
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"client_secret": "inconsistent",
|
||||
})
|
||||
@@ -442,13 +403,13 @@ func testAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
||||
assert.Equal(t, "client_secret in request body inconsistent with Authorization header", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testRefreshTokenInvalidation(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestRefreshTokenInvalidation(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -470,7 +431,7 @@ func testRefreshTokenInvalidation(t *testing.T) {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
// omit secret
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"refresh_token": parsed.RefreshToken,
|
||||
})
|
||||
resp = MakeRequest(t, req, http.StatusBadRequest)
|
||||
@@ -483,7 +444,7 @@ func testRefreshTokenInvalidation(t *testing.T) {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"refresh_token": "UNEXPECTED",
|
||||
})
|
||||
resp = MakeRequest(t, req, http.StatusBadRequest)
|
||||
@@ -496,7 +457,7 @@ func testRefreshTokenInvalidation(t *testing.T) {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"refresh_token": parsed.RefreshToken,
|
||||
})
|
||||
|
||||
@@ -523,13 +484,13 @@ func testRefreshTokenInvalidation(t *testing.T) {
|
||||
assert.Equal(t, "token was already used", parsedError.ErrorDescription)
|
||||
}
|
||||
|
||||
func testOAuthIntrospection(t *testing.T) {
|
||||
testOAuth2PrepareTestCode(t)
|
||||
func TestOAuthIntrospection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
||||
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": "authcode",
|
||||
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
||||
})
|
||||
@@ -581,11 +542,63 @@ func testOAuthIntrospection(t *testing.T) {
|
||||
assert.Contains(t, resp.Body.String(), "no valid authorization")
|
||||
}
|
||||
|
||||
func testOAuthGrantScopesReadUserFailRepos(t *testing.T) {
|
||||
func TestOAuth_GrantScopesReadUserFailRepos(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
accessToken := issueOAuthAccessTokenForScope(t, user, "openid read:user")
|
||||
appBody := api.CreateOAuth2ApplicationOptions{
|
||||
Name: "oauth-provider-scopes-test",
|
||||
RedirectURIs: []string{
|
||||
"a",
|
||||
},
|
||||
ConfidentialClient: true,
|
||||
}
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &appBody).
|
||||
AddBasicAuth(user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
var app *api.OAuth2Application
|
||||
DecodeJSON(t, resp, &app)
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
UserID: user.ID,
|
||||
Scope: "openid read:user",
|
||||
}
|
||||
|
||||
err := db.Insert(t.Context(), grant)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, grant.Scope, "openid read:user")
|
||||
|
||||
ctx := loginUser(t, user.Name)
|
||||
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeReq := NewRequest(t, "GET", authorizeURL)
|
||||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": "a",
|
||||
"code": authcode,
|
||||
})
|
||||
accessTokenResp := ctx.MakeRequest(t, accessTokenReq, 200)
|
||||
type response struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
parsed := new(response)
|
||||
|
||||
require.NoError(t, json.Unmarshal(accessTokenResp.Body.Bytes(), parsed))
|
||||
userReq := NewRequest(t, "GET", "/api/v1/user")
|
||||
userReq.SetHeader("Authorization", "Bearer "+accessToken)
|
||||
userReq.SetHeader("Authorization", "Bearer "+parsed.AccessToken)
|
||||
userResp := MakeRequest(t, userReq, http.StatusOK)
|
||||
|
||||
type userResponse struct {
|
||||
@@ -598,7 +611,7 @@ func testOAuthGrantScopesReadUserFailRepos(t *testing.T) {
|
||||
assert.Contains(t, userParsed.Email, "user2@example.com")
|
||||
|
||||
errorReq := NewRequest(t, "GET", "/api/v1/users/user2/repos")
|
||||
errorReq.SetHeader("Authorization", "Bearer "+accessToken)
|
||||
errorReq.SetHeader("Authorization", "Bearer "+parsed.AccessToken)
|
||||
errorResp := MakeRequest(t, errorReq, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
@@ -610,44 +623,14 @@ func testOAuthGrantScopesReadUserFailRepos(t *testing.T) {
|
||||
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:repository]")
|
||||
}
|
||||
|
||||
func testOAuthGrantScopesBasicRespectsWriteUser(t *testing.T) {
|
||||
func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
accessToken := issueOAuthAccessTokenForScope(t, user, "openid read:user")
|
||||
fullName := "oauth2-basic-scope-test"
|
||||
|
||||
updateBody := &api.UserSettingsOptions{
|
||||
FullName: &fullName,
|
||||
}
|
||||
|
||||
bearerReq := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", updateBody)
|
||||
bearerReq.SetHeader("Authorization", "Bearer "+accessToken)
|
||||
bearerResp := MakeRequest(t, bearerReq, http.StatusForbidden)
|
||||
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
bearerError := new(errorResponse)
|
||||
require.NoError(t, json.Unmarshal(bearerResp.Body.Bytes(), bearerError))
|
||||
assert.Contains(t, bearerError.Message, "required=[write:user]")
|
||||
|
||||
basicReq := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", updateBody)
|
||||
basicAuth := base64.StdEncoding.EncodeToString([]byte(accessToken + ":x-oauth-basic"))
|
||||
basicReq.SetHeader("Authorization", "Basic "+basicAuth)
|
||||
basicResp := MakeRequest(t, basicReq, http.StatusForbidden)
|
||||
|
||||
basicError := new(errorResponse)
|
||||
require.NoError(t, json.Unmarshal(basicResp.Body.Bytes(), basicError))
|
||||
assert.Contains(t, basicError.Message, "required=[write:user]")
|
||||
}
|
||||
|
||||
func issueOAuthAccessTokenForScope(t *testing.T, user *user_model.User, scope string) string {
|
||||
t.Helper()
|
||||
|
||||
appBody := api.CreateOAuth2ApplicationOptions{
|
||||
Name: "oauth-provider-scopes-test",
|
||||
RedirectURIs: []string{
|
||||
"https://example.com",
|
||||
"a",
|
||||
},
|
||||
ConfidentialClient: true,
|
||||
}
|
||||
@@ -656,55 +639,8 @@ func issueOAuthAccessTokenForScope(t *testing.T, user *user_model.User, scope st
|
||||
AddBasicAuth(user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
app := DecodeJSON(t, resp, &api.OAuth2Application{})
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
UserID: user.ID,
|
||||
Scope: scope,
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), grant))
|
||||
|
||||
ctx := loginUser(t, user.Name)
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=https://example.com&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeReq := NewRequest(t, "GET", authorizeURL)
|
||||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
authcode := strings.Split(strings.Split(authorizeResp.Body.String(), "?code=")[1], "&")[0]
|
||||
|
||||
accessTokenReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": "https://example.com",
|
||||
"code": authcode,
|
||||
})
|
||||
accessTokenResp := ctx.MakeRequest(t, accessTokenReq, http.StatusOK)
|
||||
type response struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
parsed := new(response)
|
||||
require.NoError(t, json.Unmarshal(accessTokenResp.Body.Bytes(), parsed))
|
||||
return parsed.AccessToken
|
||||
}
|
||||
|
||||
func testOAuthGrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
appBody := api.CreateOAuth2ApplicationOptions{
|
||||
Name: "oauth-provider-scopes-test",
|
||||
RedirectURIs: []string{
|
||||
"https://example.com",
|
||||
},
|
||||
ConfidentialClient: true,
|
||||
}
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &appBody).
|
||||
AddBasicAuth(user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
app := DecodeJSON(t, resp, &api.OAuth2Application{})
|
||||
var app *api.OAuth2Application
|
||||
DecodeJSON(t, resp, &app)
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
@@ -719,7 +655,7 @@ func testOAuthGrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||
|
||||
ctx := loginUser(t, user.Name)
|
||||
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=https://example.com&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeReq := NewRequest(t, "GET", authorizeURL)
|
||||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
@@ -728,7 +664,7 @@ func testOAuthGrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": authcode,
|
||||
})
|
||||
accessTokenResp := ctx.MakeRequest(t, accessTokenReq, http.StatusOK)
|
||||
@@ -826,13 +762,15 @@ func testOAuthGrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:user read:organization]")
|
||||
}
|
||||
|
||||
func testOAuthGrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
func TestOAuth_GrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
|
||||
appBody := api.CreateOAuth2ApplicationOptions{
|
||||
Name: "oauth-provider-scopes-test",
|
||||
RedirectURIs: []string{
|
||||
"https://example.com",
|
||||
"a",
|
||||
},
|
||||
ConfidentialClient: true,
|
||||
}
|
||||
@@ -841,7 +779,8 @@ func testOAuthGrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
AddBasicAuth(user.Name)
|
||||
appResp := MakeRequest(t, appReq, http.StatusCreated)
|
||||
|
||||
app := DecodeJSON(t, appResp, &api.OAuth2Application{})
|
||||
var app *api.OAuth2Application
|
||||
DecodeJSON(t, appResp, &app)
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
@@ -856,7 +795,7 @@ func testOAuthGrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
|
||||
ctx := loginUser(t, user.Name)
|
||||
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=https://example.com&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeReq := NewRequest(t, "GET", authorizeURL)
|
||||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
@@ -866,7 +805,7 @@ func testOAuthGrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": authcode,
|
||||
})
|
||||
accessTokenResp := ctx.MakeRequest(t, accessTokenReq, http.StatusOK)
|
||||
@@ -924,13 +863,15 @@ func testOAuthGrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testOAuthGrantScopesClaimAllGroups(t *testing.T) {
|
||||
func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
|
||||
appBody := api.CreateOAuth2ApplicationOptions{
|
||||
Name: "oauth-provider-scopes-test",
|
||||
RedirectURIs: []string{
|
||||
"https://example.com",
|
||||
"a",
|
||||
},
|
||||
ConfidentialClient: true,
|
||||
}
|
||||
@@ -939,7 +880,8 @@ func testOAuthGrantScopesClaimAllGroups(t *testing.T) {
|
||||
AddBasicAuth(user.Name)
|
||||
appResp := MakeRequest(t, appReq, http.StatusCreated)
|
||||
|
||||
app := DecodeJSON(t, appResp, &api.OAuth2Application{})
|
||||
var app *api.OAuth2Application
|
||||
DecodeJSON(t, appResp, &app)
|
||||
|
||||
grant := &auth_model.OAuth2Grant{
|
||||
ApplicationID: app.ID,
|
||||
@@ -954,7 +896,7 @@ func testOAuthGrantScopesClaimAllGroups(t *testing.T) {
|
||||
|
||||
ctx := loginUser(t, user.Name)
|
||||
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=https://example.com&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeURL := fmt.Sprintf("/login/oauth/authorize?client_id=%s&redirect_uri=a&response_type=code&state=thestate", app.ClientID)
|
||||
authorizeReq := NewRequest(t, "GET", authorizeURL)
|
||||
authorizeResp := ctx.MakeRequest(t, authorizeReq, http.StatusSeeOther)
|
||||
|
||||
@@ -964,7 +906,7 @@ func testOAuthGrantScopesClaimAllGroups(t *testing.T) {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app.ClientID,
|
||||
"client_secret": app.ClientSecret,
|
||||
"redirect_uri": "https://example.com",
|
||||
"redirect_uri": "a",
|
||||
"code": authcode,
|
||||
})
|
||||
accessTokenResp := ctx.MakeRequest(t, accessTokenReq, http.StatusOK)
|
||||
@@ -1024,7 +966,8 @@ func testOAuth2WellKnown(t *testing.T) {
|
||||
t.Run("WellKnown", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", urlOpenidConfiguration)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
respMap := DecodeJSON(t, resp, map[string]any{})
|
||||
var respMap map[string]any
|
||||
DecodeJSON(t, resp, &respMap)
|
||||
assert.Equal(t, "https://try.gitea.io", respMap["issuer"])
|
||||
assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
|
||||
assert.Equal(t, "https://try.gitea.io/login/oauth/access_token", respMap["token_endpoint"])
|
||||
@@ -1038,7 +981,8 @@ func testOAuth2WellKnown(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.OAuth2.JWTClaimIssuer, "https://try.gitea.io/")()
|
||||
req := NewRequest(t, "GET", urlOpenidConfiguration)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
respMap := DecodeJSON(t, resp, map[string]any{})
|
||||
var respMap map[string]any
|
||||
DecodeJSON(t, resp, &respMap)
|
||||
assert.Equal(t, "https://try.gitea.io/", respMap["issuer"]) // has trailing by JWTClaimIssuer
|
||||
assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
|
||||
})
|
||||
@@ -1058,7 +1002,7 @@ func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func createOAuth2MockProvider() *httptest.Server {
|
||||
func createMockServer() *httptest.Server {
|
||||
var mockServer *httptest.Server
|
||||
mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
@@ -1077,8 +1021,10 @@ func createOAuth2MockProvider() *httptest.Server {
|
||||
return mockServer
|
||||
}
|
||||
|
||||
func testSignInOauthCallbackSyncSSHKeys(t *testing.T) {
|
||||
mockServer := createOAuth2MockProvider()
|
||||
func TestSignInOauthCallbackSyncSSHKeys(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
mockServer := createMockServer()
|
||||
defer mockServer.Close()
|
||||
|
||||
ctx := t.Context()
|
||||
@@ -1158,7 +1104,7 @@ func testSignInOauthCallbackSyncSSHKeys(t *testing.T) {
|
||||
// Checks if an OAuth provider with spaces within the name does work,
|
||||
// with the encoding of its names in the URL (PR#37327)
|
||||
func testOAuthSourceSpecialChars(t *testing.T) {
|
||||
mockServer := createOAuth2MockProvider()
|
||||
mockServer := createMockServer()
|
||||
defer mockServer.Close()
|
||||
|
||||
addOAuth2Source(t, "test space", oauth2.Source{
|
||||
|
||||
@@ -192,21 +192,17 @@ func testRenameInvalidUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func testRenameReservedUsername(t *testing.T) {
|
||||
// ".", "..", ".well-known" are also reserved but invalid as form input.
|
||||
reservedNames := []string{
|
||||
reservedUsernames := []string{
|
||||
// ".", "..", ".well-known", // The names are not only reserved but also invalid
|
||||
"api",
|
||||
"openapi3.v1.json",
|
||||
"swagger.v1.json",
|
||||
}
|
||||
patternNotAllowedNames := []string{
|
||||
"name.keys",
|
||||
}
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
locale := translation.NewLocale("en-US")
|
||||
check := func(name, msgKey string) {
|
||||
for _, reservedUsername := range reservedUsernames {
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"name": name,
|
||||
"name": reservedUsername,
|
||||
"email": "user2@example.com",
|
||||
"language": "en-US",
|
||||
})
|
||||
@@ -216,14 +212,12 @@ func testRenameReservedUsername(t *testing.T) {
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
actualMsg := strings.TrimSpace(htmlDoc.doc.Find(".ui.negative.message").Text())
|
||||
assert.Equal(t, locale.TrString(msgKey, name), actualMsg)
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{Name: name})
|
||||
}
|
||||
for _, name := range reservedNames {
|
||||
check(name, "user.form.name_reserved")
|
||||
}
|
||||
for _, name := range patternNotAllowedNames {
|
||||
check(name, "user.form.name_pattern_not_allowed")
|
||||
expectedMsg := locale.TrString("user.form.name_reserved", reservedUsername)
|
||||
if strings.Contains(reservedUsername, ".") {
|
||||
expectedMsg = locale.TrString("user.form.name_pattern_not_allowed", reservedUsername)
|
||||
}
|
||||
assert.Equal(t, expectedMsg, actualMsg)
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +333,8 @@ func testUserListStopWatches(t *testing.T) {
|
||||
session := loginUser(t, owner.Name)
|
||||
req := NewRequest(t, "GET", "/user/stopwatches")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
apiWatches := DecodeJSON(t, resp, []*api.StopWatch{})
|
||||
var apiWatches []*api.StopWatch
|
||||
DecodeJSON(t, resp, &apiWatches)
|
||||
stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID})
|
||||
if assert.Len(t, apiWatches, 1) {
|
||||
|
||||
+1
-15
@@ -38,25 +38,11 @@ const webComponents = new Set([
|
||||
'text-expander',
|
||||
]);
|
||||
|
||||
function failOnWarningsPlugin(): Rolldown.Plugin {
|
||||
let warningCount = 0;
|
||||
return {
|
||||
name: 'fail-on-warnings',
|
||||
onLog(level) {
|
||||
if (level === 'warn') warningCount++;
|
||||
},
|
||||
buildEnd() {
|
||||
if (!warningCount) return;
|
||||
throw new Error(`${warningCount} warnings present`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const commonRolldownOptions: Rolldown.RolldownOptions = {
|
||||
checks: {
|
||||
eval: false, // htmx needs eval
|
||||
pluginTimings: false,
|
||||
},
|
||||
...(env.CI ? {plugins: [failOnWarningsPlugin()]} : {}),
|
||||
};
|
||||
|
||||
function commonViteOpts({build, ...other}: InlineConfig): InlineConfig {
|
||||
|
||||
@@ -124,12 +124,18 @@ textarea:focus,
|
||||
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.toggle.checkbox {
|
||||
margin-top: 2.21428571em;
|
||||
}
|
||||
.ui.form .fields:not(.grouped):not(.inline) .field:not(:only-child) .ui.slider.checkbox {
|
||||
margin-top: 2.61428571em;
|
||||
}
|
||||
.ui.ui.form .field .fields .field:not(:only-child) .ui.checkbox {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
.ui.ui.form .field .fields .field:not(:only-child) .ui.toggle.checkbox {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.ui.ui.form .field .fields .field:not(:only-child) .ui.slider.checkbox {
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
.ui.form .field > .selection.dropdown {
|
||||
min-width: 14em; /* matches the default min width */
|
||||
@@ -198,14 +204,14 @@ textarea:focus,
|
||||
.ui.form .field.error input {
|
||||
background-color: var(--color-error-bg);
|
||||
border-color: var(--color-error-border);
|
||||
color: var(--color-input-text);
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
.ui.form .field.error textarea:focus,
|
||||
.ui.form .field.error select:focus,
|
||||
.ui.form .field.error input:focus {
|
||||
background-color: var(--color-error-bg);
|
||||
border-color: var(--color-error-border);
|
||||
color: var(--color-input-text);
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
.ui.form .field.error select {
|
||||
@@ -283,11 +289,15 @@ textarea:focus,
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-transform: none;
|
||||
}
|
||||
.ui.form .grouped.fields .field {
|
||||
.ui.form .grouped.fields .field,
|
||||
.ui.form .grouped.inline.fields .field {
|
||||
display: block;
|
||||
margin: 0.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui.form .grouped.inline.fields .ui.checkbox {
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
.ui.form .fields {
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {checkAppUrl} from '../common-page.ts';
|
||||
import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts';
|
||||
import {POST} from '../../modules/fetch.ts';
|
||||
import {showFomanticModal} from '../../modules/fomantic/modal.ts';
|
||||
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||
import {pathEscape} from '../../utils/url.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
@@ -78,7 +78,7 @@ function initAdminAuthentication() {
|
||||
}
|
||||
|
||||
function onOAuth2Change(applyDefaultValues: boolean) {
|
||||
hideElem('.open_id_connect_auto_discovery_url, .open_id_connect_external_id_claim, .oauth2_use_custom_url');
|
||||
hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url');
|
||||
for (const input of document.querySelectorAll<HTMLInputElement>('.open_id_connect_auto_discovery_url input[required]')) {
|
||||
input.removeAttribute('required');
|
||||
}
|
||||
@@ -88,7 +88,6 @@ function initAdminAuthentication() {
|
||||
case 'openidConnect':
|
||||
document.querySelector<HTMLInputElement>('.open_id_connect_auto_discovery_url input')!.setAttribute('required', 'required');
|
||||
showElem('.open_id_connect_auto_discovery_url');
|
||||
showElem('.open_id_connect_external_id_claim');
|
||||
break;
|
||||
default: {
|
||||
const elProviderCustomUrlSettings = document.querySelector<HTMLInputElement>(`#${provider}_customURLSettings`);
|
||||
@@ -250,7 +249,7 @@ function initAdminNotice() {
|
||||
const elNoticeDesc = el.closest('tr')!.querySelector('.notice-description')!;
|
||||
const elModalDesc = detailModal.querySelector('.content pre')!;
|
||||
elModalDesc.textContent = elNoticeDesc.textContent;
|
||||
showFomanticModal(detailModal);
|
||||
fomanticQuery(detailModal).modal('show');
|
||||
}));
|
||||
|
||||
// Select actions
|
||||
@@ -286,6 +285,6 @@ function initAdminNotice() {
|
||||
}
|
||||
}
|
||||
await POST(this.getAttribute('data-link')!, {data});
|
||||
window.location.reload();
|
||||
window.location.href = this.getAttribute('data-redirect')!;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type {InplaceRenderPlugin} from '../render/plugin.ts';
|
||||
import {newInplacePluginPdfViewer} from '../render/plugins/inplace-pdf-viewer.ts';
|
||||
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
||||
import {createElementFromHTML} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {html} from '../utils/html.ts';
|
||||
import {basename} from '../utils.ts';
|
||||
|
||||
@@ -31,7 +30,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str
|
||||
rendered = true;
|
||||
}
|
||||
} catch (e) {
|
||||
errorMsg = errorMessage(e);
|
||||
errorMsg = `${e}`;
|
||||
} finally {
|
||||
container.classList.remove('is-loading');
|
||||
}
|
||||
|
||||
@@ -1,42 +1,94 @@
|
||||
import {createApp} from 'vue';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {createElementFromHTML} from '../utils/dom.ts';
|
||||
import {registerGlobalEventFunc} from '../modules/observer.ts';
|
||||
|
||||
export function initRepoPullRequestUpdate(el: HTMLElement) {
|
||||
const elDropdown = el.querySelector(':scope > .ui.dropdown');
|
||||
if (!elDropdown) return;
|
||||
const elButton = el.querySelector<HTMLButtonElement>(':scope > button')!;
|
||||
function initRepoPullRequestUpdate(el: HTMLElement) {
|
||||
const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base');
|
||||
if (!prUpdateButtonContainer) return;
|
||||
|
||||
fomanticQuery(elDropdown).dropdown({
|
||||
const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button')!;
|
||||
const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown')!;
|
||||
prUpdateButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
const redirect = this.getAttribute('data-redirect');
|
||||
this.classList.add('is-loading');
|
||||
let response: Response | undefined;
|
||||
try {
|
||||
response = await POST(this.getAttribute('data-do')!);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.classList.remove('is-loading');
|
||||
}
|
||||
let data: Record<string, any> | undefined;
|
||||
try {
|
||||
data = await response?.json(); // the response is probably not a JSON
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (data?.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
} else if (redirect) {
|
||||
window.location.href = redirect;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
fomanticQuery(prUpdateDropdown).dropdown({
|
||||
onChange(_text: string, _value: string, $choice: any) {
|
||||
const choiceEl = $choice[0];
|
||||
elButton.textContent = choiceEl.textContent;
|
||||
elButton.setAttribute('data-url', choiceEl.getAttribute('data-update-url'));
|
||||
const url = choiceEl.getAttribute('data-do');
|
||||
if (url) {
|
||||
const buttonText = prUpdateButton.querySelector('.button-text');
|
||||
if (buttonText) {
|
||||
buttonText.textContent = choiceEl.textContent;
|
||||
}
|
||||
prUpdateButton.setAttribute('data-do', url);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function onCommitStatusChecksToggle(btn: HTMLElement) {
|
||||
const panel = btn.closest('.commit-status-toggle')!.parentElement!;
|
||||
const list = panel.querySelector<HTMLElement>('.commit-status-list')!;
|
||||
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
|
||||
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');
|
||||
function initRepoPullRequestCommitStatus(el: HTMLElement) {
|
||||
for (const btn of el.querySelectorAll('.commit-status-hide-checks')) {
|
||||
const panel = btn.closest('.commit-status-panel')!;
|
||||
const list = panel.querySelector<HTMLElement>('.commit-status-list')!;
|
||||
btn.addEventListener('click', () => {
|
||||
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
|
||||
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function initRepoPullRequestMergeForm(box: HTMLElement) {
|
||||
const el = box.querySelector('#pull-request-merge-form');
|
||||
if (!el) return;
|
||||
|
||||
const data = JSON.parse(el.getAttribute('data-merge-form-props')!);
|
||||
const {default: PullRequestMergeForm} = await import('../components/PullRequestMergeForm.vue');
|
||||
const view = createApp(PullRequestMergeForm, {mergeFormProps: data});
|
||||
view.mount(el); // TODO: can unmount when reloaded?
|
||||
const view = createApp(PullRequestMergeForm);
|
||||
view.mount(el);
|
||||
}
|
||||
|
||||
function executeScripts(elem: Element) {
|
||||
for (const oldScript of elem.querySelectorAll('script')) {
|
||||
// TODO: that's the only way to load the data for the merge form. In the future
|
||||
// we need to completely decouple the page data and embedded script
|
||||
// eslint-disable-next-line github/no-dynamic-script-tag
|
||||
const newScript = document.createElement('script');
|
||||
for (const attr of oldScript.attributes) {
|
||||
if (attr.name === 'type' && attr.value === 'module') continue;
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
newScript.text = oldScript.text;
|
||||
document.body.append(newScript);
|
||||
}
|
||||
}
|
||||
|
||||
export function initRepoPullMergeBox(el: HTMLElement) {
|
||||
registerGlobalEventFunc('click', 'onCommitStatusChecksToggle', onCommitStatusChecksToggle);
|
||||
initRepoPullRequestCommitStatus(el);
|
||||
initRepoPullRequestUpdate(el);
|
||||
initRepoPullRequestMergeForm(el);
|
||||
|
||||
const reloadingIntervalValue = el.getAttribute('data-pull-merge-box-reloading-interval');
|
||||
@@ -72,6 +124,7 @@ export function initRepoPullMergeBox(el: HTMLElement) {
|
||||
}
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
const newElem = createElementFromHTML(await resp.text());
|
||||
executeScripts(newElem);
|
||||
el.replaceWith(newElem);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {initMarkupRenderAsciicast} from './asciicast.ts';
|
||||
import {initMarkupTasklist} from './tasklist.ts';
|
||||
import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts';
|
||||
import {initExternalRenderIframe} from './render-iframe.ts';
|
||||
import {initMarkupRefIssue} from './refissue.ts';
|
||||
import {toggleElemClass} from '../utils/dom.ts';
|
||||
|
||||
// code that runs for all markup content
|
||||
@@ -25,5 +26,6 @@ export function initMarkupContent(): void {
|
||||
initMarkupCodeMermaid(el);
|
||||
initMarkupCodeMath(el);
|
||||
initMarkupRenderAsciicast(el);
|
||||
initMarkupRefIssue(el);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {generateElemId} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {isDarkTheme} from '../utils.ts';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
|
||||
@@ -12,7 +11,7 @@ function safeRenderIframeLink(link: any): string | null {
|
||||
}
|
||||
return url.href;
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse link: ${link}, error: ${errorMessage(e)}`);
|
||||
console.error(`Failed to parse link: ${link}, error: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {isGiteaError, processWindowErrorEvent, showGlobalErrorMessage} from './errors.ts';
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '<div class="page-content"></div>';
|
||||
});
|
||||
import {isGiteaError, showGlobalErrorMessage} from './errors.ts';
|
||||
|
||||
test('isGiteaError', () => {
|
||||
expect(isGiteaError('', '')).toBe(true);
|
||||
@@ -20,6 +16,7 @@ test('isGiteaError', () => {
|
||||
});
|
||||
|
||||
test('showGlobalErrorMessage', () => {
|
||||
document.body.innerHTML = '<div class="page-content"></div>';
|
||||
showGlobalErrorMessage('test msg 1');
|
||||
showGlobalErrorMessage('test msg 2');
|
||||
showGlobalErrorMessage('test msg 1'); // duplicated
|
||||
@@ -28,21 +25,3 @@ test('showGlobalErrorMessage', () => {
|
||||
expect(document.body.innerHTML).toContain('>test msg 2<');
|
||||
expect(document.querySelectorAll('.js-global-error').length).toEqual(2);
|
||||
});
|
||||
|
||||
test('processWindowErrorEvent renders stack trace in details', () => {
|
||||
const error = new Error('boom');
|
||||
error.stack = `Error: boom\n at fn (${window.location.origin}/assets/js/index.js:1:1)`;
|
||||
processWindowErrorEvent({error, type: 'error'} as ErrorEvent & PromiseRejectionEvent);
|
||||
expect(document.querySelector('.js-global-error summary')!.textContent).toContain('JavaScript error: boom');
|
||||
expect(document.querySelector('.js-global-error pre')!.textContent).toContain('/assets/js/index.js:1:1');
|
||||
});
|
||||
|
||||
test('processWindowErrorEvent falls back to message without stack', () => {
|
||||
processWindowErrorEvent({
|
||||
error: {message: 'script error'}, type: 'error',
|
||||
filename: `${window.location.origin}/assets/js/x.js`, lineno: 5, colno: 10,
|
||||
} as ErrorEvent & PromiseRejectionEvent);
|
||||
const msgText = document.querySelector('.js-global-error .ui.message')!.textContent;
|
||||
expect(msgText).toContain('JavaScript error: script error');
|
||||
expect(msgText).toContain('@ 5:10');
|
||||
});
|
||||
|
||||
@@ -49,6 +49,17 @@ export function isGiteaError(filename: string, stack: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detect whether an error originated from Gitea's own scripts, not from
|
||||
// browser extensions or other external scripts.
|
||||
const extensionRe = /(chrome|moz|safari(-web)?)-extension:\/\//;
|
||||
export function isGiteaError(filename: string, stack: string): boolean {
|
||||
if (extensionRe.test(filename) || extensionRe.test(stack)) return false;
|
||||
const assetBaseUrl = new URL(`${window.config.assetUrlPrefix}/`, window.location.origin).href;
|
||||
if (filename && !filename.startsWith(assetBaseUrl) && !filename.startsWith(window.location.origin)) return false;
|
||||
if (stack && !stack.includes(assetBaseUrl)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) {
|
||||
const err = error ?? reason;
|
||||
// `error` and `reason` are not guaranteed to be errors. If the value is falsy, it is likely a
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import './globals.ts';
|
||||
// Stub APIs not implemented by happy-dom but needed by dependencies
|
||||
// XPathEvaluator is used by htmx at module evaluation time
|
||||
// TODO: Remove after https://github.com/capricorn86/happy-dom/pull/2103 is released
|
||||
if (!globalThis.XPathEvaluator) {
|
||||
globalThis.XPathEvaluator = class {
|
||||
createExpression() { return {evaluate: () => ({iterateNext: () => null})} }
|
||||
} as any;
|
||||
}
|
||||
|
||||
// Dynamic import so polyfills above are applied before htmx evaluates
|
||||
await import('./globals.ts');
|
||||
|
||||
window.config = {
|
||||
appUrl: 'http://localhost:3000/',
|
||||
@@ -12,7 +22,8 @@ window.config = {
|
||||
enableTimeTracking: true,
|
||||
mermaidMaxSourceCharacters: 5000,
|
||||
i18n: {},
|
||||
frontendInited: false,
|
||||
};
|
||||
|
||||
window.testModules = {};
|
||||
|
||||
export {}; // mark as module for top-level await
|
||||
|
||||
Reference in New Issue
Block a user