mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-06 23:58:44 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
494017e478 | ||
|
|
56051d9b3b | ||
|
|
4815c4aeae | ||
|
|
2e3a191097 | ||
|
|
361221c531 | ||
|
|
d86433cce2 | ||
|
|
85897f9b28 | ||
|
|
4703e5270f | ||
|
|
9fc3915e04 | ||
|
|
a4a6c785b4 | ||
|
|
634454c48c | ||
|
|
737c947287 | ||
|
|
1ef74004a2 | ||
|
|
5c73da7c54 | ||
|
|
a32aaf4d43 | ||
|
|
0f834f052b | ||
|
|
a3c660f89a | ||
|
|
d5d5fb1925 | ||
|
|
ae37f31df6 | ||
|
|
1f8cbbab3d | ||
|
|
af0cab23ea | ||
|
|
73066e3f97 | ||
|
|
919b82461a | ||
|
|
69ba37e9fd | ||
|
|
ea9e09abe5 | ||
|
|
2891edbbcb | ||
|
|
8dbe83d205 | ||
|
|
2831ae369e | ||
|
|
e6395e1e81 | ||
|
|
8a39a4812f | ||
|
|
3d7d0c36e7 | ||
|
|
30d989d411 | ||
|
|
b3af359cc6 | ||
|
|
0629c08a6d | ||
|
|
54d828f8ec | ||
|
|
6d4dfcd187 | ||
|
|
f7f4256c82 |
2
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -11,7 +11,7 @@ body:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
server (https://discord.gg/gitea) or forum (https://forum.gitea.com).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,7 +7,7 @@ contact_links:
|
||||
url: https://discord.gg/Gitea
|
||||
about: Please ask questions and discuss configuration or deployment problems here.
|
||||
- name: Discourse Forum
|
||||
url: https://discourse.gitea.io
|
||||
url: https://forum.gitea.com
|
||||
about: Questions and configuration or deployment problems can also be discussed on our forum.
|
||||
- name: Frequently Asked Questions
|
||||
url: https://docs.gitea.com/help/faq
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@@ -7,7 +7,7 @@ body:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
server (https://discord.gg/gitea) or forum (https://forum.gitea.com).
|
||||
3. Please take a moment to check that your feature hasn't already been suggested.
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
2
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
@@ -11,7 +11,7 @@ body:
|
||||
value: |
|
||||
1. Please speak English, this is the language all maintainers can speak and write.
|
||||
2. Please ask questions or configuration/deploy problems on our Discord
|
||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
||||
server (https://discord.gg/gitea) or forum (https://forum.gitea.com).
|
||||
3. Please take a moment to check that your issue doesn't already exist.
|
||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||
5. Please give all relevant information below for bug reports, because
|
||||
|
||||
4
.github/workflows/pull-db-tests.yml
vendored
4
.github/workflows/pull-db-tests.yml
vendored
@@ -194,7 +194,9 @@ jobs:
|
||||
test-mssql:
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
# specifying the version of ubuntu in use as mssql fails on newer kernels
|
||||
# pending resolution from vendor
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -4,6 +4,47 @@ This changelog goes through the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.22.3](https://github.com/go-gitea/gitea/releases/tag/1.22.3) - 2024-10-08
|
||||
|
||||
* SECURITY
|
||||
* Fix bug when a token is given public only (#32204) (#32218)
|
||||
* PERFORMANCE
|
||||
* Increase `cacheContextLifetime` to reduce false reports (#32011) (#32023)
|
||||
* Don't join repository when loading action table data (#32127) (#32143)
|
||||
* BUGFIXES
|
||||
* Fix javascript error when an anonymous user visits migration page (#32144) (#32179)
|
||||
* Don't init signing keys if oauth2 provider is disabled (#32177)
|
||||
* Fix wrong status of `Set up Job` when first step is skipped (#32120) (#32125)
|
||||
* Fix bug when deleting a migrated branch (#32075) (#32123)
|
||||
* Truncate commit message during Discord webhook push events (#31970) (#32121)
|
||||
* Allow to set branch protection in an empty repository (#32095) (#32119)
|
||||
* Fix panic when cloning with wrong ssh format. (#32076) (#32118)
|
||||
* Fix rename branch permission bug (#32066) (#32108)
|
||||
* Fix: database not update release when using `git push --tags --force` (#32040) (#32074)
|
||||
* Add missing comment reply handling (#32050) (#32065)
|
||||
* Do not escape relative path in RPM primary index (#32038) (#32054)
|
||||
* Fix `/repos/{owner}/{repo}/pulls/{index}/files` endpoint not populating `previous_filename` (#32017) (#32028)
|
||||
* Support allowed hosts for migrations to work with proxy (#32025) (#32026)
|
||||
* Fix the logic of finding the latest pull review commit ID (#32139) (#32165)
|
||||
* Fix bug in getting merged pull request by commit (#32079) (#32117)
|
||||
* Fix wrong last modify time (#32102) (#32104)
|
||||
* Fix incorrect `/tokens` api (#32085) (#32092)
|
||||
* Handle invalid target when creating releases using API (#31841) (#32043)
|
||||
* Check if the `due_date` is nil when editing issues (#32035) (#32042)
|
||||
* Fix container parallel upload bugs (#32022)
|
||||
* Fixed race condition when deleting documents by repoId in ElasticSearch (#32185) (#32188)
|
||||
* Refactor CSRF protector (#32057) (#32069)
|
||||
* Fix Bug in Issue/pulls list (#32081) (#32115)
|
||||
* Include collaboration repositories on dashboard source/forks/mirrors list (#31946) (#32122)
|
||||
* Add null check for responseData.invalidTopics (#32212) (#32217)
|
||||
* TESTING
|
||||
* Fix mssql ci with a new mssql version on ci (#32094)
|
||||
* MISC
|
||||
* Upgrade some dependencies include minio-go (#32166)
|
||||
* Add bin to Composer Metadata (#32099) (#32106)
|
||||
* Lazy load avatar images (#32051) (#32063)
|
||||
* Upgrade cache to v0.2.1 (#32003) (#32009)
|
||||
|
||||
## [1.22.2](https://github.com/go-gitea/gitea/releases/tag/1.22.2) - 2024-08-28
|
||||
|
||||
* Security
|
||||
|
||||
22
assets/go-licenses.json
generated
22
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
11
cmd/serv.go
11
cmd/serv.go
@@ -143,6 +143,12 @@ func runServ(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
_ = fail(ctx, "Internal Server Error", "Panic: %v\n%s", err, log.Stack(2))
|
||||
}
|
||||
}()
|
||||
|
||||
keys := strings.Split(c.Args().First(), "-")
|
||||
if len(keys) != 2 || keys[0] != "key" {
|
||||
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
|
||||
@@ -189,10 +195,7 @@ func runServ(c *cli.Context) error {
|
||||
}
|
||||
|
||||
verb := words[0]
|
||||
repoPath := words[1]
|
||||
if repoPath[0] == '/' {
|
||||
repoPath = repoPath[1:]
|
||||
}
|
||||
repoPath := strings.TrimPrefix(words[1], "/")
|
||||
|
||||
var lfsVerb string
|
||||
if verb == lfsAuthenticateVerb {
|
||||
|
||||
36
go.mod
36
go.mod
@@ -9,7 +9,7 @@ require (
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
connectrpc.com/connect v1.15.0
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
@@ -66,8 +66,8 @@ require (
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/klauspost/cpuid/v2 v2.2.7
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.79.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
@@ -75,8 +75,8 @@ require (
|
||||
github.com/meilisearch/meilisearch-go v0.26.2
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/microsoft/go-mssqldb v1.7.0
|
||||
github.com/minio/minio-go/v7 v7.0.69
|
||||
github.com/microsoft/go-mssqldb v1.7.2
|
||||
github.com/minio/minio-go/v7 v7.0.77
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
@@ -104,13 +104,13 @@ require (
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/tools v0.24.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
@@ -123,8 +123,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.25.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
@@ -186,6 +185,7 @@ require (
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // 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/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/inflect v0.21.0 // indirect
|
||||
@@ -198,7 +198,7 @@ require (
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.9 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -232,7 +232,6 @@ require (
|
||||
github.com/mholt/acmez/v2 v2.0.1 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -255,7 +254,7 @@ require (
|
||||
github.com/rhysd/actionlint v1.6.27 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
@@ -291,10 +290,9 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
|
||||
110
go.sum
110
go.sum
@@ -1,7 +1,5 @@
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
|
||||
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
|
||||
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||
@@ -22,8 +20,8 @@ gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
|
||||
@@ -166,7 +164,6 @@ github.com/blevesearch/zapx/v16 v16.1.5/go.mod h1:J4mSF39w1QELc11EWRSBFkPeZuO7r/
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -210,7 +207,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -243,7 +239,6 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1 h1:iPCqofzMO41WVbcS/B5Ym7AwHQg9cyQ7Ie/R2XU5L3A=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1/go.mod h1:VY4oyqUnpULFB3SCRpl24GFDIN1PmfiQIvN/G4ScSNg=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
@@ -312,6 +307,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
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-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
@@ -336,8 +333,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
|
||||
@@ -358,8 +353,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
|
||||
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=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
@@ -378,7 +373,6 @@ github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1Ix
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -387,7 +381,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -418,7 +411,6 @@ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -507,13 +499,13 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
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.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -530,13 +522,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
|
||||
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
|
||||
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
|
||||
github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -556,7 +545,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.26.2 h1:3gTlmiV1dHHumVUhYdJbvh3camiNiyqQ1hNveVsU2OE=
|
||||
@@ -565,16 +553,14 @@ github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
|
||||
github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
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.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw=
|
||||
github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@@ -614,12 +600,10 @@ github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5Bn
|
||||
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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
@@ -633,7 +617,6 @@ github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
@@ -680,8 +663,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -703,16 +686,11 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
|
||||
github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
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=
|
||||
@@ -771,7 +749,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||
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/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
@@ -847,7 +824,6 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
@@ -859,8 +835,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
@@ -871,14 +847,11 @@ 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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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=
|
||||
@@ -893,10 +866,10 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -905,15 +878,13 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -944,8 +915,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
@@ -955,27 +926,23 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
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=
|
||||
@@ -985,15 +952,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
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/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
@@ -1005,7 +969,6 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
@@ -1020,7 +983,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
||||
@@ -450,9 +450,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
sess := db.GetEngine(ctx).Where(cond)
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
@@ -226,6 +226,8 @@ func TestNotifyWatchers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFeedsCorrupted(t *testing.T) {
|
||||
// Now we will not check for corrupted data in the feeds
|
||||
// users should run doctor to fix their data
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||
@@ -239,8 +241,8 @@ func TestGetFeedsCorrupted(t *testing.T) {
|
||||
IncludePrivate: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actions, 0)
|
||||
assert.Equal(t, int64(0), count)
|
||||
assert.Len(t, actions, 1)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
func TestConsistencyUpdateAction(t *testing.T) {
|
||||
|
||||
@@ -179,27 +179,6 @@ func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) {
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
// GetMigratingTaskByID returns the migrating task by repo's id
|
||||
func GetMigratingTaskByID(ctx context.Context, id, doerID int64) (*Task, *migration.MigrateOptions, error) {
|
||||
task := Task{
|
||||
ID: id,
|
||||
DoerID: doerID,
|
||||
Type: structs.TaskTypeMigrateRepo,
|
||||
}
|
||||
has, err := db.GetEngine(ctx).Get(&task)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !has {
|
||||
return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type}
|
||||
}
|
||||
|
||||
var opts migration.MigrateOptions
|
||||
if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &task, &opts, nil
|
||||
}
|
||||
|
||||
// CreateTask creates a task on database
|
||||
func CreateTask(ctx context.Context, task *Task) error {
|
||||
return db.Insert(ctx, task)
|
||||
|
||||
@@ -83,3 +83,22 @@
|
||||
issue_id: 2 # in repo_id 1
|
||||
review_id: 20
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 10
|
||||
type: 22 # review
|
||||
poster_id: 5
|
||||
issue_id: 3 # in repo_id 1
|
||||
content: "reviewed by user5"
|
||||
review_id: 21
|
||||
created_unix: 946684816
|
||||
|
||||
-
|
||||
id: 11
|
||||
type: 27 # review request
|
||||
poster_id: 2
|
||||
issue_id: 3 # in repo_id 1
|
||||
content: "review request for user5"
|
||||
review_id: 22
|
||||
assignee_id: 5
|
||||
created_unix: 946684817
|
||||
|
||||
@@ -179,3 +179,22 @@
|
||||
content: "Review Comment"
|
||||
updated_unix: 946684810
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 21
|
||||
type: 2
|
||||
reviewer_id: 5
|
||||
issue_id: 3
|
||||
content: "reviewed by user5"
|
||||
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||
updated_unix: 946684816
|
||||
created_unix: 946684816
|
||||
|
||||
-
|
||||
id: 22
|
||||
type: 4
|
||||
reviewer_id: 5
|
||||
issue_id: 3
|
||||
content: "review request for user5"
|
||||
updated_unix: 946684817
|
||||
created_unix: 946684817
|
||||
|
||||
@@ -387,7 +387,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
|
||||
|
||||
// Note: This doesn't page as we only expect a very limited number of reviews
|
||||
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
|
||||
Type: ReviewTypeApprove,
|
||||
Types: []ReviewType{ReviewTypeApprove},
|
||||
IssueID: pr.IssueID,
|
||||
OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
|
||||
})
|
||||
|
||||
@@ -364,7 +364,7 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
|
||||
return nil, nil
|
||||
}
|
||||
reviews, err := FindReviews(ctx, FindReviewOptions{
|
||||
Type: ReviewTypePending,
|
||||
Types: []ReviewType{ReviewTypePending},
|
||||
IssueID: issue.ID,
|
||||
ReviewerID: reviewer.ID,
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ func (reviews ReviewList) LoadIssues(ctx context.Context) error {
|
||||
// FindReviewOptions represent possible filters to find reviews
|
||||
type FindReviewOptions struct {
|
||||
db.ListOptions
|
||||
Type ReviewType
|
||||
Types []ReviewType
|
||||
IssueID int64
|
||||
ReviewerID int64
|
||||
OfficialOnly bool
|
||||
@@ -78,8 +78,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
|
||||
if opts.ReviewerID > 0 {
|
||||
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
|
||||
}
|
||||
if opts.Type != ReviewTypeUnknown {
|
||||
cond = cond.And(builder.Eq{"type": opts.Type})
|
||||
if len(opts.Types) > 0 {
|
||||
cond = cond.And(builder.In("type", opts.Types))
|
||||
}
|
||||
if opts.OfficialOnly {
|
||||
cond = cond.And(builder.Eq{"official": true})
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestReviewType_Icon(t *testing.T) {
|
||||
func TestFindReviews(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
IssueID: 2,
|
||||
ReviewerID: 1,
|
||||
})
|
||||
@@ -75,7 +75,7 @@ func TestFindReviews(t *testing.T) {
|
||||
func TestFindLatestReviews(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reviews, err := issues_model.FindLatestReviews(db.DefaultContext, issues_model.FindReviewOptions{
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
IssueID: 11,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -398,6 +398,10 @@ func (u *User) IsIndividual() bool {
|
||||
return u.Type == UserTypeIndividual
|
||||
}
|
||||
|
||||
func (u *User) IsUser() bool {
|
||||
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
|
||||
}
|
||||
|
||||
// IsBot returns whether or not the user is of type bot
|
||||
func (u *User) IsBot() bool {
|
||||
return u.Type == UserTypeBot
|
||||
|
||||
@@ -18,8 +18,32 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||
return fullStepsOfEmptySteps(task)
|
||||
}
|
||||
|
||||
firstStep := task.Steps[0]
|
||||
// firstStep is the first step that has run or running, not include preStep.
|
||||
// For example,
|
||||
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): firstStep is step1.
|
||||
// 2. preStep(Success) -> step1(Skipped) -> step2(Success) -> postStep(Success): firstStep is step2.
|
||||
// 3. preStep(Success) -> step1(Running) -> step2(Waiting) -> postStep(Waiting): firstStep is step1.
|
||||
// 4. preStep(Success) -> step1(Skipped) -> step2(Skipped) -> postStep(Skipped): firstStep is nil.
|
||||
// 5. preStep(Success) -> step1(Cancelled) -> step2(Cancelled) -> postStep(Cancelled): firstStep is nil.
|
||||
var firstStep *actions_model.ActionTaskStep
|
||||
// lastHasRunStep is the last step that has run.
|
||||
// For example,
|
||||
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1.
|
||||
// 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3.
|
||||
// 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2.
|
||||
// So its Stopped is the Started of postStep when there are no more steps to run.
|
||||
var lastHasRunStep *actions_model.ActionTaskStep
|
||||
|
||||
var logIndex int64
|
||||
for _, step := range task.Steps {
|
||||
if firstStep == nil && (step.Status.HasRun() || step.Status.IsRunning()) {
|
||||
firstStep = step
|
||||
}
|
||||
if step.Status.HasRun() {
|
||||
lastHasRunStep = step
|
||||
}
|
||||
logIndex += step.LogLength
|
||||
}
|
||||
|
||||
preStep := &actions_model.ActionTaskStep{
|
||||
Name: preStepName,
|
||||
@@ -28,32 +52,17 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
|
||||
if firstStep.Status.HasRun() || firstStep.Status.IsRunning() {
|
||||
// No step has run or is running, so preStep is equal to the task
|
||||
if firstStep == nil {
|
||||
preStep.Stopped = task.Stopped
|
||||
preStep.Status = task.Status
|
||||
} else {
|
||||
preStep.LogLength = firstStep.LogIndex
|
||||
preStep.Stopped = firstStep.Started
|
||||
preStep.Status = actions_model.StatusSuccess
|
||||
} else if task.Status.IsDone() {
|
||||
preStep.Stopped = task.Stopped
|
||||
preStep.Status = actions_model.StatusFailure
|
||||
if task.Status.IsSkipped() {
|
||||
preStep.Status = actions_model.StatusSkipped
|
||||
}
|
||||
}
|
||||
logIndex += preStep.LogLength
|
||||
|
||||
// lastHasRunStep is the last step that has run.
|
||||
// For example,
|
||||
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1.
|
||||
// 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3.
|
||||
// 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2.
|
||||
// So its Stopped is the Started of postStep when there are no more steps to run.
|
||||
var lastHasRunStep *actions_model.ActionTaskStep
|
||||
for _, step := range task.Steps {
|
||||
if step.Status.HasRun() {
|
||||
lastHasRunStep = step
|
||||
}
|
||||
logIndex += step.LogLength
|
||||
}
|
||||
if lastHasRunStep == nil {
|
||||
lastHasRunStep = preStep
|
||||
}
|
||||
|
||||
@@ -137,6 +137,25 @@ func TestFullSteps(t *testing.T) {
|
||||
{Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first step is skipped",
|
||||
task: &actions_model.ActionTask{
|
||||
Steps: []*actions_model.ActionTaskStep{
|
||||
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||
},
|
||||
Status: actions_model.StatusSuccess,
|
||||
Started: 10000,
|
||||
Stopped: 10100,
|
||||
LogLength: 100,
|
||||
},
|
||||
want: []*actions_model.ActionTaskStep{
|
||||
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
|
||||
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
|
||||
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
|
||||
{Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 10100},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
_ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
|
||||
_ "code.gitea.io/gitea/models" // https://forum.gitea.com/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
12
modules/cache/context.go
vendored
12
modules/cache/context.go
vendored
@@ -63,9 +63,9 @@ func (cc *cacheContext) isDiscard() bool {
|
||||
}
|
||||
|
||||
// cacheContextLifetime is the max lifetime of cacheContext.
|
||||
// Since cacheContext is used to cache data in a request level context, 10s is enough.
|
||||
// If a cacheContext is used more than 10s, it's probably misuse.
|
||||
const cacheContextLifetime = 10 * time.Second
|
||||
// Since cacheContext is used to cache data in a request level context, 5 minutes is enough.
|
||||
// If a cacheContext is used more than 5 minutes, it's probably misuse.
|
||||
const cacheContextLifetime = 5 * time.Minute
|
||||
|
||||
var timeNow = time.Now
|
||||
|
||||
@@ -131,7 +131,7 @@ func GetContextData(ctx context.Context, tp, key any) any {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c)
|
||||
return nil
|
||||
}
|
||||
return c.Get(tp, key)
|
||||
@@ -144,7 +144,7 @@ func SetContextData(ctx context.Context, tp, key, value any) {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c)
|
||||
return
|
||||
}
|
||||
c.Put(tp, key, value)
|
||||
@@ -157,7 +157,7 @@ func RemoveContextData(ctx context.Context, tp, key any) {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c)
|
||||
return
|
||||
}
|
||||
c.Delete(tp, key)
|
||||
|
||||
2
modules/cache/context_test.go
vendored
2
modules/cache/context_test.go
vendored
@@ -45,7 +45,7 @@ func TestWithCacheContext(t *testing.T) {
|
||||
timeNow = now
|
||||
}()
|
||||
timeNow = func() time.Time {
|
||||
return now().Add(10 * time.Second)
|
||||
return now().Add(5 * time.Minute)
|
||||
}
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
|
||||
@@ -13,11 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check
|
||||
func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return NewDialContextWithProxy(usage, allowList, blockList, nil)
|
||||
}
|
||||
|
||||
func NewDialContextWithProxy(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
func NewDialContext(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// How Go HTTP Client works with redirection:
|
||||
// transport.RoundTrip URL=http://domain.com, Host=domain.com
|
||||
// transport.DialContext addrOrHost=domain.com:80
|
||||
|
||||
@@ -75,7 +75,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
|
||||
w.Header().Set("Etag", etag)
|
||||
}
|
||||
if lastModified != nil && !lastModified.IsZero() {
|
||||
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
if len(etag) > 0 {
|
||||
|
||||
@@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
||||
httpcache.SetCacheControlInHeader(header, duration)
|
||||
|
||||
if !opts.LastModified.IsZero() {
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
@@ -197,8 +198,33 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes indexes by ids
|
||||
// Delete entries by repoId
|
||||
func (b *Indexer) Delete(ctx context.Context, repoID int64) error {
|
||||
if err := b.doDelete(ctx, repoID); err != nil {
|
||||
// Maybe there is a conflict during the delete operation, so we should retry after a refresh
|
||||
log.Warn("Deletion of entries of repo %v within index %v was erroneus. Trying to refresh index before trying again", repoID, b.inner.VersionedIndexName(), err)
|
||||
if err := b.refreshIndex(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.doDelete(ctx, repoID); err != nil {
|
||||
log.Error("Could not delete entries of repo %v within index %v", repoID, b.inner.VersionedIndexName())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Indexer) refreshIndex(ctx context.Context) error {
|
||||
if _, err := b.inner.Client.Refresh(b.inner.VersionedIndexName()).Do(ctx); err != nil {
|
||||
log.Error("Error while trying to refresh index %v", b.inner.VersionedIndexName(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete entries by repoId
|
||||
func (b *Indexer) doDelete(ctx context.Context, repoID int64) error {
|
||||
_, err := b.inner.Client.DeleteByQuery(b.inner.VersionedIndexName()).
|
||||
Query(elastic.NewTermsQuery("repo_id", repoID)).
|
||||
Do(ctx)
|
||||
|
||||
@@ -43,6 +43,7 @@ type Metadata struct {
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
License Licenses `json:"license,omitempty"`
|
||||
Authors []Author `json:"authors,omitempty"`
|
||||
Bin []string `json:"bin,omitempty"`
|
||||
Autoload map[string]any `json:"autoload,omitempty"`
|
||||
AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
|
||||
@@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
|
||||
name = "avatar"
|
||||
}
|
||||
|
||||
return template.HTML(`<img class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
}
|
||||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
|
||||
@@ -218,8 +218,6 @@ string.desc = Z - A
|
||||
[error]
|
||||
occurred = An error occurred
|
||||
report_message = If you believe that this is a Gitea bug, please search for issues on <a href="https://github.com/go-gitea/gitea/issues" target="_blank">GitHub</a> or open a new issue if necessary.
|
||||
missing_csrf = Bad Request: no CSRF token present
|
||||
invalid_csrf = Bad Request: invalid CSRF token
|
||||
not_found = The target couldn't be found.
|
||||
network_error = Network error
|
||||
|
||||
|
||||
@@ -63,6 +63,20 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
|
||||
return
|
||||
}
|
||||
|
||||
// check if scope only applies to public resources
|
||||
publicOnly, err := scope.PublicOnly()
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if publicOnly {
|
||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,23 +10,29 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
container_model "code.gitea.io/gitea/models/packages/container"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
var uploadVersionMutex sync.Mutex
|
||||
// TODO: use clustered lock
|
||||
var uploadVersionMutex = sync.NewExclusivePool()
|
||||
|
||||
// saveAsPackageBlob creates a package blob from an upload
|
||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
|
||||
pkgPath := pci.PackageInfo.Owner.LowerName + "/" + pci.PackageInfo.Name
|
||||
uploadVersionMutex.CheckIn(pkgPath)
|
||||
defer uploadVersionMutex.CheckOut(pkgPath)
|
||||
|
||||
pb := packages_service.NewPackageBlob(hsr)
|
||||
|
||||
exists := false
|
||||
@@ -80,6 +86,10 @@ func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader
|
||||
|
||||
// mountBlob mounts the specific blob to a different package
|
||||
func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
|
||||
pkgPath := pi.Owner.LowerName + "/" + pi.Name
|
||||
uploadVersionMutex.CheckIn(pkgPath)
|
||||
defer uploadVersionMutex.CheckOut(pkgPath)
|
||||
|
||||
uploadVersion, err := getOrCreateUploadVersion(ctx, pi)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -93,9 +103,6 @@ func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packag
|
||||
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
||||
var uploadVersion *packages_model.PackageVersion
|
||||
|
||||
// FIXME: Replace usage of mutex with database transaction
|
||||
// https://github.com/go-gitea/gitea/pull/21862
|
||||
uploadVersionMutex.Lock()
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
created := true
|
||||
p := &packages_model.Package{
|
||||
@@ -140,7 +147,6 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
|
||||
|
||||
return nil
|
||||
})
|
||||
uploadVersionMutex.Unlock()
|
||||
|
||||
return uploadVersion, err
|
||||
}
|
||||
@@ -172,10 +178,14 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error {
|
||||
func deleteBlob(ctx context.Context, owner *user_model.User, image, digest string) error {
|
||||
pkgPath := owner.LowerName + "/" + image
|
||||
uploadVersionMutex.CheckIn(pkgPath)
|
||||
defer uploadVersionMutex.CheckOut(pkgPath)
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
OwnerID: owner.ID,
|
||||
Image: image,
|
||||
Digest: digest,
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
@@ -540,7 +541,7 @@ func DeleteBlob(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := deleteBlob(ctx, ctx.Package.Owner.ID, ctx.Params("image"), d); err != nil {
|
||||
if err := deleteBlob(ctx, ctx.Package.Owner, ctx.Params("image"), d); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
@@ -550,6 +551,9 @@ func DeleteBlob(ctx *context.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: use clustered lock
|
||||
var lockManifest = sync.NewExclusivePool()
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-manifests
|
||||
func UploadManifest(ctx *context.Context) {
|
||||
reference := ctx.Params("reference")
|
||||
@@ -581,6 +585,10 @@ func UploadManifest(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
imagePath := ctx.Package.Owner.Name + "/" + ctx.Params("image")
|
||||
lockManifest.CheckIn(imagePath)
|
||||
defer lockManifest.CheckOut(imagePath)
|
||||
|
||||
digest, err := processManifest(ctx, mci, buf)
|
||||
if err != nil {
|
||||
var namedError *namedError
|
||||
@@ -679,6 +687,10 @@ func DeleteManifest(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
imagePath := ctx.Package.Owner.Name + "/" + ctx.Params("image")
|
||||
lockManifest.CheckIn(imagePath)
|
||||
defer lockManifest.CheckOut(imagePath)
|
||||
|
||||
pvs, err := container_model.GetManifestVersions(ctx, opts)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
|
||||
@@ -115,7 +115,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
||||
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
|
||||
|
||||
latest := pds[len(pds)-1]
|
||||
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat)
|
||||
ctx.Resp.Header().Set("Last-Modified", lastModifed)
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(params.Filename))
|
||||
if isChecksumExtension(ext) {
|
||||
|
||||
@@ -235,6 +235,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
|
||||
}
|
||||
}
|
||||
|
||||
func checkTokenPublicOnly() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.PublicOnly {
|
||||
return
|
||||
}
|
||||
|
||||
requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
|
||||
if !ok || len(requiredScopeCategories) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// public Only permission check
|
||||
switch {
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
|
||||
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
|
||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a token is being used for auth, we check that it contains the required scope
|
||||
// if a token is not being used, reqToken will enforce other sign in methods
|
||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
||||
@@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
|
||||
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
|
||||
|
||||
// use the http method to determine the access level
|
||||
requiredScopeLevel := auth_model.Read
|
||||
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
|
||||
@@ -261,6 +314,18 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
|
||||
// get the required scope for the given access level and category
|
||||
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
|
||||
allow, err := scope.HasScope(requiredScopes...)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !allow {
|
||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["requiredScopeCategories"] = requiredScopeCategories
|
||||
|
||||
// check if scope only applies to public resources
|
||||
publicOnly, err := scope.PublicOnly()
|
||||
@@ -269,21 +334,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
return
|
||||
}
|
||||
|
||||
// this context is used by the middleware in the specific route
|
||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
|
||||
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
|
||||
|
||||
allow, err := scope.HasScope(requiredScopes...)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if allow {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
||||
// assign to true so that those searching should only filter public repositories/users/organizations
|
||||
ctx.PublicOnly = publicOnly
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if true == ctx.Data["IsApiToken"] {
|
||||
publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
|
||||
publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
|
||||
|
||||
if pubRepoExists && publicRepo.(bool) &&
|
||||
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
|
||||
return
|
||||
}
|
||||
|
||||
if pubOrgExists && publicOrg.(bool) &&
|
||||
ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
|
||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
return
|
||||
}
|
||||
@@ -879,11 +912,11 @@ func Routes() *web.Route {
|
||||
m.Group("/user/{username}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
m.Group("/user-id/{user-id}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserIDAssignmentAPI())
|
||||
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
|
||||
}
|
||||
|
||||
@@ -939,7 +972,7 @@ func Routes() *web.Route {
|
||||
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
||||
|
||||
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
||||
}, context.UserAssignmentAPI(), individualPermsChecker)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
||||
|
||||
// Users (requires user scope)
|
||||
@@ -957,7 +990,7 @@ func Routes() *web.Route {
|
||||
m.Get("/starred", user.GetStarredRepos)
|
||||
|
||||
m.Get("/subscriptions", user.GetWatchedRepos)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
|
||||
// Users (requires user scope)
|
||||
@@ -1044,7 +1077,7 @@ func Routes() *web.Route {
|
||||
m.Get("", user.IsStarring)
|
||||
m.Put("", user.Star)
|
||||
m.Delete("", user.Unstar)
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
m.Get("/times", repo.ListMyTrackedTimes)
|
||||
m.Get("/stopwatches", repo.GetStopwatches)
|
||||
@@ -1069,18 +1102,20 @@ func Routes() *web.Route {
|
||||
m.Get("", user.CheckUserBlock)
|
||||
m.Put("", user.BlockUser)
|
||||
m.Delete("", user.UnblockUser)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
})
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
|
||||
// Repositories (requires repo scope, org scope)
|
||||
m.Post("/org/{org}/repos",
|
||||
// FIXME: we need org in context
|
||||
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
||||
reqToken(),
|
||||
bind(api.CreateRepoOption{}),
|
||||
repo.CreateOrgRepoDeprecated)
|
||||
|
||||
// requires repo scope
|
||||
// FIXME: Don't expose repository id outside of the system
|
||||
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
|
||||
|
||||
// Repos (requires repo scope)
|
||||
@@ -1274,6 +1309,8 @@ func Routes() *web.Route {
|
||||
m.Group("/{ref}", func() {
|
||||
m.Get("/status", repo.GetCombinedCommitStatusByRef)
|
||||
m.Get("/statuses", repo.GetCommitStatusesByRef)
|
||||
}, context.ReferencesGitRepo())
|
||||
m.Group("/{sha}", func() {
|
||||
m.Get("/pull", repo.GetCommitPullRequest)
|
||||
}, context.ReferencesGitRepo())
|
||||
}, reqRepoReader(unit.TypeCode))
|
||||
@@ -1319,7 +1356,7 @@ func Routes() *web.Route {
|
||||
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
||||
m.Delete("", repo.DeleteAvatar)
|
||||
}, reqAdmin(), reqToken())
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
|
||||
// Notifications (requires notifications scope)
|
||||
@@ -1328,7 +1365,7 @@ func Routes() *web.Route {
|
||||
m.Combo("/notifications", reqToken()).
|
||||
Get(notify.ListRepoNotifications).
|
||||
Put(notify.ReadRepoNotifications)
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
||||
|
||||
// Issue (requires issue scope)
|
||||
@@ -1442,7 +1479,7 @@ func Routes() *web.Route {
|
||||
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
||||
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
||||
})
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
||||
|
||||
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
|
||||
@@ -1453,14 +1490,14 @@ func Routes() *web.Route {
|
||||
m.Get("/files", reqToken(), packages.ListPackageFiles)
|
||||
})
|
||||
m.Get("/", reqToken(), packages.ListPackages)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
|
||||
|
||||
// Organizations
|
||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
||||
m.Group("/users/{username}/orgs", func() {
|
||||
m.Get("", reqToken(), org.ListUserOrgs)
|
||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
||||
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
||||
m.Group("/orgs/{org}", func() {
|
||||
@@ -1518,7 +1555,7 @@ func Routes() *web.Route {
|
||||
m.Delete("", org.UnblockUser)
|
||||
})
|
||||
}, reqToken(), reqOrgOwnership())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||
m.Group("/teams/{teamid}", func() {
|
||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
@@ -1538,7 +1575,7 @@ func Routes() *web.Route {
|
||||
Get(reqToken(), org.GetTeamRepo)
|
||||
})
|
||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
|
||||
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/cron", func() {
|
||||
|
||||
@@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/OrganizationList"
|
||||
|
||||
vMode := []api.VisibleType{api.VisibleTypePublic}
|
||||
if ctx.IsSigned {
|
||||
if ctx.IsSigned && !ctx.PublicOnly {
|
||||
vMode = append(vMode, api.VisibleTypeLimited)
|
||||
if ctx.Doer.IsAdmin {
|
||||
vMode = append(vMode, api.VisibleTypePrivate)
|
||||
|
||||
@@ -325,11 +325,11 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommitPullRequest returns the pull request of the commit
|
||||
// GetCommitPullRequest returns the merged pull request of the commit
|
||||
func GetCommitPullRequest(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
|
||||
// ---
|
||||
// summary: Get the pull request of the commit
|
||||
// summary: Get the merged pull request of the commit
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -354,7 +354,7 @@ func GetCommitPullRequest(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params(":sha"))
|
||||
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params("sha"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
|
||||
|
||||
@@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
Actor: ctx.Doer,
|
||||
}
|
||||
if ctx.IsSigned {
|
||||
opts.Private = true
|
||||
opts.Private = !ctx.PublicOnly
|
||||
opts.AllLimited = true
|
||||
}
|
||||
if ctx.FormString("owner") != "" {
|
||||
@@ -828,10 +828,16 @@ func EditIssue(ctx *context.APIContext) {
|
||||
if (form.Deadline != nil || form.RemoveDeadline != nil) && canWrite {
|
||||
var deadlineUnix timeutil.TimeStamp
|
||||
|
||||
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
|
||||
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, form.Deadline.Location())
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
if form.RemoveDeadline == nil || !*form.RemoveDeadline {
|
||||
if form.Deadline == nil {
|
||||
ctx.Error(http.StatusBadRequest, "", "The due_date cannot be empty")
|
||||
return
|
||||
}
|
||||
if !form.Deadline.IsZero() {
|
||||
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, form.Deadline.Location())
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||
|
||||
@@ -83,7 +83,6 @@ func ListPullReviews(ctx *context.APIContext) {
|
||||
|
||||
opts := issues_model.FindReviewOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Type: issues_model.ReviewTypeUnknown,
|
||||
IssueID: pr.IssueID,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
@@ -251,6 +252,8 @@ func CreateRelease(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err)
|
||||
} else if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "ProtectedTagName", err)
|
||||
} else if git.IsErrNotExist(err) {
|
||||
ctx.Error(http.StatusNotFound, "ErrNotExist", fmt.Errorf("target \"%v\" not found: %w", rel.Target, err))
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateRelease", err)
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) {
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
||||
if ctx.PublicOnly {
|
||||
private = false
|
||||
}
|
||||
|
||||
opts := &repo_model.SearchRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Actor: ctx.Doer,
|
||||
@@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) {
|
||||
TeamID: ctx.FormInt64("team_id"),
|
||||
TopicOnly: ctx.FormBool("topic"),
|
||||
Collaborate: optional.None[bool](),
|
||||
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
||||
Private: private,
|
||||
Template: optional.None[bool](),
|
||||
StarredByID: ctx.FormInt64("starredBy"),
|
||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||
|
||||
@@ -118,6 +118,10 @@ func CreateAccessToken(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
|
||||
return
|
||||
}
|
||||
if scope == "" {
|
||||
ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope")
|
||||
return
|
||||
}
|
||||
t.Scope = scope
|
||||
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
@@ -129,6 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) {
|
||||
Token: t.Token,
|
||||
ID: t.ID,
|
||||
TokenLastEight: t.TokenLastEight,
|
||||
Scopes: t.Scope.StringSlice(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@@ -67,11 +68,16 @@ func Search(ctx *context.APIContext) {
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewActionsUser()}
|
||||
default:
|
||||
var visible []structs.VisibleType
|
||||
if ctx.PublicOnly {
|
||||
visible = []structs.VisibleType{structs.VisibleTypePublic}
|
||||
}
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: uid,
|
||||
Type: user_model.UserTypeIndividual,
|
||||
Visible: visible,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -395,7 +395,8 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string
|
||||
|
||||
ctx.Resp.Header().Set("Content-Type", contentType)
|
||||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
||||
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
http.ServeFile(ctx.Resp, ctx.Req, reqFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -461,6 +461,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ctx.Data["AssigneeID"] = assigneeID
|
||||
ctx.Data["PosterID"] = posterID
|
||||
ctx.Data["Keyword"] = keyword
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
switch {
|
||||
case isShowClosed.Value():
|
||||
ctx.Data["State"] = "closed"
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -284,3 +285,40 @@ func MigrateCancelPost(ctx *context.Context) {
|
||||
}
|
||||
ctx.Redirect(ctx.Repo.Repository.Link())
|
||||
}
|
||||
|
||||
// MigrateStatus returns migrate task's status
|
||||
func MigrateStatus(ctx *context.Context) {
|
||||
task, err := admin_model.GetMigratingTask(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
if admin_model.IsErrTaskDoesNotExist(err) {
|
||||
ctx.JSON(http.StatusNotFound, map[string]any{
|
||||
"err": "task does not exist or you do not have access to this task",
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Error("GetMigratingTask: %v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"err": http.StatusText(http.StatusInternalServerError),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
message := task.Message
|
||||
|
||||
if task.Message != "" && task.Message[0] == '{' {
|
||||
// assume message is actually a translatable string
|
||||
var translatableMessage admin_model.TranslatableMessage
|
||||
if err := json.Unmarshal([]byte(message), &translatableMessage); err != nil {
|
||||
translatableMessage = admin_model.TranslatableMessage{
|
||||
Format: "migrate.migrating_failed.error",
|
||||
Args: []any{task.Message},
|
||||
}
|
||||
}
|
||||
message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"status": task.Status,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
admin_model "code.gitea.io/gitea/models/admin"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// TaskStatus returns task's status
|
||||
func TaskStatus(ctx *context.Context) {
|
||||
task, opts, err := admin_model.GetMigratingTaskByID(ctx, ctx.ParamsInt64("task"), ctx.Doer.ID)
|
||||
if err != nil {
|
||||
if admin_model.IsErrTaskDoesNotExist(err) {
|
||||
ctx.JSON(http.StatusNotFound, map[string]any{
|
||||
"error": "task `" + strconv.FormatInt(ctx.ParamsInt64("task"), 10) + "` does not exist",
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"err": err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
message := task.Message
|
||||
|
||||
if task.Message != "" && task.Message[0] == '{' {
|
||||
// assume message is actually a translatable string
|
||||
var translatableMessage admin_model.TranslatableMessage
|
||||
if err := json.Unmarshal([]byte(message), &translatableMessage); err != nil {
|
||||
translatableMessage = admin_model.TranslatableMessage{
|
||||
Format: "migrate.migrating_failed.error",
|
||||
Args: []any{task.Message},
|
||||
}
|
||||
}
|
||||
message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"status": task.Status,
|
||||
"message": message,
|
||||
"repo-id": task.RepoID,
|
||||
"repo-name": opts.RepoName,
|
||||
"start": task.StartTime,
|
||||
"end": task.EndTime,
|
||||
})
|
||||
}
|
||||
@@ -129,6 +129,8 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
|
||||
// ensure the session uid is deleted
|
||||
_ = ctx.Session.Delete("uid")
|
||||
}
|
||||
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,7 +667,6 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get("/forgot_password", auth.ForgotPasswd)
|
||||
m.Post("/forgot_password", auth.ForgotPasswdPost)
|
||||
m.Post("/logout", auth.SignOut)
|
||||
m.Get("/task/{task}", reqSignIn, user.TaskStatus)
|
||||
m.Get("/stopwatches", reqSignIn, user.GetStopwatches)
|
||||
m.Get("/search", ignExploreSignIn, user.Search)
|
||||
m.Group("/oauth2", func() {
|
||||
@@ -1034,6 +1035,13 @@ func registerRoutes(m *web.Route) {
|
||||
}, ignSignIn, context.UserAssignmentWeb(), context.OrgAssignment())
|
||||
// end "/{username}/-": packages, projects, code
|
||||
|
||||
m.Group("/{username}/{reponame}/-", func() {
|
||||
m.Group("/migrate", func() {
|
||||
m.Get("/status", repo.MigrateStatus)
|
||||
})
|
||||
}, ignSignIn, context.RepoAssignment, reqRepoCodeReader)
|
||||
// end "/{username}/{reponame}/-": migrate
|
||||
|
||||
m.Group("/{username}/{reponame}/settings", func() {
|
||||
m.Group("", func() {
|
||||
m.Combo("").Get(repo_setting.Settings).
|
||||
@@ -1061,9 +1069,7 @@ func registerRoutes(m *web.Route) {
|
||||
m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch).
|
||||
Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost)
|
||||
m.Post("/{id}/delete", repo_setting.DeleteProtectedBranchRulePost)
|
||||
}, repo.MustBeNotEmpty)
|
||||
|
||||
m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.RenameBranchPost)
|
||||
})
|
||||
|
||||
m.Group("/tags", func() {
|
||||
m.Get("", repo_setting.ProtectedTags)
|
||||
@@ -1296,6 +1302,7 @@ func registerRoutes(m *web.Route) {
|
||||
}, web.Bind(forms.NewBranchForm{}))
|
||||
m.Post("/delete", repo.DeleteBranchPost)
|
||||
m.Post("/restore", repo.RestoreBranchPost)
|
||||
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
|
||||
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
||||
|
||||
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
||||
|
||||
@@ -30,10 +30,14 @@ const ProviderHeaderKey = "gitea-oauth2-provider"
|
||||
|
||||
// Init initializes the oauth source
|
||||
func Init(ctx context.Context) error {
|
||||
if err := InitSigningKey(); err != nil {
|
||||
return err
|
||||
// this is for oauth2 provider
|
||||
if setting.OAuth2.Enabled {
|
||||
if err := InitSigningKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// others for oauth2 clients
|
||||
// Lock our mutex
|
||||
gothRWMutex.Lock()
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ type APIContext struct {
|
||||
|
||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||
|
||||
Repo *Repository
|
||||
Org *APIOrganization
|
||||
Package *Package
|
||||
Repo *Repository
|
||||
Org *APIOrganization
|
||||
Package *Package
|
||||
PublicOnly bool // Whether the request is for a public endpoint
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -138,10 +138,8 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
csrfOpts := CsrfOptions{
|
||||
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
||||
Header: "X-Csrf-Token",
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
CookiePath: setting.SessionConfig.CookiePath,
|
||||
SameSite: setting.SessionConfig.SameSite,
|
||||
@@ -167,7 +165,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
||||
ctx.Csrf = NewCSRFProtector(csrfOpts)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
||||
@@ -204,8 +202,6 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
|
||||
@@ -20,64 +20,42 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
CsrfHeaderName = "X-Csrf-Token"
|
||||
CsrfFormName = "_csrf"
|
||||
)
|
||||
|
||||
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
|
||||
type CSRFProtector interface {
|
||||
// GetHeaderName returns HTTP header to search for token.
|
||||
GetHeaderName() string
|
||||
// GetFormName returns form value to search for token.
|
||||
GetFormName() string
|
||||
// GetToken returns the token.
|
||||
GetToken() string
|
||||
// Validate validates the token in http context.
|
||||
// PrepareForSessionUser prepares the csrf protector for the current session user.
|
||||
PrepareForSessionUser(ctx *Context)
|
||||
// Validate validates the csrf token in http context.
|
||||
Validate(ctx *Context)
|
||||
// DeleteCookie deletes the cookie
|
||||
// DeleteCookie deletes the csrf cookie
|
||||
DeleteCookie(ctx *Context)
|
||||
}
|
||||
|
||||
type csrfProtector struct {
|
||||
opt CsrfOptions
|
||||
// Token generated to pass via header, cookie, or hidden form value.
|
||||
Token string
|
||||
// This value must be unique per user.
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetHeaderName returns the name of the HTTP header for csrf token.
|
||||
func (c *csrfProtector) GetHeaderName() string {
|
||||
return c.opt.Header
|
||||
}
|
||||
|
||||
// GetFormName returns the name of the form value for csrf token.
|
||||
func (c *csrfProtector) GetFormName() string {
|
||||
return c.opt.Form
|
||||
}
|
||||
|
||||
// GetToken returns the current token. This is typically used
|
||||
// to populate a hidden form in an HTML template.
|
||||
func (c *csrfProtector) GetToken() string {
|
||||
return c.Token
|
||||
// id must be unique per user.
|
||||
id string
|
||||
// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
|
||||
token string
|
||||
}
|
||||
|
||||
// CsrfOptions maintains options to manage behavior of Generate.
|
||||
type CsrfOptions struct {
|
||||
// The global secret value used to generate Tokens.
|
||||
Secret string
|
||||
// HTTP header used to set and get token.
|
||||
Header string
|
||||
// Form value used to set and get token.
|
||||
Form string
|
||||
// Cookie value used to set and get token.
|
||||
Cookie string
|
||||
// Cookie domain.
|
||||
@@ -87,103 +65,64 @@ type CsrfOptions struct {
|
||||
CookieHTTPOnly bool
|
||||
// SameSite set the cookie SameSite type
|
||||
SameSite http.SameSite
|
||||
// Key used for getting the unique ID per user.
|
||||
SessionKey string
|
||||
// oldSessionKey saves old value corresponding to SessionKey.
|
||||
oldSessionKey string
|
||||
// If true, send token via X-Csrf-Token header.
|
||||
SetHeader bool
|
||||
// If true, send token via _csrf cookie.
|
||||
SetCookie bool
|
||||
// Set the Secure flag to true on the cookie.
|
||||
Secure bool
|
||||
// Disallow Origin appear in request header.
|
||||
Origin bool
|
||||
// Cookie lifetime. Default is 0
|
||||
CookieLifeTime int
|
||||
// sessionKey is the key used for getting the unique ID per user.
|
||||
sessionKey string
|
||||
// oldSessionKey saves old value corresponding to sessionKey.
|
||||
oldSessionKey string
|
||||
}
|
||||
|
||||
func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
|
||||
if opt.Secret == "" {
|
||||
randBytes, err := util.CryptoRandomBytes(8)
|
||||
if err != nil {
|
||||
// this panic can be handled by the recover() in http handlers
|
||||
panic(fmt.Errorf("failed to generate random bytes: %w", err))
|
||||
}
|
||||
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
if opt.Header == "" {
|
||||
opt.Header = "X-Csrf-Token"
|
||||
}
|
||||
if opt.Form == "" {
|
||||
opt.Form = "_csrf"
|
||||
}
|
||||
if opt.Cookie == "" {
|
||||
opt.Cookie = "_csrf"
|
||||
}
|
||||
if opt.CookiePath == "" {
|
||||
opt.CookiePath = "/"
|
||||
}
|
||||
if opt.SessionKey == "" {
|
||||
opt.SessionKey = "uid"
|
||||
}
|
||||
if opt.CookieLifeTime == 0 {
|
||||
opt.CookieLifeTime = int(CsrfTokenTimeout.Seconds())
|
||||
}
|
||||
|
||||
opt.oldSessionKey = "_old_" + opt.SessionKey
|
||||
return opt
|
||||
}
|
||||
|
||||
func newCsrfCookie(c *csrfProtector, value string) *http.Cookie {
|
||||
func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: c.opt.Cookie,
|
||||
Name: opt.Cookie,
|
||||
Value: value,
|
||||
Path: c.opt.CookiePath,
|
||||
Domain: c.opt.CookieDomain,
|
||||
MaxAge: c.opt.CookieLifeTime,
|
||||
Secure: c.opt.Secure,
|
||||
HttpOnly: c.opt.CookieHTTPOnly,
|
||||
SameSite: c.opt.SameSite,
|
||||
Path: opt.CookiePath,
|
||||
Domain: opt.CookieDomain,
|
||||
MaxAge: int(CsrfTokenTimeout.Seconds()),
|
||||
Secure: opt.Secure,
|
||||
HttpOnly: opt.CookieHTTPOnly,
|
||||
SameSite: opt.SameSite,
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
||||
opt = prepareDefaultCsrfOptions(opt)
|
||||
x := &csrfProtector{opt: opt}
|
||||
|
||||
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
|
||||
return x
|
||||
func NewCSRFProtector(opt CsrfOptions) CSRFProtector {
|
||||
if opt.Secret == "" {
|
||||
panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code
|
||||
}
|
||||
opt.Cookie = util.IfZero(opt.Cookie, "_csrf")
|
||||
opt.CookiePath = util.IfZero(opt.CookiePath, "/")
|
||||
opt.sessionKey = "uid"
|
||||
opt.oldSessionKey = "_old_" + opt.sessionKey
|
||||
return &csrfProtector{opt: opt}
|
||||
}
|
||||
|
||||
x.ID = "0"
|
||||
uidAny := ctx.Session.Get(opt.SessionKey)
|
||||
if uidAny != nil {
|
||||
func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
||||
c.id = "0"
|
||||
if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil {
|
||||
switch uidVal := uidAny.(type) {
|
||||
case string:
|
||||
x.ID = uidVal
|
||||
c.id = uidVal
|
||||
case int64:
|
||||
x.ID = strconv.FormatInt(uidVal, 10)
|
||||
c.id = strconv.FormatInt(uidVal, 10)
|
||||
default:
|
||||
log.Error("invalid uid type in session: %T", uidAny)
|
||||
}
|
||||
}
|
||||
|
||||
oldUID := ctx.Session.Get(opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != x.ID
|
||||
cookieToken := ctx.GetSiteCookie(opt.Cookie)
|
||||
oldUID := ctx.Session.Get(c.opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != c.id
|
||||
cookieToken := ctx.GetSiteCookie(c.opt.Cookie)
|
||||
|
||||
needsNew := true
|
||||
if uidChanged {
|
||||
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
|
||||
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
||||
} else if cookieToken != "" {
|
||||
// If cookie token presents, re-use existing unexpired token, else generate a new one.
|
||||
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
||||
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
||||
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
||||
x.Token = cookieToken
|
||||
c.token = cookieToken
|
||||
needsNew = false
|
||||
}
|
||||
}
|
||||
@@ -191,42 +130,33 @@ func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
||||
|
||||
if needsNew {
|
||||
// FIXME: actionId.
|
||||
x.Token = GenerateCsrfToken(x.opt.Secret, x.ID, "POST", time.Now())
|
||||
if opt.SetCookie {
|
||||
cookie := newCsrfCookie(x, x.Token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
|
||||
cookie := newCsrfCookie(&c.opt, c.token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
if opt.SetHeader {
|
||||
ctx.Resp.Header().Add(opt.Header, x.Token)
|
||||
}
|
||||
return x
|
||||
ctx.Data["CsrfToken"] = c.token
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + template.HTMLEscapeString(c.token) + `">`)
|
||||
}
|
||||
|
||||
func (c *csrfProtector) validateToken(ctx *Context, token string) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.ID, "POST", time.Now()) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) {
|
||||
c.DeleteCookie(ctx)
|
||||
if middleware.IsAPIPath(ctx.Req) {
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
|
||||
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
|
||||
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
|
||||
// If this validation fails, custom Error is sent in the reply.
|
||||
// If neither a header nor form value is found, http.StatusBadRequest is sent.
|
||||
// If this validation fails, http.StatusBadRequest is sent.
|
||||
func (c *csrfProtector) Validate(ctx *Context) {
|
||||
if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
|
||||
if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
|
||||
if token := ctx.Req.FormValue(CsrfFormName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
@@ -234,9 +164,7 @@ func (c *csrfProtector) Validate(ctx *Context) {
|
||||
}
|
||||
|
||||
func (c *csrfProtector) DeleteCookie(ctx *Context) {
|
||||
if c.opt.SetCookie {
|
||||
cookie := newCsrfCookie(c, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
cookie := newCsrfCookie(&c.opt, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
@@ -607,7 +607,10 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
}
|
||||
}
|
||||
|
||||
isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || ctx.Link == ctx.Repo.RepoLink+"/settings" || strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/")
|
||||
isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink ||
|
||||
ctx.Link == ctx.Repo.RepoLink+"/settings" ||
|
||||
strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/") ||
|
||||
ctx.Link == ctx.Repo.RepoLink+"/-/migrate/status"
|
||||
|
||||
// Disable everything when the repo is being created
|
||||
if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
|
||||
|
||||
@@ -424,6 +424,7 @@ func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
|
||||
// ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile
|
||||
func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
|
||||
status := "changed"
|
||||
previousFilename := ""
|
||||
if f.IsDeleted {
|
||||
status = "deleted"
|
||||
} else if f.IsCreated {
|
||||
@@ -432,23 +433,21 @@ func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit stri
|
||||
status = "copied"
|
||||
} else if f.IsRenamed && f.Type == gitdiff.DiffFileRename {
|
||||
status = "renamed"
|
||||
previousFilename = f.OldName
|
||||
} else if f.Addition == 0 && f.Deletion == 0 {
|
||||
status = "unchanged"
|
||||
}
|
||||
|
||||
file := &api.ChangedFile{
|
||||
Filename: f.GetDiffFileName(),
|
||||
Status: status,
|
||||
Additions: f.Addition,
|
||||
Deletions: f.Deletion,
|
||||
Changes: f.Addition + f.Deletion,
|
||||
HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
|
||||
ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
|
||||
RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
|
||||
}
|
||||
|
||||
if status == "rename" {
|
||||
file.PreviousFilename = f.OldName
|
||||
Filename: f.GetDiffFileName(),
|
||||
Status: status,
|
||||
Additions: f.Addition,
|
||||
Deletions: f.Deletion,
|
||||
Changes: f.Addition + f.Deletion,
|
||||
PreviousFilename: previousFilename,
|
||||
HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
|
||||
ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
|
||||
RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
|
||||
}
|
||||
|
||||
return file
|
||||
|
||||
@@ -82,43 +82,40 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
|
||||
return nil
|
||||
}
|
||||
|
||||
attachmentIDs := make([]string, 0, len(content.Attachments))
|
||||
if setting.Attachment.Enabled {
|
||||
for _, attachment := range content.Attachments {
|
||||
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
||||
Name: attachment.Name,
|
||||
UploaderID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, a.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
if content.Content == "" && len(attachmentIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch r := ref.(type) {
|
||||
case *issues_model.Issue:
|
||||
attachmentIDs := make([]string, 0, len(content.Attachments))
|
||||
if setting.Attachment.Enabled {
|
||||
for _, attachment := range content.Attachments {
|
||||
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
||||
Name: attachment.Name,
|
||||
UploaderID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, a.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
if content.Content == "" && len(attachmentIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
_, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateIssueComment failed: %w", err)
|
||||
}
|
||||
case *issues_model.Comment:
|
||||
comment := r
|
||||
|
||||
if content.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if comment.Type == issues_model.CommentTypeCode {
|
||||
switch comment.Type {
|
||||
case issues_model.CommentTypeCode:
|
||||
_, err := pull_service.CreateCodeComment(
|
||||
ctx,
|
||||
doer,
|
||||
@@ -130,11 +127,16 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
|
||||
false, // not pending review but a single review
|
||||
comment.ReviewID,
|
||||
"",
|
||||
nil,
|
||||
attachmentIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateCodeComment failed: %w", err)
|
||||
}
|
||||
default:
|
||||
_, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateIssueComment failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -24,6 +24,6 @@ func NewMigrationHTTPTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Migrations.SkipTLSVerify},
|
||||
Proxy: proxy.Proxy(),
|
||||
DialContext: hostmatcher.NewDialContext("migration", allowList, blockList),
|
||||
DialContext: hostmatcher.NewDialContext("migration", allowList, blockList, setting.Proxy.ProxyURLFixed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,9 +499,5 @@ func Init() error {
|
||||
// TODO: at the moment, if ALLOW_LOCALNETWORKS=false, ALLOWED_DOMAINS=domain.com, and domain.com has IP 127.0.0.1, then it's still allowed.
|
||||
// if we want to block such case, the private&loopback should be added to the blockList when ALLOW_LOCALNETWORKS=false
|
||||
|
||||
if setting.Proxy.Enabled && setting.Proxy.ProxyURLFixed != nil {
|
||||
allowList.AppendPattern(setting.Proxy.ProxyURLFixed.Host)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -438,7 +437,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
|
||||
Archive: pd.FileMetadata.ArchiveSize,
|
||||
},
|
||||
Location: Location{
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))),
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture),
|
||||
},
|
||||
Format: Format{
|
||||
License: pd.VersionMetadata.License,
|
||||
|
||||
@@ -990,6 +990,8 @@ type CommitInfo struct {
|
||||
}
|
||||
|
||||
// GetPullCommits returns all commits on given pull request and the last review commit sha
|
||||
// Attention: The last review commit sha must be from the latest review whose commit id is not empty.
|
||||
// So the type of the latest review cannot be "ReviewTypeRequest".
|
||||
func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]CommitInfo, string, error) {
|
||||
pull := issue.PullRequest
|
||||
|
||||
@@ -1035,7 +1037,11 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co
|
||||
lastreview, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
|
||||
IssueID: issue.ID,
|
||||
ReviewerID: ctx.Doer.ID,
|
||||
Type: issues_model.ReviewTypeUnknown,
|
||||
Types: []issues_model.ReviewType{
|
||||
issues_model.ReviewTypeApprove,
|
||||
issues_model.ReviewTypeComment,
|
||||
issues_model.ReviewTypeReject,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil && !issues_model.IsErrReviewNotExist(err) {
|
||||
|
||||
@@ -348,7 +348,7 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
|
||||
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
IssueID: pull.IssueID,
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
|
||||
Dismissed: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -65,7 +65,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel
|
||||
|
||||
commit, err := gitRepo.GetCommit(rel.Target)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("createTag::GetCommit[%v]: %w", rel.Target, err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(msg) > 0 {
|
||||
|
||||
@@ -483,13 +483,12 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
||||
}
|
||||
|
||||
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
||||
return fmt.Errorf("GetBranch: %vc", err)
|
||||
}
|
||||
|
||||
if rawBranch.IsDeleted {
|
||||
return nil
|
||||
}
|
||||
// database branch record not exist or it's a deleted branch
|
||||
notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit(branchName)
|
||||
if err != nil {
|
||||
@@ -497,8 +496,10 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
||||
}
|
||||
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return err
|
||||
if !notExist {
|
||||
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
|
||||
@@ -320,8 +320,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
@@ -382,12 +383,12 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
|
||||
rel, has := relMap[lowerTag]
|
||||
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
if !has {
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
rel = &repo_model.Release{
|
||||
RepoID: repo.ID,
|
||||
Title: parts[0],
|
||||
@@ -408,10 +409,11 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
rel.IsDraft = false
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ func Init() error {
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
|
||||
Proxy: webhookProxy(allowedHostMatcher),
|
||||
DialContext: hostmatcher.NewDialContextWithProxy("webhook", allowedHostMatcher, nil, setting.Webhook.ProxyURLFixed),
|
||||
DialContext: hostmatcher.NewDialContext("webhook", allowedHostMatcher, nil, setting.Webhook.ProxyURLFixed),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@@ -149,8 +150,14 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
||||
var text string
|
||||
// for each commit, generate attachment text
|
||||
for i, commit := range p.Commits {
|
||||
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
|
||||
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
|
||||
// limit the commit message display to just the summary, otherwise it would be hard to read
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 1)[0], "\r")
|
||||
|
||||
// a limit of 50 is set because GitHub does the same
|
||||
if utf8.RuneCountInString(message) > 50 {
|
||||
message = fmt.Sprintf("%.47s...", message)
|
||||
}
|
||||
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name)
|
||||
// add linebreak to each commit but the last
|
||||
if i < len(p.Commits)-1 {
|
||||
text += "\n"
|
||||
|
||||
@@ -80,6 +80,20 @@ func TestDiscordPayload(t *testing.T) {
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithLongCommitMessage", func(t *testing.T) {
|
||||
p := pushTestMultilineCommitMessagePayload()
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", pl.Embeds[0].Description)
|
||||
assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
p := issueTestPayload()
|
||||
|
||||
|
||||
@@ -64,9 +64,17 @@ func forkTestPayload() *api.ForkPayload {
|
||||
}
|
||||
|
||||
func pushTestPayload() *api.PushPayload {
|
||||
return pushTestPayloadWithCommitMessage("commit message")
|
||||
}
|
||||
|
||||
func pushTestMultilineCommitMessagePayload() *api.PushPayload {
|
||||
return pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body.")
|
||||
}
|
||||
|
||||
func pushTestPayloadWithCommitMessage(message string) *api.PushPayload {
|
||||
commit := &api.PayloadCommit{
|
||||
ID: "2020558fe2e34debb818a514715839cabd25e778",
|
||||
Message: "commit message",
|
||||
Message: message,
|
||||
URL: "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
|
||||
Author: &api.PayloadUser{
|
||||
Name: "user1",
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
</div>
|
||||
<div class="inline required field">
|
||||
<label for="domain">{{ctx.Locale.Tr "install.domain"}}</label>
|
||||
<input id="domain" name="domain" value="{{.domain}}" placeholder="try.gitea.io" required>
|
||||
<input id="domain" name="domain" value="{{.domain}}" placeholder="demo.gitea.com" required>
|
||||
<span class="help">{{ctx.Locale.Tr "install.domain_helper"}}</span>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
@@ -139,7 +139,7 @@
|
||||
</div>
|
||||
<div class="inline required field">
|
||||
<label for="app_url">{{ctx.Locale.Tr "install.app_url"}}</label>
|
||||
<input id="app_url" name="app_url" value="{{.app_url}}" placeholder="https://try.gitea.io" required>
|
||||
<input id="app_url" name="app_url" value="{{.app_url}}" placeholder="https://demo.gitea.com" required>
|
||||
<span class="help">{{ctx.Locale.Tr "install.app_url_helper"}}</span>
|
||||
</div>
|
||||
<div class="inline required field">
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.settings.rename_branch"}}
|
||||
</div>
|
||||
<form class="ui form" action="{{$.Repository.Link}}/settings/rename_branch" method="post">
|
||||
<form class="ui form" action="{{$.Repository.Link}}/branches/rename" method="post">
|
||||
<div class="content">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="field default-branch-warning">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="ui secondary filter menu">
|
||||
{{if not .Repository.IsArchived}}
|
||||
<!-- Action Button -->
|
||||
{{if .IsShowClosed}}
|
||||
{{if and .IsShowClosed.Has .IsShowClosed.Value}}
|
||||
<button class="ui primary basic button issue-action" data-action="open" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_open"}}</button>
|
||||
{{else}}
|
||||
{{else if and .IsShowClosed.Has (not .IsShowClosed.Value)}}
|
||||
<button class="ui red basic button issue-action" data-action="close" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_close"}}</button>
|
||||
{{end}}
|
||||
{{if $.IsRepoAdmin}}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{{template "base/alert" .}}
|
||||
<div class="home">
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div id="repo_migrating" class="sixteen wide center aligned centered column" data-migrating-task-id="{{.MigrateTask.ID}}">
|
||||
<div id="repo_migrating" class="sixteen wide center aligned centered column" data-migrating-repo-link="{{.Link}}">
|
||||
<div>
|
||||
<img src="{{AssetUrlPrefix}}/img/loading.png">
|
||||
</div>
|
||||
|
||||
@@ -15,19 +15,17 @@
|
||||
<form class="tw-flex" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="action" value="default_branch">
|
||||
{{if not .Repository.IsEmpty}}
|
||||
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
<button class="ui primary button"{{if .Repository.IsEmpty}} disabled{{end}}>{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeCode}}
|
||||
{{if not .Repository.IsEmpty}}
|
||||
<a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches">
|
||||
{{ctx.Locale.Tr "repo.settings.branches"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches">
|
||||
{{ctx.Locale.Tr "repo.settings.branches"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsTags}}active {{end}}item" href="{{.RepoLink}}/settings/tags">
|
||||
{{ctx.Locale.Tr "repo.settings.tags"}}
|
||||
</a>
|
||||
|
||||
2
templates/swagger/v1_json.tmpl
generated
2
templates/swagger/v1_json.tmpl
generated
@@ -5386,7 +5386,7 @@
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get the pull request of the commit",
|
||||
"summary": "Get the merged pull request of the commit",
|
||||
"operationId": "repoGetCommitPullRequest",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -75,6 +75,34 @@ func TestAPIListIssues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIListIssuesPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID})
|
||||
|
||||
session := loginUser(t, owner1.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name))
|
||||
link.RawQuery = url.Values{"state": {"all"}}.Encode()
|
||||
req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
|
||||
|
||||
session = loginUser(t, owner2.Name)
|
||||
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name))
|
||||
link.RawQuery = url.Values{"state": {"all"}}.Encode()
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPICreateIssue(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
const body, title = "apiTestBody", "apiTestTitle"
|
||||
@@ -243,6 +271,12 @@ func TestAPISearchIssues(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, expectedIssueCount)
|
||||
|
||||
publicOnlyToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiIssues)
|
||||
assert.Len(t, apiIssues, 15) // 15 public issues
|
||||
|
||||
since := "2000-01-01T00:50:01+00:00" // 946687801
|
||||
before := time.Unix(999307200, 0).Format(time.RFC3339)
|
||||
query.Add("since", since)
|
||||
|
||||
@@ -36,6 +36,7 @@ func TestPackageComposer(t *testing.T) {
|
||||
packageType := "composer-plugin"
|
||||
packageAuthor := "Gitea Authors"
|
||||
packageLicense := "MIT"
|
||||
packageBin := "./bin/script"
|
||||
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
@@ -49,6 +50,9 @@ func TestPackageComposer(t *testing.T) {
|
||||
{
|
||||
"name": "` + packageAuthor + `"
|
||||
}
|
||||
],
|
||||
"bin": [
|
||||
"` + packageBin + `"
|
||||
]
|
||||
}`))
|
||||
archive.Close()
|
||||
@@ -210,6 +214,8 @@ func TestPackageComposer(t *testing.T) {
|
||||
assert.Len(t, pkgs[0].Authors, 1)
|
||||
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
|
||||
assert.Equal(t, "zip", pkgs[0].Dist.Type)
|
||||
assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum)
|
||||
assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
|
||||
assert.Len(t, pkgs[0].Bin, 1)
|
||||
assert.Equal(t, packageBin, pkgs[0].Bin[0])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestAPIPullReview(t *testing.T) {
|
||||
|
||||
var reviews []*api.PullReview
|
||||
DecodeJSON(t, resp, &reviews)
|
||||
if !assert.Len(t, reviews, 6) {
|
||||
if !assert.Len(t, reviews, 8) {
|
||||
return
|
||||
}
|
||||
for _, r := range reviews {
|
||||
|
||||
@@ -334,3 +334,19 @@ func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICommitPullRequest(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3"
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234"
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token)
|
||||
ctx.Session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -208,6 +208,24 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
|
||||
createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test")
|
||||
}
|
||||
|
||||
func TestAPICreateReleaseGivenInvalidTarget(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, owner.LowerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
|
||||
TagName: "i-point-to-an-invalid-target",
|
||||
Title: "Invalid Target",
|
||||
Target: "invalid-target",
|
||||
}).AddTokenAuth(token)
|
||||
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPIGetLatestRelease(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
||||
@@ -28,9 +28,13 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
session := loginUser(t, user1.LowerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
// public only token should be forbidden
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository)
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo
|
||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
@@ -42,6 +46,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.EqualValues(t, "master", branches[1].Name)
|
||||
|
||||
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
@@ -49,6 +55,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.NoError(t, json.Unmarshal(bs, &branch))
|
||||
assert.EqualValues(t, "test_branch", branch.Name)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
|
||||
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`))
|
||||
@@ -73,6 +81,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
|
||||
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -23,10 +23,10 @@ func TestAPICreateAndDeleteToken(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil)
|
||||
newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
deleteAPIAccessToken(t, newAccessToken, user)
|
||||
|
||||
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil)
|
||||
newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
deleteAPIAccessToken(t, newAccessToken, user)
|
||||
}
|
||||
|
||||
@@ -72,19 +72,19 @@ func TestAPIDeleteTokensPermission(t *testing.T) {
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
// admin can delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1").
|
||||
AddBasicAuth(admin.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can delete tokens for himself
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2").
|
||||
AddBasicAuth(user2.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can't delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil)
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll})
|
||||
req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3").
|
||||
AddBasicAuth(user4.Name)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
@@ -520,7 +520,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
||||
unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...)
|
||||
}
|
||||
|
||||
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes)
|
||||
accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, unauthorizedScopes)
|
||||
defer deleteAPIAccessToken(t, accessToken, user)
|
||||
|
||||
// Request the endpoint. Verify that permission is denied.
|
||||
@@ -532,20 +532,12 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model
|
||||
|
||||
// createAPIAccessTokenWithoutCleanUp Create an API access token and assert that
|
||||
// creation succeeded. The caller is responsible for deleting the token.
|
||||
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken {
|
||||
func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes []auth_model.AccessTokenScope) api.AccessToken {
|
||||
payload := map[string]any{
|
||||
"name": tokenName,
|
||||
}
|
||||
if scopes != nil {
|
||||
for _, scope := range *scopes {
|
||||
scopes, scopesExists := payload["scopes"].([]string)
|
||||
if !scopesExists {
|
||||
scopes = make([]string, 0)
|
||||
}
|
||||
scopes = append(scopes, string(scope))
|
||||
payload["scopes"] = scopes
|
||||
}
|
||||
"name": tokenName,
|
||||
"scopes": scopes,
|
||||
}
|
||||
|
||||
log.Debug("Requesting creation of token with scopes: %v", scopes)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload).
|
||||
AddBasicAuth(user.Name)
|
||||
@@ -563,8 +555,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us
|
||||
return newAccessToken
|
||||
}
|
||||
|
||||
// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that
|
||||
// deletion succeeded.
|
||||
// deleteAPIAccessToken deletes an API access token and assert that deletion succeeded.
|
||||
func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) {
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID).
|
||||
AddBasicAuth(user.Name)
|
||||
|
||||
@@ -38,6 +38,19 @@ func TestAPIUserSearchLoggedIn(t *testing.T) {
|
||||
assert.Contains(t, user.UserName, query)
|
||||
assert.NotEmpty(t, user.Email)
|
||||
}
|
||||
|
||||
publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query).
|
||||
AddTokenAuth(publicToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
results = SearchResults{}
|
||||
DecodeJSON(t, resp, &results)
|
||||
assert.NotEmpty(t, results.Data)
|
||||
for _, user := range results.Data {
|
||||
assert.Contains(t, user.UserName, query)
|
||||
assert.NotEmpty(t, user.Email)
|
||||
assert.True(t, user.Visibility == "public")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIUserSearchNotLoggedIn(t *testing.T) {
|
||||
|
||||
@@ -29,7 +29,7 @@ func generateImg() bytes.Buffer {
|
||||
return buff
|
||||
}
|
||||
|
||||
func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string {
|
||||
func createAttachment(t *testing.T, session *TestSession, csrf, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string {
|
||||
body := &bytes.Buffer{}
|
||||
|
||||
// Setup multi-part
|
||||
@@ -41,8 +41,6 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
|
||||
err = writer.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
csrf := GetCSRF(t, session, repoURL)
|
||||
|
||||
req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body)
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
@@ -59,14 +57,14 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
|
||||
func TestCreateAnonymousAttachment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := emptyTestSession(t)
|
||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther)
|
||||
createAttachment(t, session, GetCSRF(t, session, "/user/login"), "user2/repo1", "image.png", generateImg(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func TestCreateIssueAttachment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
const repoURL = "user2/repo1"
|
||||
session := loginUser(t, "user2")
|
||||
uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK)
|
||||
uuid := createAttachment(t, session, GetCSRF(t, session, repoURL), repoURL, "image.png", generateImg(), http.StatusOK)
|
||||
|
||||
req := NewRequest(t, "GET", repoURL+"/issues/new")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -5,12 +5,10 @@ package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -25,28 +23,12 @@ func TestCsrfProtection(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": "fake_csrf",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
|
||||
// test web form csrf via header. TODO: should use an UI api to test
|
||||
req = NewRequest(t, "POST", "/user/settings")
|
||||
req.Header.Add("X-Csrf-Token", "fake_csrf")
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc = resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp = session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -26,187 +27,190 @@ import (
|
||||
)
|
||||
|
||||
func TestIncomingEmail(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
t.Run("Payload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Payload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
|
||||
_, err := incoming_payload.CreateReferencePayload(user)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err := incoming_payload.CreateReferencePayload(user)
|
||||
assert.Error(t, err)
|
||||
issuePayload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issuePayload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
_, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3})
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3})
|
||||
assert.Error(t, err)
|
||||
ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Issue))
|
||||
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID)
|
||||
|
||||
ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Issue))
|
||||
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID)
|
||||
ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Comment))
|
||||
assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
|
||||
})
|
||||
|
||||
ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Comment))
|
||||
assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
|
||||
})
|
||||
t.Run("Token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
payload := []byte{1, 2, 3, 4, 5}
|
||||
|
||||
payload := []byte{1, 2, 3, 4, 5}
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
assert.Equal(t, payload, p)
|
||||
})
|
||||
|
||||
ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
assert.Equal(t, payload, p)
|
||||
})
|
||||
t.Run("Handler", func(t *testing.T) {
|
||||
t.Run("Reply", func(t *testing.T) {
|
||||
t.Run("Comment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Handler", func(t *testing.T) {
|
||||
t.Run("Reply", func(t *testing.T) {
|
||||
t.Run("Comment", func(t *testing.T) {
|
||||
handler := &incoming.ReplyHandler{}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment := comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
})
|
||||
|
||||
t.Run("CodeComment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
content := &incoming.MailContent{
|
||||
Content: "code reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeCode,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment = comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Unsubscribe", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, watching)
|
||||
|
||||
handler := &incoming.UnsubscribeHandler{}
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "unsub me",
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment := comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
assert.False(t, watching)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CodeComment", func(t *testing.T) {
|
||||
if setting.IncomingEmail.Enabled {
|
||||
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
|
||||
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
|
||||
t.Run("IMAP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
content := &incoming.MailContent{
|
||||
Content: "code reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeCode,
|
||||
})
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
|
||||
msg.SetHeader("From", user.Email)
|
||||
msg.SetBody("text/plain", token)
|
||||
err = gomail.Send(&smtpTestSender{}, msg)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment = comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Empty(t, comment.Attachments)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
|
||||
comment := comments[len(comments)-1]
|
||||
|
||||
return comment.PosterID == user.ID && comment.Content == token
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Unsubscribe", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, watching)
|
||||
|
||||
handler := &incoming.UnsubscribeHandler{}
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "unsub me",
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, watching)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if setting.IncomingEmail.Enabled {
|
||||
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
|
||||
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
|
||||
t.Run("IMAP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
|
||||
msg.SetHeader("From", user.Email)
|
||||
msg.SetBody("text/plain", token)
|
||||
err = gomail.Send(&smtpTestSender{}, msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
|
||||
comment := comments[len(comments)-1]
|
||||
|
||||
return comment.PosterID == user.ID && comment.Content == token
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A simple SMTP mail sender used for integration tests.
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
@@ -486,12 +487,16 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
|
||||
}
|
||||
|
||||
// GetCSRF returns CSRF token from body
|
||||
// If it fails, it means the CSRF token is not found in the response body returned by the url with the given session.
|
||||
// In this case, you should find a better url to get it.
|
||||
func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
|
||||
t.Helper()
|
||||
req := NewRequest(t, "GET", urlStr)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
return doc.GetCSRF()
|
||||
csrf := doc.GetCSRF()
|
||||
require.NotEmpty(t, csrf)
|
||||
return csrf
|
||||
}
|
||||
|
||||
// GetCSRFFrom returns CSRF token from body
|
||||
|
||||
@@ -204,9 +204,7 @@ func TestTeamSearch(t *testing.T) {
|
||||
var results TeamSearchResults
|
||||
|
||||
session := loginUser(t, user.Name)
|
||||
csrf := GetCSRF(t, session, "/"+org.Name)
|
||||
req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team")
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &results)
|
||||
assert.NotEmpty(t, results.Data)
|
||||
@@ -217,8 +215,6 @@ func TestTeamSearch(t *testing.T) {
|
||||
// no access if not organization member
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
session = loginUser(t, user5.Name)
|
||||
csrf = GetCSRF(t, session, "/"+org.Name)
|
||||
req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team")
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
34
tests/integration/pull_commit_test.go
Normal file
34
tests/integration/pull_commit_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestListPullCommits(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user5")
|
||||
req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var pullCommitList struct {
|
||||
Commits []pull_service.CommitInfo `json:"commits"`
|
||||
LastReviewCommitSha string `json:"last_review_commit_sha"`
|
||||
}
|
||||
DecodeJSON(t, resp, &pullCommitList)
|
||||
|
||||
if assert.Len(t, pullCommitList.Commits, 2) {
|
||||
assert.Equal(t, "5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", pullCommitList.Commits[0].ID)
|
||||
assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.Commits[1].ID)
|
||||
}
|
||||
assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha)
|
||||
})
|
||||
}
|
||||
@@ -28,11 +28,11 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
||||
|
||||
// get branch setting page
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequest(t, "GET", "/user2/repo1/settings/branches")
|
||||
req := NewRequest(t, "GET", "/user2/repo1/branches")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"from": "master",
|
||||
"to": "main",
|
||||
@@ -76,7 +76,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
||||
assert.Equal(t, "branch2", branch2.Name)
|
||||
|
||||
// rename branch2 to branch1
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"from": "branch2",
|
||||
"to": "branch1",
|
||||
@@ -103,7 +103,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
||||
assert.True(t, branch1.IsDeleted) // virtual deletion
|
||||
|
||||
// rename branch2 to branch1 again
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
"from": "branch2",
|
||||
"to": "branch1",
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
@@ -146,15 +145,8 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
|
||||
"_csrf": "fake_csrf",
|
||||
"new_branch_name": "test",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t,
|
||||
"Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
||||
func prepareBranch(t *testing.T, session *TestSession, repo *repo_model.Repository) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateNewTagProtected(t *testing.T) {
|
||||
@@ -60,6 +62,40 @@ func TestCreateNewTagProtected(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("GitTagForce", func(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "the tag already exists in the remote")
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName())
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
tagsTab := htmlDoc.Find(".release-list-title")
|
||||
assert.Contains(t, tagsTab.Text(), "force update v2")
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup
|
||||
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
|
||||
@@ -78,7 +78,6 @@ const sfc = {
|
||||
searchURL() {
|
||||
return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery
|
||||
}&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
|
||||
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
|
||||
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
|
||||
}${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : ''
|
||||
}`;
|
||||
|
||||
@@ -60,7 +60,7 @@ export function initRepoTopicBar() {
|
||||
// how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
|
||||
const responseData = await response.json();
|
||||
lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
|
||||
if (responseData.invalidTopics.length > 0) {
|
||||
if (responseData.invalidTopics && responseData.invalidTopics.length > 0) {
|
||||
const {invalidTopics} = responseData;
|
||||
const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
|
||||
for (const [index, value] of topics.split(',').entries()) {
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
import {GET, POST} from '../modules/fetch.js';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoMigrationStatusChecker() {
|
||||
const repoMigrating = document.getElementById('repo_migrating');
|
||||
if (!repoMigrating) return;
|
||||
|
||||
document.getElementById('repo_migrating_retry').addEventListener('click', doMigrationRetry);
|
||||
document.getElementById('repo_migrating_retry')?.addEventListener('click', doMigrationRetry);
|
||||
|
||||
const task = repoMigrating.getAttribute('data-migrating-task-id');
|
||||
const repoLink = repoMigrating.getAttribute('data-migrating-repo-link');
|
||||
|
||||
// returns true if the refresh still needs to be called after a while
|
||||
const refresh = async () => {
|
||||
const res = await GET(`${appSubUrl}/user/task/${task}`);
|
||||
const res = await GET(`${repoLink}/-/migrate/status`);
|
||||
if (res.status !== 200) return true; // continue to refresh if network error occurs
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
Reference in New Issue
Block a user