mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-05 18:32:41 +09:00
Compare commits
92 Commits
v1.17.2
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ded9a218f7 | ||
|
|
03f06d5ac1 | ||
|
|
8cd6be1723 | ||
|
|
f882747209 | ||
|
|
92796dcc8b | ||
|
|
48450939c7 | ||
|
|
7dcf9dd4d8 | ||
|
|
73189f0a19 | ||
|
|
92f72d678c | ||
|
|
7e26f2b626 | ||
|
|
3d34cdabb9 | ||
|
|
f51a19c537 | ||
|
|
068e96fbd2 | ||
|
|
721e422fa7 | ||
|
|
6f323d13dd | ||
|
|
0e95e7460e | ||
|
|
c057590a3a | ||
|
|
a8534ac4a4 | ||
|
|
e23ad87b55 | ||
|
|
e93a4a0174 | ||
|
|
601766d1fa | ||
|
|
ee6d5124bd | ||
|
|
8188cdfcd2 | ||
|
|
82d50af721 | ||
|
|
6117c8b15a | ||
|
|
ba16df8da3 | ||
|
|
87630a6583 | ||
|
|
56716f5834 | ||
|
|
65b5c8e532 | ||
|
|
9dc53ba65f | ||
|
|
d25c74f353 | ||
|
|
795913e3c7 | ||
|
|
e609ef9585 | ||
|
|
f321cdced7 | ||
|
|
f241201484 | ||
|
|
43bddc1405 | ||
|
|
9414260d67 | ||
|
|
3c07ed0911 | ||
|
|
995ae06a6e | ||
|
|
14342047ad | ||
|
|
d6d62c071f | ||
|
|
7a2daae7c3 | ||
|
|
5bc3fbd511 | ||
|
|
b0a057f1c0 | ||
|
|
43a8547df6 | ||
|
|
291787a5ef | ||
|
|
e504410708 | ||
|
|
2ccf940464 | ||
|
|
169c08e20a | ||
|
|
d5856fece7 | ||
|
|
0571ddc368 | ||
|
|
6b7ce726c2 | ||
|
|
92b5f48c40 | ||
|
|
8043fbce09 | ||
|
|
556e2d5506 | ||
|
|
675c14aba6 | ||
|
|
4b4adb1cc9 | ||
|
|
19df07f021 | ||
|
|
5a84558e7c | ||
|
|
46053c092d | ||
|
|
3f032759ed | ||
|
|
f48fda8eef | ||
|
|
cd48a007bb | ||
|
|
6afbef5a8b | ||
|
|
d745780014 | ||
|
|
652abf0ae0 | ||
|
|
1f804d35ca | ||
|
|
c83a05f114 | ||
|
|
a3c75ac438 | ||
|
|
14bc4d79c1 | ||
|
|
672d54fafa | ||
|
|
0495544b8a | ||
|
|
1fbc56d732 | ||
|
|
1a9ba1c65d | ||
|
|
cbebcc1c26 | ||
|
|
0e677d7b41 | ||
|
|
790770aef3 | ||
|
|
43b4c38d4f | ||
|
|
e79a10793f | ||
|
|
be5411d6b5 | ||
|
|
bdf3be53b0 | ||
|
|
e50473e6bb | ||
|
|
20c135cd46 | ||
|
|
937ef6fa90 | ||
|
|
54d4e664c2 | ||
|
|
c571ac6fd3 | ||
|
|
f663773200 | ||
|
|
a28677273b | ||
|
|
c8d687997d | ||
|
|
5cb1037cb7 | ||
|
|
2dcea782c5 | ||
|
|
31842f12a4 |
27
.drone.yml
27
.drone.yml
@@ -851,7 +851,8 @@ steps:
|
||||
image: plugins/hugo:latest
|
||||
pull: always
|
||||
commands:
|
||||
- apk add --no-cache make bash curl
|
||||
# https://github.com/drone-plugins/drone-hugo/issues/36
|
||||
- apk upgrade --no-cache libcurl && apk add --no-cache make bash curl
|
||||
- cd docs
|
||||
- make trans-copy clean build
|
||||
|
||||
@@ -902,8 +903,11 @@ steps:
|
||||
image: techknowlogick/drone-docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-amd64
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-amd64
|
||||
- ${DRONE_TAG:1:4}-linux-amd64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
@@ -920,8 +924,11 @@ steps:
|
||||
image: techknowlogick/drone-docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-amd64-rootless
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-amd64-rootless
|
||||
- ${DRONE_TAG:1:4}-linux-amd64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
@@ -1126,8 +1133,11 @@ steps:
|
||||
image: techknowlogick/drone-docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-arm64
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-arm64
|
||||
- ${DRONE_TAG:1:4}-linux-arm64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
@@ -1144,8 +1154,11 @@ steps:
|
||||
image: techknowlogick/drone-docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-arm64-rootless
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-arm64-rootless
|
||||
- ${DRONE_TAG:1:4}-linux-arm64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
@@ -1299,7 +1312,7 @@ steps:
|
||||
image: plugins/manifest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.rootless.tmpl
|
||||
password:
|
||||
@@ -1310,7 +1323,7 @@ steps:
|
||||
- name: manifest
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.tmpl
|
||||
password:
|
||||
|
||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -4,6 +4,96 @@ This changelog goes through all 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.io).
|
||||
|
||||
## [1.17.4](https://github.com/go-gitea/gitea/releases/tag/1.17.4) - 2022-12-21
|
||||
|
||||
* SECURITY
|
||||
* Do not allow Ghost access to limited visible user/org (#21849) (#21875)
|
||||
* Fix package access for admins and inactive users (#21580) (#21592)
|
||||
* ENHANCEMENTS
|
||||
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21927)
|
||||
* Fix vertical align of committer avatar rendered by email address (#21884) (#21919)
|
||||
* Fix setting HTTP headers after write (#21833) (#21874)
|
||||
* Ignore line anchor links with leading zeroes (#21728) (#21777)
|
||||
* Enable Monaco automaticLayout (#21516)
|
||||
* BUGFIXES
|
||||
* Do not list active repositories as unadopted (#22034) (#22167)
|
||||
* Correctly handle moved files in apply patch (#22118) (#22136)
|
||||
* Fix condition for is_internal (#22095) (#22131)
|
||||
* Fix permission check on issue/pull lock (#22114)
|
||||
* Fix sorting admin user list by last login (#22081) (#22106)
|
||||
* Workaround for container registry push/pull errors (#21862) (#22069)
|
||||
* Fix issue/PR numbers (#22037) (#22045)
|
||||
* Handle empty author names (#21902) (#22028)
|
||||
* Fix ListBranches to handle empty case (#21921) (#22025)
|
||||
* Fix enabling partial clones on 1.17 (#21809)
|
||||
* Prevent panic in doctor command when running default checks (#21791) (#21808)
|
||||
* Upgrade golang.org/x/crypto (#21792) (#21794)
|
||||
* Init git module before database migration (#21764) (#21766)
|
||||
* Set last login when activating account (#21731) (#21754)
|
||||
* Add HEAD fix to gitea doctor (#21352) (#21751)
|
||||
* Fix UI language switching bug (#21597) (#21748)
|
||||
* Remove semver compatible flag and change pypi to an array of test cases (#21708) (#21729)
|
||||
* Allow local package identifiers for PyPI packages (#21690) (#21726)
|
||||
* Fix repository adoption on Windows (#21646) (#21651)
|
||||
* Sync git hooks when config file path changed (#21619) (#21625)
|
||||
* Added check for disabled Packages (#21540) (#21614)
|
||||
* Fix `Timestamp.IsZero` (#21593) (#21604)
|
||||
* Fix issues count bug (#21600)
|
||||
* Support binary deploy in npm packages (#21589)
|
||||
* Update milestone counters when issue is deleted (#21459) (#21586)
|
||||
* SessionUser protection against nil pointer dereference (#21581)
|
||||
* Case-insensitive NuGet symbol file GUID (#21409) (#21575)
|
||||
* Suppress `ExternalLoginUserNotExist` error (#21504) (#21572)
|
||||
* Prevent Authorization header for presigned LFS urls (#21531) (#21569)
|
||||
* Update binding to fix bugs (#21560)
|
||||
* Fix generating compare link (#21519) (#21530)
|
||||
* Ignore error when retrieving changed PR review files (#21487) (#21524)
|
||||
* Fix incorrect notification commit url (#21479) (#21483)
|
||||
* Display total commit count in hook message (#21400) (#21481)
|
||||
* Enforce grouped NuGet search results (#21442) (#21480)
|
||||
* Return 404 when user is not found on avatar (#21476) (#21477)
|
||||
* Normalize NuGet package version on upload (#22186) (#22201)
|
||||
* MISC
|
||||
* Check for zero time instant in TimeStamp.IsZero() (#22171) (#22173)
|
||||
* Fix warn in database structs sync (#22111)
|
||||
* Allow for resolution of NPM registry paths that match upstream (#21568) (#21723)
|
||||
|
||||
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
|
||||
|
||||
* SECURITY
|
||||
* Sanitize and Escape refs in git backend (#21464) (#21463)
|
||||
* Bump `golang.org/x/text` (#21412) (#21413)
|
||||
* Update bluemonday (#21281) (#21287)
|
||||
* ENHANCEMENTS
|
||||
* Fix empty container layer history and UI (#21251) (#21278)
|
||||
* Use en-US as fallback when using other default language (#21200) (#21256)
|
||||
* Make the vscode clone link respect transport protocol (#20557) (#21128)
|
||||
* BUGFIXES
|
||||
* Do DB update after merge in hammer context (#21401) (#21416)
|
||||
* Add Num{Issues,Pulls} stats checks (#21404) (#21414)
|
||||
* Stop logging CheckPath returns error: context canceled (#21064) (#21405)
|
||||
* Parse OAuth Authorization header when request omits client secret (#21351) (#21374)
|
||||
* Ignore port for loopback redirect URIs (#21293) (#21373)
|
||||
* Set SemverCompatible to false for Conan packages (#21275) (#21366)
|
||||
* Tag list should include draft releases with existing tags (#21263) (#21365)
|
||||
* Fix linked account translation (#21331) (#21334)
|
||||
* Make NuGet service index publicly accessible (#21242) (#21277)
|
||||
* Foreign ID conflicts if ID is 0 for each item (#21271) (#21272)
|
||||
* Use absolute links in feeds (#21229) (#21265)
|
||||
* Prevent invalid behavior for file reviewing when loading more files (#21230) (#21234)
|
||||
* Respect `REQUIRE_SIGNIN_VIEW` for packages (#20873) (#21232)
|
||||
* Treat git object mode 40755 as directory (#21195) (#21218)
|
||||
* Allow uppercase ASCII alphabet in PyPI package names (#21095) (#21217)
|
||||
* Fix limited user cannot view himself's profile (#21212)
|
||||
* Fix template bug of admin monitor (#21209)
|
||||
* Fix reaction of issues (#21185) (#21196)
|
||||
* Fix CSV diff for added/deleted files (#21189) (#21193)
|
||||
* Fix pagination limit parameter problem (#21111)
|
||||
* TESTING
|
||||
* Fix missing m.Run() in TestMain (#21341)
|
||||
* BUILD
|
||||
* Use Go 1.19 fmt for Gitea 1.17, sync emoji data (#21239)
|
||||
|
||||
## [1.17.2](https://github.com/go-gitea/gitea/releases/tag/v1.17.2) - 2022-09-06
|
||||
|
||||
* SECURITY
|
||||
|
||||
2
Makefile
2
Makefile
@@ -33,7 +33,7 @@ GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.3.1
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.46.0
|
||||
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
|
||||
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.29.0
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.0
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
|
||||
DOCKER_IMAGE ?= gitea/gitea
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -214,8 +214,7 @@ const hdr = `
|
||||
|
||||
package emoji
|
||||
|
||||
// Code generated by gen.go. DO NOT EDIT.
|
||||
// Code generated by build/generate-emoji.go. DO NOT EDIT.
|
||||
// Sourced from %s
|
||||
//
|
||||
var GemojiData = %#v
|
||||
`
|
||||
|
||||
17
go.mod
17
go.mod
@@ -5,7 +5,7 @@ go 1.18
|
||||
require (
|
||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
|
||||
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mattn/go-sqlite3 v1.14.12
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.19
|
||||
github.com/microcosm-cc/bluemonday v1.0.20
|
||||
github.com/minio/minio-go/v7 v7.0.26
|
||||
github.com/msteinert/pam v1.0.0
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
@@ -90,12 +90,12 @@ require (
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
go.jolheiser.com/hcaptcha v0.0.4
|
||||
go.jolheiser.com/pwn v0.0.3
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/tools v0.1.12
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -271,9 +271,8 @@ require (
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
|
||||
37
go.sum
37
go.sum
@@ -69,8 +69,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EU
|
||||
contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
|
||||
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs=
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681 h1:MMSPgnVULVwV9kEBgvyEUhC9v/uviZ55hPJEMjpbNR4=
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
|
||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||
@@ -1146,8 +1146,8 @@ github.com/mholt/acmez v1.0.2 h1:C8wsEBIUVi6e0DYoxqCcFuXtwc4AWXL/jgcDjF7mjVo=
|
||||
github.com/mholt/acmez v1.0.2/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
@@ -1676,8 +1676,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1716,8 +1716,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1788,8 +1788,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1824,8 +1824,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1936,12 +1936,12 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1950,8 +1950,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -2045,16 +2046,14 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
||||
@@ -266,7 +266,7 @@ func TestPackageConan(t *testing.T) {
|
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.Equal(t, name, pd.Package.Name)
|
||||
assert.Equal(t, version1, pd.Version.Version)
|
||||
assert.IsType(t, &conan_module.Metadata{}, pd.Metadata)
|
||||
|
||||
@@ -6,10 +6,12 @@ package integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -433,6 +435,10 @@ func TestPackageContainer(t *testing.T) {
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length"))
|
||||
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
||||
|
||||
req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest))
|
||||
addTokenAuthHeader(req, anonymousToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("GetBlob", func(t *testing.T) {
|
||||
@@ -545,6 +551,32 @@ func TestPackageContainer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
t.Run("ParallelUpload", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
content := []byte{byte(i)}
|
||||
digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content))
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, digest), bytes.NewReader(content))
|
||||
addTokenAuthHeader(req, userToken)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
assert.Equal(t, digest, resp.Header().Get("Docker-Content-Digest"))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("OwnerNameChange", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -79,6 +80,18 @@ func TestPackageGeneric(t *testing.T) {
|
||||
assert.Equal(t, int64(1), pvs[0].DownloadCount)
|
||||
})
|
||||
|
||||
t.Run("RequireSignInView", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
setting.Service.RequireSignInView = true
|
||||
defer func() {
|
||||
setting.Service.RequireSignInView = false
|
||||
}()
|
||||
|
||||
req := NewRequest(t, "GET", url)
|
||||
MakeRequest(t, req, http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ func TestPackageNpm(t *testing.T) {
|
||||
packageTag2 := "release"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Test Description"
|
||||
packageBinName := "cli"
|
||||
packageBinPath := "./cli.sh"
|
||||
|
||||
data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
|
||||
upload := `{
|
||||
@@ -51,6 +53,9 @@ func TestPackageNpm(t *testing.T) {
|
||||
"author": {
|
||||
"name": "` + packageAuthor + `"
|
||||
},
|
||||
"bin": {
|
||||
"` + packageBinName + `": "` + packageBinPath + `"
|
||||
},
|
||||
"dist": {
|
||||
"integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
|
||||
"shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
|
||||
@@ -118,10 +123,16 @@ func TestPackageNpm(t *testing.T) {
|
||||
b, _ := base64.StdEncoding.DecodeString(data)
|
||||
assert.Equal(t, b, resp.Body.Bytes())
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/-/%s", root, filename))
|
||||
req = addTokenAuthHeader(req, token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, b, resp.Body.Bytes())
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pvs, 1)
|
||||
assert.Equal(t, int64(1), pvs[0].DownloadCount)
|
||||
assert.Equal(t, int64(2), pvs[0].DownloadCount)
|
||||
})
|
||||
|
||||
t.Run("PackageMetadata", func(t *testing.T) {
|
||||
@@ -150,6 +161,7 @@ func TestPackageNpm(t *testing.T) {
|
||||
assert.Equal(t, packageName, pmv.Name)
|
||||
assert.Equal(t, packageDescription, pmv.Description)
|
||||
assert.Equal(t, packageAuthor, pmv.Author.Name)
|
||||
assert.Equal(t, packageBinPath, pmv.Bin[packageBinName])
|
||||
assert.Equal(t, "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", pmv.Dist.Integrity)
|
||||
assert.Equal(t, "aaa7eaf852a948b0aa05afeda35b1badca155d90", pmv.Dist.Shasum)
|
||||
assert.Equal(t, fmt.Sprintf("%s%s/-/%s/%s", setting.AppURL, root[1:], packageVersion, filename), pmv.Dist.Tarball)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/packages/nuget"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -42,35 +44,59 @@ func TestPackageNuGet(t *testing.T) {
|
||||
symbolFilename := "test.pdb"
|
||||
symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
|
||||
|
||||
createPackage := func(id, version string) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
w, _ := archive.Create("package.nuspec")
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>` + packageName + `</id>
|
||||
<version>` + packageVersion + `</version>
|
||||
<id>` + id + `</id>
|
||||
<version>` + version + `</version>
|
||||
<authors>` + packageAuthors + `</authors>
|
||||
<description>` + packageDescription + `</description>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Microsoft.CSharp" version="4.5.0" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>`))
|
||||
archive.Close()
|
||||
content := buf.Bytes()
|
||||
return &buf
|
||||
}
|
||||
|
||||
content, _ := ioutil.ReadAll(createPackage(packageName, packageVersion))
|
||||
|
||||
url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
|
||||
|
||||
t.Run("ServiceIndex", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}).(*user_model.User)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
|
||||
cases := []struct {
|
||||
Owner string
|
||||
UseBasicAuth bool
|
||||
UseTokenAuth bool
|
||||
}{
|
||||
{privateUser.Name, false, false},
|
||||
{privateUser.Name, true, false},
|
||||
{privateUser.Name, false, true},
|
||||
{user.Name, false, false},
|
||||
{user.Name, true, false},
|
||||
{user.Name, false, true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
|
||||
if c.UseBasicAuth {
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
} else if c.UseTokenAuth {
|
||||
req = addNuGetAPIKeyHeader(req, token)
|
||||
}
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result nuget.ServiceIndexResponse
|
||||
@@ -100,6 +126,7 @@ func TestPackageNuGet(t *testing.T) {
|
||||
assert.Equal(t, root, r.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Upload", func(t *testing.T) {
|
||||
@@ -139,7 +166,7 @@ func TestPackageNuGet(t *testing.T) {
|
||||
t.Run("SymbolPackage", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
createPackage := func(id, packageType string) io.Reader {
|
||||
createSymbolPackage := func(id, packageType string) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
|
||||
@@ -165,15 +192,15 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
return &buf
|
||||
}
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage("unknown-package", "SymbolsPackage"))
|
||||
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage("unknown-package", "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "DummyPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "DummyPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
@@ -217,7 +244,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
}
|
||||
}
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
@@ -259,7 +286,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, symbolID, symbolFilename))
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFffff/%s", url, symbolFilename, symbolID, symbolFilename))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@@ -295,6 +322,43 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
|
||||
assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
|
||||
}
|
||||
|
||||
t.Run("EnforceGrouped", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", url, createPackage(packageName+".dummy", "1.0.0"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s", url, packageName))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result nuget.SearchResultResponse
|
||||
DecodeJSON(t, resp, &result)
|
||||
|
||||
assert.EqualValues(t, 3, result.TotalHits)
|
||||
assert.Len(t, result.Data, 2)
|
||||
for _, sr := range result.Data {
|
||||
if sr.ID == packageName {
|
||||
assert.Len(t, sr.Versions, 2)
|
||||
} else {
|
||||
assert.Len(t, sr.Versions, 1)
|
||||
}
|
||||
}
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName+".dummy", "1.0.0"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RegistrationService", func(t *testing.T) {
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestPackagePyPI(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
|
||||
packageName := "test-package"
|
||||
packageVersion := "1.0.1"
|
||||
packageVersion := "1!1.0.1+r1234"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Test Description"
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestPackagePyPI(t *testing.T) {
|
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
@@ -99,7 +99,7 @@ func TestPackagePyPI(t *testing.T) {
|
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
@@ -163,7 +163,7 @@ func TestPackagePyPI(t *testing.T) {
|
||||
nodes := htmlDoc.doc.Find("a").Nodes
|
||||
assert.Len(t, nodes, 2)
|
||||
|
||||
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, packageName, packageVersion, hashSHA256))
|
||||
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, regexp.QuoteMeta(packageName), regexp.QuoteMeta(packageVersion), hashSHA256))
|
||||
|
||||
for _, a := range nodes {
|
||||
for _, att := range a.Attr {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
func TestPackageAPI(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
@@ -143,6 +144,27 @@ func TestPackageAPI(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageAccess(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
|
||||
inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9}).(*user_model.User)
|
||||
|
||||
uploadPackage := func(doer, owner *user_model.User, expectedStatus int) {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/file.bin", owner.Name)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
|
||||
AddBasicAuthHeader(req, doer.Name)
|
||||
MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
uploadPackage(user, inactive, http.StatusUnauthorized)
|
||||
uploadPackage(inactive, inactive, http.StatusUnauthorized)
|
||||
uploadPackage(inactive, user, http.StatusUnauthorized)
|
||||
uploadPackage(admin, inactive, http.StatusCreated)
|
||||
uploadPackage(admin, user, http.StatusCreated)
|
||||
}
|
||||
|
||||
func TestPackageCleanup(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -430,19 +431,19 @@ var tokenCounter int64
|
||||
|
||||
func getTokenForLoggedInUser(t testing.TB, session *TestSession) string {
|
||||
t.Helper()
|
||||
tokenCounter++
|
||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
req = NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"name": fmt.Sprintf("api-testing-token-%d", tokenCounter),
|
||||
"name": fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)),
|
||||
})
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
req = NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
token := htmlDoc.doc.Find(".ui.info p").Text()
|
||||
assert.NotEmpty(t, token)
|
||||
return token
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
|
||||
reqOne := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)+"/status")
|
||||
testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state)
|
||||
|
||||
// By short SHA
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)[:10]+"/statuses")
|
||||
reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)[:10]+"/status")
|
||||
testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), session.MakeRequest(t, reqOne, http.StatusOK), state)
|
||||
|
||||
// By Ref
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/statuses")
|
||||
reqOne = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/status")
|
||||
|
||||
@@ -218,6 +218,11 @@ func (a *Action) GetRepoLink() string {
|
||||
return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName()))
|
||||
}
|
||||
|
||||
// GetRepoAbsoluteLink returns the absolute link to action repository.
|
||||
func (a *Action) GetRepoAbsoluteLink() string {
|
||||
return setting.AppURL + url.PathEscape(a.GetRepoUserName()) + "/" + url.PathEscape(a.GetRepoName())
|
||||
}
|
||||
|
||||
// GetRepositoryFromMatch returns a *repo_model.Repository from a username and repo strings
|
||||
func GetRepositoryFromMatch(ownerName, repoName string) (*repo_model.Repository, error) {
|
||||
var err error
|
||||
@@ -282,7 +287,7 @@ func (a *Action) GetRefLink() string {
|
||||
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
|
||||
case strings.HasPrefix(a.RefName, git.TagPrefix):
|
||||
return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix))
|
||||
case len(a.RefName) == 40 && git.IsValidSHAPattern(a.RefName):
|
||||
case len(a.RefName) == git.SHAFullLength && git.IsValidSHAPattern(a.RefName):
|
||||
return a.GetRepoLink() + "/src/commit/" + a.RefName
|
||||
default:
|
||||
// FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -19,7 +20,7 @@ import (
|
||||
|
||||
func TestAction_GetRepoPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}).(*repo_model.Repository)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
|
||||
action := &Action{RepoID: repo.ID}
|
||||
assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath())
|
||||
@@ -27,12 +28,15 @@ func TestAction_GetRepoPath(t *testing.T) {
|
||||
|
||||
func TestAction_GetRepoLink(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}).(*repo_model.Repository)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
|
||||
action := &Action{RepoID: repo.ID}
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{ID: 2}).(*issue_model.Comment)
|
||||
action := &Action{RepoID: repo.ID, CommentID: comment.ID}
|
||||
setting.AppSubURL = "/suburl"
|
||||
expected := path.Join(setting.AppSubURL, owner.Name, repo.Name)
|
||||
assert.Equal(t, expected, action.GetRepoLink())
|
||||
assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink())
|
||||
assert.Equal(t, comment.HTMLURL(), action.GetCommentLink())
|
||||
}
|
||||
|
||||
func TestGetFeeds(t *testing.T) {
|
||||
|
||||
@@ -65,11 +65,17 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) {
|
||||
|
||||
// PaddedKeyID show KeyID padded to 16 characters
|
||||
func (key *GPGKey) PaddedKeyID() string {
|
||||
if len(key.KeyID) > 15 {
|
||||
return key.KeyID
|
||||
return PaddedKeyID(key.KeyID)
|
||||
}
|
||||
|
||||
// PaddedKeyID show KeyID padded to 16 characters
|
||||
func PaddedKeyID(keyID string) string {
|
||||
if len(keyID) > 15 {
|
||||
return keyID
|
||||
}
|
||||
|
||||
zeros := "0000000000000000"
|
||||
return zeros[0:16-len(key.KeyID)] + key.KeyID
|
||||
return zeros[0:16-len(keyID)] + keyID
|
||||
}
|
||||
|
||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -56,6 +57,18 @@ func (app *OAuth2Application) PrimaryRedirectURI() string {
|
||||
|
||||
// ContainsRedirectURI checks if redirectURI is allowed for app
|
||||
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
|
||||
uri, err := url.Parse(redirectURI)
|
||||
// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
|
||||
if err == nil && uri.Scheme == "http" && uri.Port() != "" {
|
||||
ip := net.ParseIP(uri.Hostname())
|
||||
if ip != nil && ip.IsLoopback() {
|
||||
// strip port
|
||||
uri.Host = uri.Hostname()
|
||||
if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return util.IsStringInSlice(redirectURI, app.RedirectURIs, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,26 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
|
||||
assert.False(t, app.ContainsRedirectURI("d"))
|
||||
}
|
||||
|
||||
func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
|
||||
app := &OAuth2Application{
|
||||
RedirectURIs: []string{"http://127.0.0.1/", "http://::1/", "http://192.168.0.1/", "http://intranet/", "https://127.0.0.1/"},
|
||||
}
|
||||
|
||||
// http loopback uris should ignore port
|
||||
// https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
|
||||
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1:3456/"))
|
||||
assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
|
||||
assert.True(t, app.ContainsRedirectURI("http://[::1]:3456/"))
|
||||
|
||||
// not http
|
||||
assert.False(t, app.ContainsRedirectURI("https://127.0.0.1:3456/"))
|
||||
// not loopback
|
||||
assert.False(t, app.ContainsRedirectURI("http://192.168.0.1:9954/"))
|
||||
assert.False(t, app.ContainsRedirectURI("http://intranet:3456/"))
|
||||
// unparseable
|
||||
assert.False(t, app.ContainsRedirectURI(":"))
|
||||
}
|
||||
|
||||
func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
app := unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application)
|
||||
|
||||
@@ -19,8 +19,12 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultAvatarClass is the default class of a rendered avatar
|
||||
DefaultAvatarClass = "ui avatar vm"
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
const DefaultAvatarPixelSize = 28
|
||||
DefaultAvatarPixelSize = 28
|
||||
)
|
||||
|
||||
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
|
||||
type EmailHash struct {
|
||||
|
||||
@@ -291,6 +291,10 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
||||
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
|
||||
}
|
||||
|
||||
if _, err := git.NewIDFromString(opts.SHA); err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
|
||||
}
|
||||
|
||||
// Get the next Status Index
|
||||
idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
|
||||
if err != nil {
|
||||
|
||||
@@ -881,7 +881,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
||||
}
|
||||
}
|
||||
case CommentTypeReopen, CommentTypeClose:
|
||||
if err = updateIssueClosedNum(ctx, opts.Issue); err != nil {
|
||||
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,7 +722,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateIssueClosedNum(ctx, issue); err != nil {
|
||||
// update repository's issue closed number
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1006,12 +1007,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
||||
}
|
||||
}
|
||||
|
||||
if opts.IsPull {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
} else {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
}
|
||||
if err != nil {
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2104,15 +2100,6 @@ func (issue *Issue) BlockingDependencies(ctx context.Context) (issueDeps []*Depe
|
||||
return issueDeps, err
|
||||
}
|
||||
|
||||
func updateIssueClosedNum(ctx context.Context, issue *Issue) (err error) {
|
||||
if issue.IsPull {
|
||||
err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, true, "num_closed_pulls")
|
||||
} else {
|
||||
err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, false, "num_closed_issues")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
|
||||
func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(content)
|
||||
|
||||
@@ -181,6 +181,10 @@ func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
|
||||
Reaction: opts.Type,
|
||||
UserID: opts.DoerID,
|
||||
}
|
||||
if findOpts.CommentID == 0 {
|
||||
// explicit search of Issue Reactions where CommentID = 0
|
||||
findOpts.CommentID = -1
|
||||
}
|
||||
|
||||
existingR, _, err := FindReactions(ctx, findOpts)
|
||||
if err != nil {
|
||||
@@ -256,7 +260,13 @@ func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
|
||||
CommentID: opts.CommentID,
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Where("original_author_id = 0").Delete(reaction)
|
||||
sess := db.GetEngine(ctx).Where("original_author_id = 0")
|
||||
if opts.CommentID == -1 {
|
||||
reaction.CommentID = 0
|
||||
sess.MustCols("comment_id")
|
||||
}
|
||||
|
||||
_, err := sess.Delete(reaction)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -266,6 +276,7 @@ func DeleteIssueReaction(doerID, issueID int64, content string) error {
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
CommentID: -1,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
@@ -496,6 +497,13 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if git.DefaultContext == nil {
|
||||
if err = git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for i, m := range migrations[v-minDBVersion:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
|
||||
|
||||
@@ -448,8 +448,9 @@ func CountOrgs(opts FindOrgOptions) (int64, error) {
|
||||
|
||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||
// Not SignedUser
|
||||
if user == nil {
|
||||
// If user is nil, it's an anonymous user/request.
|
||||
// The Ghost user is handled like an anonymous user.
|
||||
if user == nil || user.IsGhost() {
|
||||
return orgOrUser.Visibility == structs.VisibleTypePublic
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ type FindReleasesOptions struct {
|
||||
IsPreRelease util.OptionalBool
|
||||
IsDraft util.OptionalBool
|
||||
TagNames []string
|
||||
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags
|
||||
}
|
||||
|
||||
func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
|
||||
@@ -191,6 +192,13 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
|
||||
if !opts.IsDraft.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
|
||||
}
|
||||
if !opts.HasSha1.IsNone() {
|
||||
if opts.HasSha1.IsTrue() {
|
||||
cond = cond.And(builder.Neq{"sha1": ""})
|
||||
} else {
|
||||
cond = cond.And(builder.Eq{"sha1": ""})
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -562,24 +562,19 @@ func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
|
||||
return repoStatsCorrectNum(ctx, id, false, "num_issues")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, false, false)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
|
||||
return repoStatsCorrectNum(ctx, id, true, "num_pulls")
|
||||
}
|
||||
|
||||
func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
|
||||
return err
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, false)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
|
||||
return repo_model.StatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, false, true)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
|
||||
return repo_model.StatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
|
||||
}
|
||||
|
||||
func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
|
||||
@@ -605,15 +600,27 @@ func CheckRepoStats(ctx context.Context) error {
|
||||
repoStatsCorrectNumStars,
|
||||
"repository count 'num_stars'",
|
||||
},
|
||||
// Repository.NumIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", false),
|
||||
repoStatsCorrectNumIssues,
|
||||
"repository count 'num_issues'",
|
||||
},
|
||||
// Repository.NumClosedIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false),
|
||||
repoStatsCorrectNumClosedIssues,
|
||||
"repository count 'num_closed_issues'",
|
||||
},
|
||||
// Repository.NumPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", true),
|
||||
repoStatsCorrectNumPulls,
|
||||
"repository count 'num_pulls'",
|
||||
},
|
||||
// Repository.NumClosedPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
|
||||
repoStatsCorrectNumClosedPulls,
|
||||
"repository count 'num_closed_pulls'",
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ type PushMirror struct {
|
||||
Repo *Repository `xorm:"-"`
|
||||
RemoteName string
|
||||
|
||||
SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
|
||||
Interval time.Duration
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrUserDoesNotHaveAccessToRepo represets an error where the user doesn't has access to a given repo.
|
||||
@@ -319,13 +321,7 @@ func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
|
||||
|
||||
// UnitEnabled if this repository has the given unit enabled
|
||||
func (repo *Repository) UnitEnabled(tp unit.Type) (result bool) {
|
||||
if err := db.WithContext(func(ctx *db.Context) error {
|
||||
result = repo.UnitEnabledCtx(ctx, tp)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error("repo.UnitEnabled: %v", err)
|
||||
}
|
||||
return
|
||||
return repo.UnitEnabledCtx(db.DefaultContext, tp)
|
||||
}
|
||||
|
||||
// UnitEnabled if this repository has the given unit enabled
|
||||
@@ -760,33 +756,28 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// StatsCorrectNumClosed update repository's issue related numbers
|
||||
func StatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error {
|
||||
_, err := db.Exec(ctx, "UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, isPull, id)
|
||||
return err
|
||||
// UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
|
||||
func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
|
||||
field := "num_"
|
||||
if isClosed {
|
||||
field += "closed_"
|
||||
}
|
||||
if isPull {
|
||||
field += "pulls"
|
||||
} else {
|
||||
field += "issues"
|
||||
}
|
||||
|
||||
// UpdateRepoIssueNumbers update repository issue numbers
|
||||
func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
|
||||
e := db.GetEngine(ctx)
|
||||
if isPull {
|
||||
if _, err := e.ID(repoID).Decr("num_pulls").Update(new(Repository)); err != nil {
|
||||
subQuery := builder.Select("count(*)").
|
||||
From("issue").Where(builder.Eq{
|
||||
"repo_id": repoID,
|
||||
"is_pull": isPull,
|
||||
}.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
|
||||
|
||||
// builder.Update(cond) will generate SQL like UPDATE ... SET cond
|
||||
query := builder.Update(builder.Eq{field: subQuery}).
|
||||
From("repository").
|
||||
Where(builder.Eq{"id": repoID})
|
||||
_, err := db.Exec(ctx, query)
|
||||
return err
|
||||
}
|
||||
if isClosed {
|
||||
if _, err := e.ID(repoID).Decr("num_closed_pulls").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := e.ID(repoID).Decr("num_issues").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
if isClosed {
|
||||
if _, err := e.ID(repoID).Decr("num_closed_issues").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
&user_model.Setting{UserID: u.ID},
|
||||
&pull_model.AutoMerge{DoerID: u.ID},
|
||||
&pull_model.ReviewState{UserID: u.ID},
|
||||
&user_model.Redirect{RedirectUserID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
@@ -1265,7 +1265,7 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond {
|
||||
|
||||
// IsUserVisibleToViewer check if viewer is able to see user profile
|
||||
func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
|
||||
if viewer != nil && viewer.IsAdmin {
|
||||
if viewer != nil && (viewer.IsAdmin || viewer.ID == u.ID) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1304,7 +1304,7 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
if count == 0 {
|
||||
// No common organization
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package appstate
|
||||
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
|
||||
type RuntimeState struct {
|
||||
LastAppPath string `json:"last_app_path"`
|
||||
LastCustomConf string `json:"last_custom_conf"`
|
||||
}
|
||||
|
||||
// Name returns the item name
|
||||
|
||||
@@ -59,6 +59,7 @@ func b1(img *image.Paletted, x, y, size, angle int) {
|
||||
}
|
||||
|
||||
// a small block
|
||||
//
|
||||
// ----------
|
||||
// | |
|
||||
// | #### |
|
||||
|
||||
@@ -388,7 +388,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) == 40 {
|
||||
} else if len(refName) == git.SHAFullLength {
|
||||
ctx.Repo.CommitID = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
@@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
|
||||
if statusPrefix == 4 || statusPrefix == 5 {
|
||||
log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
|
||||
}
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
ctx.Resp.WriteHeader(status)
|
||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
@@ -345,16 +346,45 @@ func (ctx *Context) RespHeader() http.Header {
|
||||
return ctx.Resp.Header()
|
||||
}
|
||||
|
||||
type ServeHeaderOptions struct {
|
||||
ContentType string // defaults to "application/octet-stream"
|
||||
ContentTypeCharset string
|
||||
Disposition string // defaults to "attachment"
|
||||
Filename string
|
||||
CacheDuration time.Duration // defaults to 5 minutes
|
||||
}
|
||||
|
||||
// SetServeHeaders sets necessary content serve headers
|
||||
func (ctx *Context) SetServeHeaders(filename string) {
|
||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||
header := ctx.Resp.Header()
|
||||
|
||||
contentType := typesniffer.ApplicationOctetStream
|
||||
if opts.ContentType != "" {
|
||||
if opts.ContentTypeCharset != "" {
|
||||
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
||||
} else {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
}
|
||||
header.Set("Content-Type", contentType)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
if opts.Filename != "" {
|
||||
disposition := opts.Disposition
|
||||
if disposition == "" {
|
||||
disposition = "attachment"
|
||||
}
|
||||
|
||||
backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
|
||||
header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
|
||||
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
}
|
||||
|
||||
duration := opts.CacheDuration
|
||||
if duration == 0 {
|
||||
duration = 5 * time.Minute
|
||||
}
|
||||
httpcache.AddCacheControlToHeader(header, duration)
|
||||
}
|
||||
|
||||
// ServeContent serves content to http request
|
||||
@@ -366,7 +396,9 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
|
||||
modTime = v
|
||||
}
|
||||
}
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
|
||||
}
|
||||
|
||||
@@ -378,13 +410,17 @@ func (ctx *Context) ServeFile(file string, names ...string) {
|
||||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
http.ServeFile(ctx.Resp, ctx.Req, file)
|
||||
}
|
||||
|
||||
// ServeStream serves file via io stream
|
||||
func (ctx *Context) ServeStream(rd io.Reader, name string) {
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
_, err := io.Copy(ctx.Resp, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Download file failed", err)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
@@ -52,48 +53,12 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
|
||||
Owner: ctx.ContextUser,
|
||||
}
|
||||
|
||||
if ctx.Package.Owner.IsOrganization() {
|
||||
org := organization.OrgFromUser(ctx.Package.Owner)
|
||||
|
||||
// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
|
||||
if ctx.Doer != nil {
|
||||
var err error
|
||||
ctx.Package.AccessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
|
||||
ctx.Package.AccessMode, err = determineAccessMode(ctx)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
|
||||
errCb(http.StatusInternalServerError, "determineAccessMode", err)
|
||||
return
|
||||
}
|
||||
// If access mode is less than write check every team for more permissions
|
||||
if ctx.Package.AccessMode < perm.AccessModeWrite {
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, "GetUserOrgTeams", err)
|
||||
return
|
||||
}
|
||||
for _, t := range teams {
|
||||
perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
|
||||
if ctx.Package.AccessMode < perm {
|
||||
ctx.Package.AccessMode = perm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. If authorize level is none, check if org is visible to user
|
||||
if ctx.Package.AccessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
|
||||
ctx.Package.AccessMode = perm.AccessModeRead
|
||||
}
|
||||
} else {
|
||||
if ctx.Doer != nil && !ctx.Doer.IsGhost() {
|
||||
// 1. Check if user is package owner
|
||||
if ctx.Doer.ID == ctx.Package.Owner.ID {
|
||||
ctx.Package.AccessMode = perm.AccessModeOwner
|
||||
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
|
||||
ctx.Package.AccessMode = perm.AccessModeRead
|
||||
}
|
||||
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
|
||||
ctx.Package.AccessMode = perm.AccessModeRead
|
||||
}
|
||||
}
|
||||
|
||||
packageType := ctx.Params("type")
|
||||
name := ctx.Params("name")
|
||||
@@ -117,6 +82,60 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
|
||||
}
|
||||
}
|
||||
|
||||
func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
|
||||
if setting.Service.RequireSignInView && ctx.Doer == nil {
|
||||
return perm.AccessModeNone, nil
|
||||
}
|
||||
|
||||
if ctx.Doer != nil && !ctx.Doer.IsGhost() && (!ctx.Doer.IsActive || ctx.Doer.ProhibitLogin) {
|
||||
return perm.AccessModeNone, nil
|
||||
}
|
||||
|
||||
accessMode := perm.AccessModeNone
|
||||
if ctx.Package.Owner.IsOrganization() {
|
||||
org := organization.OrgFromUser(ctx.Package.Owner)
|
||||
|
||||
// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
|
||||
if ctx.Doer != nil {
|
||||
var err error
|
||||
accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return accessMode, err
|
||||
}
|
||||
// If access mode is less than write check every team for more permissions
|
||||
if accessMode < perm.AccessModeWrite {
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
return accessMode, err
|
||||
}
|
||||
for _, t := range teams {
|
||||
perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
|
||||
if accessMode < perm {
|
||||
accessMode = perm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. If authorize level is none, check if org is visible to user
|
||||
if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
|
||||
accessMode = perm.AccessModeRead
|
||||
}
|
||||
} else {
|
||||
if ctx.Doer != nil && !ctx.Doer.IsGhost() {
|
||||
// 1. Check if user is package owner
|
||||
if ctx.Doer.ID == ctx.Package.Owner.ID {
|
||||
accessMode = perm.AccessModeOwner
|
||||
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
|
||||
accessMode = perm.AccessModeRead
|
||||
}
|
||||
} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
|
||||
accessMode = perm.AccessModeRead
|
||||
}
|
||||
}
|
||||
|
||||
return accessMode, nil
|
||||
}
|
||||
|
||||
// PackageContexter initializes a package context for a request.
|
||||
func PackageContexter() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
||||
@@ -524,7 +524,9 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
||||
}
|
||||
|
||||
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
@@ -805,7 +807,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
||||
}
|
||||
// For legacy and API support only full commit sha
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) == 40 {
|
||||
if len(parts) > 0 && len(parts[0]) == git.SHAFullLength {
|
||||
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@@ -841,7 +843,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
||||
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
|
||||
case RepoRefCommit:
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 {
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength {
|
||||
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@@ -950,7 +952,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= 40 {
|
||||
} else if len(refName) >= 7 && len(refName) <= git.SHAFullLength {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
@@ -960,7 +962,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < 40 {
|
||||
if len(refName) < git.SHAFullLength {
|
||||
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ func TestGuessDelimiter(t *testing.T) {
|
||||
},
|
||||
// case 3 - tab delimited
|
||||
{
|
||||
csv: "1 2",
|
||||
csv: "1\t2",
|
||||
expectedDelimiter: '\t',
|
||||
},
|
||||
// case 4 - pipe delimited
|
||||
|
||||
@@ -205,6 +205,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
||||
// find stopwatches without existing issue
|
||||
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
|
||||
"stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
|
||||
// find redirects without existing user.
|
||||
genericOrphanCheck("Orphaned Redirects without existing redirect user",
|
||||
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
|
||||
)
|
||||
|
||||
for _, c := range consistencyChecks {
|
||||
|
||||
89
modules/doctor/heads.go
Normal file
89
modules/doctor/heads.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||
numRepos := 0
|
||||
numHeadsBroken := 0
|
||||
numDefaultBranchesBroken := 0
|
||||
numReposUpdated := 0
|
||||
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||
numRepos++
|
||||
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse", "--", repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
// what we expect: default branch is valid, and HEAD points to it
|
||||
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
|
||||
return nil
|
||||
}
|
||||
|
||||
if headErr != nil {
|
||||
numHeadsBroken++
|
||||
}
|
||||
if defaultBranchErr != nil {
|
||||
numDefaultBranchesBroken++
|
||||
}
|
||||
|
||||
// if default branch is broken, let the user fix that in the UI
|
||||
if defaultBranchErr != nil {
|
||||
logger.Warn("Default branch for %s/%s doesn't point to a valid commit", repo.OwnerName, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we're not autofixing, that's all we can do
|
||||
if !autofix {
|
||||
return nil
|
||||
}
|
||||
|
||||
// otherwise, let's try fixing HEAD
|
||||
err := git.NewCommand(ctx, "symbolic-ref", "--", "HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
if err != nil {
|
||||
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
||||
return nil
|
||||
}
|
||||
numReposUpdated++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Critical("Error when fixing repo HEADs: %v", err)
|
||||
}
|
||||
|
||||
if autofix {
|
||||
logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Info("All %d repos have their HEADs in the correct state", numRepos)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
||||
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
||||
} else if numHeadsBroken != 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Warn("HEADs are broken for %d/%d repos", numHeadsBroken, numRepos)
|
||||
} else {
|
||||
logger.Critical("Out of %d repos, HEADS are broken for %d and default branches are broken for %d", numRepos, numHeadsBroken, numDefaultBranchesBroken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&Check{
|
||||
Title: "Synchronize repo HEADs",
|
||||
Name: "synchronize-repo-heads",
|
||||
IsDefault: true,
|
||||
Run: synchronizeRepoHeads,
|
||||
Priority: 7,
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@ type Command struct {
|
||||
parentContext context.Context
|
||||
desc string
|
||||
globalArgsLength int
|
||||
brokenArgs []string
|
||||
}
|
||||
|
||||
func (c *Command) String() string {
|
||||
@@ -50,6 +51,7 @@ func (c *Command) String() string {
|
||||
}
|
||||
|
||||
// NewCommand creates and returns a new Git Command based on given command and arguments.
|
||||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
|
||||
func NewCommand(ctx context.Context, args ...string) *Command {
|
||||
// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
|
||||
cargs := make([]string, len(globalCommandArgs))
|
||||
@@ -63,11 +65,13 @@ func NewCommand(ctx context.Context, args ...string) *Command {
|
||||
}
|
||||
|
||||
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
|
||||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
|
||||
func NewCommandNoGlobals(args ...string) *Command {
|
||||
return NewCommandContextNoGlobals(DefaultContext, args...)
|
||||
}
|
||||
|
||||
// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
|
||||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
|
||||
func NewCommandContextNoGlobals(ctx context.Context, args ...string) *Command {
|
||||
return &Command{
|
||||
name: GitExecutable,
|
||||
@@ -89,12 +93,28 @@ func (c *Command) SetDescription(desc string) *Command {
|
||||
return c
|
||||
}
|
||||
|
||||
// AddArguments adds new argument(s) to the command.
|
||||
// AddArguments adds new argument(s) to the command. Each argument must be safe to be trusted.
|
||||
// User-provided arguments should be passed to AddDynamicArguments instead.
|
||||
func (c *Command) AddArguments(args ...string) *Command {
|
||||
c.args = append(c.args, args...)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddDynamicArguments adds new dynamic argument(s) to the command.
|
||||
// The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options
|
||||
func (c *Command) AddDynamicArguments(args ...string) *Command {
|
||||
for _, arg := range args {
|
||||
if arg != "" && arg[0] == '-' {
|
||||
c.brokenArgs = append(c.brokenArgs, arg)
|
||||
}
|
||||
}
|
||||
if len(c.brokenArgs) != 0 {
|
||||
return c
|
||||
}
|
||||
c.args = append(c.args, args...)
|
||||
return c
|
||||
}
|
||||
|
||||
// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
|
||||
type RunOpts struct {
|
||||
Env []string
|
||||
@@ -138,13 +158,22 @@ func CommonCmdServEnvs() []string {
|
||||
return commonBaseEnvs()
|
||||
}
|
||||
|
||||
var ErrBrokenCommand = errors.New("git command is broken")
|
||||
|
||||
// Run runs the command with the RunOpts
|
||||
func (c *Command) Run(opts *RunOpts) error {
|
||||
if len(c.brokenArgs) != 0 {
|
||||
log.Error("git command is broken: %s, broken args: %s", c.String(), strings.Join(c.brokenArgs, " "))
|
||||
return ErrBrokenCommand
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &RunOpts{}
|
||||
}
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = defaultCommandExecutionTimeout
|
||||
|
||||
// We must not change the provided options
|
||||
timeout := opts.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = defaultCommandExecutionTimeout
|
||||
}
|
||||
|
||||
if len(opts.Dir) == 0 {
|
||||
@@ -179,7 +208,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
||||
if opts.UseContextTimeout {
|
||||
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
||||
} else {
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
|
||||
}
|
||||
defer finished()
|
||||
|
||||
@@ -280,9 +309,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
|
||||
}
|
||||
stdoutBuf := &bytes.Buffer{}
|
||||
stderrBuf := &bytes.Buffer{}
|
||||
opts.Stdout = stdoutBuf
|
||||
opts.Stderr = stderrBuf
|
||||
err := c.Run(opts)
|
||||
|
||||
// We must not change the provided options as it could break future calls - therefore make a copy.
|
||||
newOpts := &RunOpts{
|
||||
Env: opts.Env,
|
||||
Timeout: opts.Timeout,
|
||||
UseContextTimeout: opts.UseContextTimeout,
|
||||
Dir: opts.Dir,
|
||||
Stdout: stdoutBuf,
|
||||
Stderr: stderrBuf,
|
||||
Stdin: opts.Stdin,
|
||||
PipelineFunc: opts.PipelineFunc,
|
||||
}
|
||||
|
||||
err := c.Run(newOpts)
|
||||
stderr = stderrBuf.Bytes()
|
||||
if err != nil {
|
||||
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
|
||||
|
||||
@@ -26,4 +26,19 @@ func TestRunWithContextStd(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "exit status 129 - unknown option:")
|
||||
assert.Empty(t, stdout)
|
||||
}
|
||||
|
||||
cmd = NewCommand(context.Background())
|
||||
cmd.AddDynamicArguments("-test")
|
||||
assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
|
||||
|
||||
cmd = NewCommand(context.Background())
|
||||
cmd.AddDynamicArguments("--test")
|
||||
assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
|
||||
|
||||
subCmd := "version"
|
||||
cmd = NewCommand(context.Background()).AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production
|
||||
stdout, stderr, err = cmd.RunStdString(&RunOpts{})
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, stderr)
|
||||
assert.Contains(t, stdout, "git version")
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
|
||||
// CommitsCountFiles returns number of total commits of until given revision.
|
||||
func CommitsCountFiles(ctx context.Context, repoPath string, revision, relpath []string) (int64, error) {
|
||||
cmd := NewCommand(ctx, "rev-list", "--count")
|
||||
cmd.AddArguments(revision...)
|
||||
cmd.AddDynamicArguments(revision...)
|
||||
if len(relpath) > 0 {
|
||||
cmd.AddArguments("--")
|
||||
cmd.AddArguments(relpath...)
|
||||
|
||||
@@ -69,7 +69,6 @@ func NewParser(r io.Reader, format Format) *Parser {
|
||||
// It could, for example return something like:
|
||||
//
|
||||
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
||||
//
|
||||
func (p *Parser) Next() map[string]string {
|
||||
if !p.scanner.Scan() {
|
||||
return nil
|
||||
@@ -90,7 +89,6 @@ func (p *Parser) Err() error {
|
||||
// parseRef parses out all key-value pairs from a single reference block, such as
|
||||
//
|
||||
// "objecttype tag\0refname:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27"
|
||||
//
|
||||
func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
|
||||
if refBlock == "" {
|
||||
// must be at EOF
|
||||
|
||||
@@ -190,11 +190,6 @@ func InitOnceWithSync(ctx context.Context) (err error) {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
|
||||
}
|
||||
|
||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||
if CheckGitVersionAtLeast("2.9") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||
|
||||
@@ -44,7 +44,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
case "160000":
|
||||
entry.entryMode = EntryModeCommit
|
||||
pos += 14 // skip over "160000 object "
|
||||
case "040000":
|
||||
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
|
||||
entry.entryMode = EntryModeTree
|
||||
pos += 12 // skip over "040000 tree "
|
||||
default:
|
||||
@@ -119,7 +119,7 @@ loop:
|
||||
entry.entryMode = EntryModeSymlink
|
||||
case "160000":
|
||||
entry.entryMode = EntryModeCommit
|
||||
case "40000":
|
||||
case "40000", "40755": // git uses 40000 for tree object, but some users may get 40755 for unknown reasons
|
||||
entry.entryMode = EntryModeTree
|
||||
default:
|
||||
log.Debug("Unknown mode: %v", string(mode))
|
||||
|
||||
@@ -191,8 +191,8 @@ func (c *CheckAttributeReader) Run() error {
|
||||
// CheckPath check attr for given path
|
||||
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Error("CheckPath returns error: %v", err)
|
||||
if err != nil && err != c.ctx.Err() {
|
||||
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
|
||||
|
||||
// IsBranchExist returns true if given branch exists in current repository.
|
||||
func (repo *Repository) IsBranchExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
|
||||
// add previous arguments except for --grep and --all
|
||||
hashCmd.AddArguments(args...)
|
||||
// add keyword as <commit>
|
||||
hashCmd.AddArguments(v)
|
||||
hashCmd.AddDynamicArguments(v)
|
||||
|
||||
// search with given constraints for commit matching sha hash of v
|
||||
hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path})
|
||||
@@ -208,10 +208,11 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
|
||||
}()
|
||||
go func() {
|
||||
stderr := strings.Builder{}
|
||||
err := NewCommand(repo.Ctx, "log", revision, "--follow",
|
||||
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
|
||||
prettyLogFormat, "--", file).
|
||||
Run(&RunOpts{
|
||||
gitCmd := NewCommand(repo.Ctx, "log", prettyLogFormat, "--follow",
|
||||
"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page))
|
||||
gitCmd.AddDynamicArguments(revision)
|
||||
gitCmd.AddArguments("--", file)
|
||||
err := gitCmd.Run(&RunOpts{
|
||||
Dir: repo.Path,
|
||||
Stdout: stdoutWriter,
|
||||
Stderr: &stderr,
|
||||
|
||||
@@ -42,7 +42,7 @@ func (repo *Repository) RemoveReference(name string) error {
|
||||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == 40 {
|
||||
if len(commitID) == SHAFullLength {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
|
||||
@@ -138,7 +138,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
||||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == 40 && IsValidSHAPattern(commitID) {
|
||||
if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
// ReadTreeToIndex reads a treeish to the index
|
||||
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
|
||||
if len(treeish) != 40 {
|
||||
if len(treeish) != SHAFullLength {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -59,15 +59,15 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
|
||||
args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
|
||||
gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", fmt.Sprintf("--since='%s'", since))
|
||||
if len(branch) == 0 {
|
||||
args = append(args, "--branches=*")
|
||||
gitCmd.AddArguments("--branches=*")
|
||||
} else {
|
||||
args = append(args, "--first-parent", branch)
|
||||
gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
|
||||
}
|
||||
|
||||
stderr := new(strings.Builder)
|
||||
err = NewCommand(repo.Ctx, args...).Run(&RunOpts{
|
||||
err = gitCmd.Run(&RunOpts{
|
||||
Env: []string{},
|
||||
Dir: repo.Path,
|
||||
Stdout: stdoutWriter,
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// IsTagExist returns true if given tag exists in the repository.
|
||||
func (repo *Repository) IsTagExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != 40 {
|
||||
if len(idStr) != SHAFullLength {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", idStr).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -67,7 +67,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
||||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != 40 {
|
||||
if len(idStr) != SHAFullLength {
|
||||
res, err := repo.GetRefCommitID(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -18,6 +18,9 @@ const EmptySHA = "0000000000000000000000000000000000000000"
|
||||
// EmptyTreeSHA is the SHA of an empty tree
|
||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||
|
||||
// SHAFullLength is the full length of a git SHA
|
||||
const SHAFullLength = 40
|
||||
|
||||
// SHAPattern can be used to determine if a string is an valid sha
|
||||
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
|
||||
|
||||
@@ -51,7 +54,7 @@ func MustIDFromString(s string) SHA1 {
|
||||
func NewIDFromString(s string) (SHA1, error) {
|
||||
var id SHA1
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) != 40 {
|
||||
if len(s) != SHAFullLength {
|
||||
return id, fmt.Errorf("Length must be 40: %s", s)
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
|
||||
@@ -10,6 +10,7 @@ package git
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
@@ -19,8 +20,10 @@ import (
|
||||
type Signature = object.Signature
|
||||
|
||||
// Helper to get a signature from the commit line, which looks like these:
|
||||
//
|
||||
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
|
||||
// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
|
||||
//
|
||||
// but without the "author " at the beginning (this method should)
|
||||
// be used for author and committer.
|
||||
//
|
||||
@@ -28,7 +31,9 @@ type Signature = object.Signature
|
||||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
||||
sig := new(Signature)
|
||||
emailStart := bytes.IndexByte(line, '<')
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
emailEnd := bytes.IndexByte(line, '>')
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -37,8 +38,10 @@ func (s *Signature) Decode(b []byte) {
|
||||
}
|
||||
|
||||
// Helper to get a signature from the commit line, which looks like these:
|
||||
//
|
||||
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
|
||||
// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
|
||||
//
|
||||
// but without the "author " at the beginning (this method should)
|
||||
// be used for author and committer.
|
||||
func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
|
||||
@@ -49,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
hasTime := emailEnd+2 < len(line)
|
||||
|
||||
@@ -24,19 +24,17 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
page = 1
|
||||
}
|
||||
|
||||
args := make([]string, 0, 12+len(branches)+len(files))
|
||||
|
||||
args = append(args, "--graph", "--date-order", "--decorate=full")
|
||||
graphCmd := git.NewCommand(r.Ctx, "log", "--graph", "--date-order", "--decorate=full")
|
||||
|
||||
if hidePRRefs {
|
||||
args = append(args, "--exclude="+git.PullPrefix+"*")
|
||||
graphCmd.AddArguments("--exclude=" + git.PullPrefix + "*")
|
||||
}
|
||||
|
||||
if len(branches) == 0 {
|
||||
args = append(args, "--all")
|
||||
graphCmd.AddArguments("--all")
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
graphCmd.AddArguments(
|
||||
"-C",
|
||||
"-M",
|
||||
fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
|
||||
@@ -44,15 +42,12 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
fmt.Sprintf("--pretty=format:%s", format))
|
||||
|
||||
if len(branches) > 0 {
|
||||
args = append(args, branches...)
|
||||
graphCmd.AddDynamicArguments(branches...)
|
||||
}
|
||||
args = append(args, "--")
|
||||
if len(files) > 0 {
|
||||
args = append(args, files...)
|
||||
graphCmd.AddArguments("--")
|
||||
graphCmd.AddArguments(files...)
|
||||
}
|
||||
|
||||
graphCmd := git.NewCommand(r.Ctx, "log")
|
||||
graphCmd.AddArguments(args...)
|
||||
graph := NewGraph()
|
||||
|
||||
stderr := new(strings.Builder)
|
||||
|
||||
@@ -93,6 +93,7 @@ func NewFileLogger() LoggerProvider {
|
||||
|
||||
// Init file logger with json config.
|
||||
// config like:
|
||||
//
|
||||
// {
|
||||
// "filename":"log/gogs.log",
|
||||
// "maxsize":1<<30,
|
||||
|
||||
@@ -48,6 +48,7 @@ func NewSMTPLogger() LoggerProvider {
|
||||
|
||||
// Init smtp writer with json config.
|
||||
// config like:
|
||||
//
|
||||
// {
|
||||
// "Username":"example@gmail.com",
|
||||
// "password:"password",
|
||||
|
||||
@@ -7,6 +7,7 @@ package markup_test
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -32,6 +33,7 @@ func TestMain(m *testing.M) {
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRender_Commits(t *testing.T) {
|
||||
@@ -336,7 +338,7 @@ func TestRender_emoji(t *testing.T) {
|
||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
|
||||
test(
|
||||
"😎🤪🔐🤑❓",
|
||||
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
|
||||
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
|
||||
|
||||
// should match nothing
|
||||
test(
|
||||
|
||||
@@ -6,6 +6,7 @@ package markdown_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -37,6 +38,7 @@ func TestMain(m *testing.M) {
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
|
||||
@@ -613,6 +613,7 @@ func (m *webhookNotifier) NotifyPushCommits(pusher *user_model.User, repo *repo_
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
TotalCommits: commits.Len,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
@@ -843,6 +844,7 @@ func (m *webhookNotifier) NotifySyncPushCommits(pusher *user_model.User, repo *r
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
TotalCommits: commits.Len,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
|
||||
@@ -95,8 +95,10 @@ func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
|
||||
if i := strings.Index(cmd, "#(nop) "); i != -1 {
|
||||
cmd = strings.TrimSpace(cmd[i+7:])
|
||||
}
|
||||
if cmd != "" {
|
||||
imageLayers = append(imageLayers, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
metadata := &Metadata{
|
||||
Type: TypeOCI,
|
||||
|
||||
@@ -30,6 +30,13 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
||||
return s.store.Open(KeyToRelativePath(key))
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func (s *ContentStore) Has(key BlobHash256Key) error {
|
||||
_, err := s.store.Stat(KeyToRelativePath(key))
|
||||
return err
|
||||
}
|
||||
|
||||
// Save stores a package blob
|
||||
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
|
||||
_, err := s.store.Save(KeyToRelativePath(key), r, size)
|
||||
|
||||
@@ -66,7 +66,8 @@ type PackageMetadata struct {
|
||||
License string `json:"license,omitempty"`
|
||||
}
|
||||
|
||||
// PackageMetadataVersion https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion response: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
|
||||
type PackageMetadataVersion struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
@@ -80,6 +81,7 @@ type PackageMetadataVersion struct {
|
||||
Dependencies map[string]string `json:"dependencies,omitempty"`
|
||||
DevDependencies map[string]string `json:"devDependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
Dist PackageDistribution `json:"dist"`
|
||||
@@ -192,6 +194,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
DevelopmentDependencies: meta.DevDependencies,
|
||||
PeerDependencies: meta.PeerDependencies,
|
||||
OptionalDependencies: meta.OptionalDependencies,
|
||||
Bin: meta.Bin,
|
||||
Readme: meta.Readme,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ func TestParsePackage(t *testing.T) {
|
||||
packageVersion := "1.0.1-pre"
|
||||
packageTag := "latest"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageBin := "gitea"
|
||||
packageDescription := "Test Description"
|
||||
data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
|
||||
integrity := "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg=="
|
||||
@@ -236,6 +237,9 @@ func TestParsePackage(t *testing.T) {
|
||||
Dependencies: map[string]string{
|
||||
"package": "1.2.0",
|
||||
},
|
||||
Bin: map[string]string{
|
||||
"bin": packageBin,
|
||||
},
|
||||
Dist: PackageDistribution{
|
||||
Integrity: integrity,
|
||||
},
|
||||
@@ -264,6 +268,7 @@ func TestParsePackage(t *testing.T) {
|
||||
assert.Equal(t, packageDescription, p.Metadata.Description)
|
||||
assert.Equal(t, packageDescription, p.Metadata.Readme)
|
||||
assert.Equal(t, packageAuthor, p.Metadata.Author)
|
||||
assert.Equal(t, packageBin, p.Metadata.Bin["bin"])
|
||||
assert.Equal(t, "MIT", p.Metadata.License)
|
||||
assert.Equal(t, "https://gitea.io/", p.Metadata.ProjectURL)
|
||||
assert.Contains(t, p.Metadata.Dependencies, "package")
|
||||
|
||||
@@ -20,5 +20,6 @@ type Metadata struct {
|
||||
DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peer_dependencies,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ package nuget
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -181,7 +183,23 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
||||
return &Package{
|
||||
PackageType: packageType,
|
||||
ID: p.Metadata.ID,
|
||||
Version: v.String(),
|
||||
Version: toNormalizedVersion(v),
|
||||
Metadata: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
|
||||
// https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
|
||||
func toNormalizedVersion(v *version.Version) string {
|
||||
var buf bytes.Buffer
|
||||
segments := v.Segments64()
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
if len(segments) > 3 && segments[3] > 0 {
|
||||
fmt.Fprintf(&buf, ".%d", segments[3])
|
||||
}
|
||||
pre := v.Prerelease()
|
||||
if pre != "" {
|
||||
fmt.Fprint(&buf, "-", pre)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -147,6 +147,19 @@ func TestParseNuspecMetaData(t *testing.T) {
|
||||
assert.Len(t, deps, 1)
|
||||
assert.Equal(t, dependencyID, deps[0].ID)
|
||||
assert.Equal(t, dependencyVersion, deps[0].Version)
|
||||
|
||||
t.Run("NormalizedVersion", func(t *testing.T) {
|
||||
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>test</id>
|
||||
<version>1.04.5.2.5-rc.1+metadata</version>
|
||||
</metadata>
|
||||
</package>`))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, np)
|
||||
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Symbols Package", func(t *testing.T) {
|
||||
|
||||
@@ -25,7 +25,7 @@ func DumpMemProfileForUsername(pprofDataPath, username string) error {
|
||||
}
|
||||
|
||||
// DumpCPUProfileForUsername dumps a CPU profile at pprofDataPath as cpuprofile_<username>_<temporary id>
|
||||
// it returns the stop function which stops, writes and closes the CPU profile file
|
||||
// the stop function it returns stops, writes and closes the CPU profile file
|
||||
func DumpCPUProfileForUsername(pprofDataPath, username string) (func(), error) {
|
||||
f, err := os.CreateTemp(pprofDataPath, fmt.Sprintf("cpuprofile_%s_", username))
|
||||
if err != nil {
|
||||
|
||||
@@ -272,6 +272,7 @@ type PushPayload struct {
|
||||
After string `json:"after"`
|
||||
CompareURL string `json:"compare_url"`
|
||||
Commits []*PayloadCommit `json:"commits"`
|
||||
TotalCommits int `json:"total_commits"`
|
||||
HeadCommit *PayloadCommit `json:"head_commit"`
|
||||
Repo *Repository `json:"repository"`
|
||||
Pusher *User `json:"pusher"`
|
||||
|
||||
@@ -154,6 +154,7 @@ func NewFuncMap() []template.FuncMap {
|
||||
"DiffTypeToStr": DiffTypeToStr,
|
||||
"DiffLineTypeToStr": DiffLineTypeToStr,
|
||||
"ShortSha": base.ShortSha,
|
||||
"MD5": base.EncodeMD5,
|
||||
"ActionContent2Commits": ActionContent2Commits,
|
||||
"PathEscape": url.PathEscape,
|
||||
"PathEscapeSegments": util.PathEscapeSegments,
|
||||
@@ -457,6 +458,19 @@ func NewFuncMap() []template.FuncMap {
|
||||
return items
|
||||
},
|
||||
"HasPrefix": strings.HasPrefix,
|
||||
"CompareLink": func(baseRepo, repo *repo_model.Repository, branchName string) string {
|
||||
var curBranch string
|
||||
if repo.ID != baseRepo.ID {
|
||||
curBranch += fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
|
||||
}
|
||||
curBranch += util.PathEscapeSegments(branchName)
|
||||
|
||||
return fmt.Sprintf("%s/compare/%s...%s",
|
||||
baseRepo.Link(),
|
||||
util.PathEscapeSegments(baseRepo.DefaultBranch),
|
||||
curBranch,
|
||||
)
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -633,7 +647,7 @@ func SVG(icon string, others ...interface{}) template.HTML {
|
||||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
func Avatar(item interface{}, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
switch t := item.(type) {
|
||||
case *user_model.User:
|
||||
@@ -664,7 +678,7 @@ func AvatarByAction(action *models.Action, others ...interface{}) template.HTML
|
||||
|
||||
// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
|
||||
func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
src := repo.RelAvatarLink()
|
||||
if src != "" {
|
||||
@@ -675,7 +689,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
|
||||
|
||||
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
|
||||
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
|
||||
|
||||
if src != "" {
|
||||
|
||||
@@ -13,8 +13,13 @@ import (
|
||||
// TimeStamp defines a timestamp
|
||||
type TimeStamp int64
|
||||
|
||||
var (
|
||||
// mock is NOT concurrency-safe!!
|
||||
var mock time.Time
|
||||
mock time.Time
|
||||
|
||||
// Used for IsZero, to check if timestamp is the zero time instant.
|
||||
timeZeroUnix = time.Time{}.Unix()
|
||||
)
|
||||
|
||||
// Set sets the time to a mocked time.Time
|
||||
func Set(now time.Time) {
|
||||
@@ -103,5 +108,5 @@ func (ts TimeStamp) FormatDate() string {
|
||||
|
||||
// IsZero is zero time
|
||||
func (ts TimeStamp) IsZero() bool {
|
||||
return ts.AsTimeInLocation(time.Local).IsZero()
|
||||
return int64(ts) == 0 || int64(ts) == timeZeroUnix
|
||||
}
|
||||
|
||||
@@ -41,14 +41,14 @@ func NewLocaleStore() *LocaleStore {
|
||||
}
|
||||
|
||||
// AddLocaleByIni adds locale by ini into the store
|
||||
func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error {
|
||||
func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error {
|
||||
if _, ok := ls.localeMap[langName]; ok {
|
||||
return ErrLocaleAlreadyExist
|
||||
}
|
||||
iniFile, err := ini.LoadSources(ini.LoadOptions{
|
||||
IgnoreInlineComment: true,
|
||||
UnescapeValueCommentSymbols: true,
|
||||
}, localeFile, otherLocaleFiles...)
|
||||
}, source, moreSource)
|
||||
if err == nil {
|
||||
iniFile.BlockMode = false
|
||||
lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Tr(t *testing.T) {
|
||||
func TestLocaleStore(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
.dot.name = Dot Name
|
||||
fmt = %[1]s %[2]s
|
||||
@@ -28,8 +28,8 @@ sub = Changed Sub String
|
||||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1))
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2))
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, nil))
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
|
||||
ls.SetDefaultLang("lang1")
|
||||
|
||||
result := ls.Tr("lang1", "fmt", "a", "b")
|
||||
@@ -54,3 +54,21 @@ sub = Changed Sub String
|
||||
assert.Equal(t, []string{"lang1", "lang2"}, langs)
|
||||
assert.Equal(t, []string{"Lang1", "Lang2"}, descs)
|
||||
}
|
||||
|
||||
func TestLocaleStoreMoreSource(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
a=11
|
||||
b=12
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
b=21
|
||||
c=22
|
||||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
|
||||
assert.Equal(t, "11", ls.Tr("lang1", "a"))
|
||||
assert.Equal(t, "21", ls.Tr("lang1", "b"))
|
||||
assert.Equal(t, "22", ls.Tr("lang1", "c"))
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ func InitLocales() {
|
||||
log.Fatal("Failed to list locale files: %v", err)
|
||||
}
|
||||
|
||||
localFiles := make(map[string][]byte, len(localeNames))
|
||||
localeData := make(map[string][]byte, len(localeNames))
|
||||
for _, name := range localeNames {
|
||||
localFiles[name], err = options.Locale(name)
|
||||
localeData[name], err = options.Locale(name)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load %s locale file. %v", name, err)
|
||||
}
|
||||
@@ -75,8 +75,16 @@ func InitLocales() {
|
||||
|
||||
matcher = language.NewMatcher(supportedTags)
|
||||
for i := range setting.Names {
|
||||
var localeDataBase []byte
|
||||
if i == 0 && setting.Langs[0] != "en-US" {
|
||||
// Only en-US has complete translations. When use other language as default, the en-US should still be used as fallback.
|
||||
localeDataBase = localeData["locale_en-US.ini"]
|
||||
if localeDataBase == nil {
|
||||
log.Fatal("Failed to load locale_en-US.ini file.")
|
||||
}
|
||||
}
|
||||
key := "locale_" + setting.Langs[i] + ".ini"
|
||||
if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
|
||||
if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localeDataBase, localeData[key]); err != nil {
|
||||
log.Error("Failed to set messages to %s: %v", setting.Langs[i], err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ func isSnakeCaseLowerOrNumber(c byte) bool {
|
||||
// ToSnakeCase convert the input string to snake_case format.
|
||||
//
|
||||
// Some samples.
|
||||
//
|
||||
// "FirstName" => "first_name"
|
||||
// "HTTPServer" => "http_server"
|
||||
// "NoHTTPS" => "no_https"
|
||||
// "GO_PATH" => "go_path"
|
||||
// "GO PATH" => "go_path" // space is converted to underscore.
|
||||
// "GO-PATH" => "go_path" // hyphen is converted to underscore.
|
||||
//
|
||||
func ToSnakeCase(input string) string {
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -55,6 +55,7 @@ func Routes() *web.Route {
|
||||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
r.Group("/{username}", func() {
|
||||
@@ -66,7 +67,7 @@ func Routes() *web.Route {
|
||||
r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
|
||||
r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
|
||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/conan", func() {
|
||||
r.Group("/v1", func() {
|
||||
r.Get("/ping", conan.Ping)
|
||||
@@ -154,7 +155,7 @@ func Routes() *web.Route {
|
||||
}, conan.ExtractPathParameters)
|
||||
})
|
||||
})
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/generic", func() {
|
||||
r.Group("/{packagename}/{packageversion}/{filename}", func() {
|
||||
r.Get("", generic.DownloadPackageFile)
|
||||
@@ -163,18 +164,19 @@ func Routes() *web.Route {
|
||||
r.Delete("", generic.DeletePackage)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
})
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/helm", func() {
|
||||
r.Get("/index.yaml", helm.Index)
|
||||
r.Get("/{filename}", helm.DownloadPackageFile)
|
||||
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/maven", func() {
|
||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
|
||||
r.Get("/*", maven.DownloadPackageFile)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/nuget", func() {
|
||||
r.Get("/index.json", nuget.ServiceIndex)
|
||||
r.Get("/index.json", nuget.ServiceIndex) // Needs to be unauthenticated for the NuGet client.
|
||||
r.Group("", func() {
|
||||
r.Get("/query", nuget.SearchService)
|
||||
r.Group("/registration/{id}", func() {
|
||||
r.Get("/index.json", nuget.RegistrationIndex)
|
||||
@@ -189,18 +191,21 @@ func Routes() *web.Route {
|
||||
r.Put("/symbolpackage", nuget.UploadSymbolPackage)
|
||||
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-f]{32}}FFFFFFFF/{filename2}", nuget.DownloadSymbolFile)
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
})
|
||||
r.Group("/npm", func() {
|
||||
r.Group("/@{scope}/{id}", func() {
|
||||
r.Get("", npm.PackageMetadata)
|
||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
||||
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
|
||||
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
||||
})
|
||||
r.Group("/{id}", func() {
|
||||
r.Get("", npm.PackageMetadata)
|
||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
||||
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
|
||||
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
||||
})
|
||||
r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
|
||||
r.Get("", npm.ListPackageTags)
|
||||
@@ -216,12 +221,12 @@ func Routes() *web.Route {
|
||||
r.Delete("", npm.DeletePackageTag)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
})
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/pypi", func() {
|
||||
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
|
||||
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
||||
r.Get("/simple/{id}", pypi.PackageMetadata)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/rubygems", func() {
|
||||
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
|
||||
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
|
||||
@@ -233,7 +238,7 @@ func Routes() *web.Route {
|
||||
r.Delete("/yank", rubygems.DeletePackage)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
})
|
||||
}, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
|
||||
}, context_service.UserAssignmentWeb(), context.PackageAssignment())
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -254,6 +259,7 @@ func ContainerRoutes() *web.Route {
|
||||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
||||
|
||||
@@ -342,7 +342,6 @@ func uploadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
||||
Name: rref.Name,
|
||||
Version: rref.Version,
|
||||
},
|
||||
SemverCompatible: true,
|
||||
Creator: ctx.Doer,
|
||||
}
|
||||
pfci := &packages_service.PackageFileCreationInfo{
|
||||
|
||||
@@ -7,8 +7,11 @@ package container
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
@@ -19,6 +22,8 @@ import (
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
var uploadVersionMutex sync.Mutex
|
||||
|
||||
// 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(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
|
||||
@@ -28,6 +33,11 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||
|
||||
contentStore := packages_module.NewContentStore()
|
||||
|
||||
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(func(ctx context.Context) error {
|
||||
created := true
|
||||
p := &packages_model.Package{
|
||||
@@ -68,11 +78,30 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||
}
|
||||
}
|
||||
|
||||
uploadVersion = pv
|
||||
|
||||
return nil
|
||||
})
|
||||
uploadVersionMutex.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.WithTx(func(ctx context.Context) error {
|
||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||
if err != nil {
|
||||
log.Error("Error inserting package blob: %v", err)
|
||||
return err
|
||||
}
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
if exists {
|
||||
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||
log.Error("Error saving package blob in content store: %v", err)
|
||||
@@ -83,7 +112,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||
|
||||
pf := &packages_model.PackageFile{
|
||||
VersionID: pv.ID,
|
||||
VersionID: uploadVersion.ID,
|
||||
BlobID: pb.ID,
|
||||
Name: filename,
|
||||
LowerName: filename,
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -193,7 +194,7 @@ func InitiateUploadBlob(ctx *context.Context) {
|
||||
mount := ctx.FormTrim("mount")
|
||||
from := ctx.FormTrim("from")
|
||||
if mount != "" {
|
||||
blob, _ := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
Image: from,
|
||||
Digest: mount,
|
||||
})
|
||||
@@ -361,7 +362,7 @@ func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescri
|
||||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
|
||||
return container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Image: ctx.Params("image"),
|
||||
Digest: digest,
|
||||
@@ -503,7 +504,7 @@ func getManifestFromContext(ctx *context.Context) (*packages_model.PackageFileDe
|
||||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
|
||||
return container_model.GetContainerBlob(ctx, opts)
|
||||
return workaroundGetContainerBlob(ctx, opts)
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#checking-if-content-exists-in-the-registry
|
||||
@@ -643,3 +644,23 @@ func GetTagList(ctx *context.Context) {
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
|
||||
blob, err := container_model.GetContainerBlob(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(blob.Blob.HashSHA256))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", blob.Blob.HashSHA256)
|
||||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blob, nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -403,6 +405,15 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
|
||||
log.Error("Error inserting package blob: %v", err)
|
||||
return nil, false, "", err
|
||||
}
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
if exists {
|
||||
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
contentStore := packages_module.NewContentStore()
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
|
||||
|
||||
@@ -67,6 +67,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
|
||||
PeerDependencies: metadata.PeerDependencies,
|
||||
OptionalDependencies: metadata.OptionalDependencies,
|
||||
Readme: metadata.Readme,
|
||||
Bin: metadata.Bin,
|
||||
Dist: npm_module.PackageDistribution{
|
||||
Shasum: pd.Files[0].Blob.HashSHA1,
|
||||
Integrity: "sha512-" + base64.StdEncoding.EncodeToString(hashBytes),
|
||||
|
||||
@@ -105,6 +105,49 @@ func DownloadPackageFile(ctx *context.Context) {
|
||||
ctx.ServeStream(s, pf.Name)
|
||||
}
|
||||
|
||||
// DownloadPackageFileByName finds the version and serves the contents of a package
|
||||
func DownloadPackageFileByName(ctx *context.Context) {
|
||||
filename := ctx.Params("filename")
|
||||
|
||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Type: packages_model.TypeNpm,
|
||||
Name: packages_model.SearchValue{
|
||||
ExactMatch: true,
|
||||
Value: packageNameFromParams(ctx),
|
||||
},
|
||||
HasFileWithName: filename,
|
||||
IsInternal: false,
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if len(pvs) != 1 {
|
||||
apiError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pvs[0],
|
||||
&packages_service.PackageFileInfo{
|
||||
Filename: filename,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageFileNotExist {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeStream(s, pf.Name)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
func UploadPackage(ctx *context.Context) {
|
||||
npmPackage, err := npm_module.ParsePackage(ctx.Req.Body)
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
package nuget
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// ServiceIndexResponse https://docs.microsoft.com/en-us/nuget/api/service-index#resources
|
||||
@@ -113,8 +109,8 @@ func createRegistrationIndexResponse(l *linkBuilder, pds []*packages_model.Packa
|
||||
{
|
||||
RegistrationPageURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
|
||||
Count: len(pds),
|
||||
Lower: normalizeVersion(pds[0].SemVer),
|
||||
Upper: normalizeVersion(pds[len(pds)-1].SemVer),
|
||||
Lower: pds[0].Version.Version,
|
||||
Upper: pds[len(pds)-1].Version.Version,
|
||||
Items: items,
|
||||
},
|
||||
},
|
||||
@@ -191,7 +187,7 @@ type PackageVersionsResponse struct {
|
||||
func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *PackageVersionsResponse {
|
||||
versions := make([]string, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
versions = append(versions, normalizeVersion(pd.SemVer))
|
||||
versions = append(versions, pd.Version.Version)
|
||||
}
|
||||
|
||||
return &PackageVersionsResponse{
|
||||
@@ -224,20 +220,13 @@ type SearchResultVersion struct {
|
||||
}
|
||||
|
||||
func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages_model.PackageDescriptor) *SearchResultResponse {
|
||||
grouped := make(map[string][]*packages_model.PackageDescriptor)
|
||||
for _, pd := range pds {
|
||||
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
|
||||
}
|
||||
|
||||
data := make([]*SearchResult, 0, len(pds))
|
||||
|
||||
if len(pds) > 0 {
|
||||
groupID := pds[0].Package.Name
|
||||
group := make([]*packages_model.PackageDescriptor, 0, 10)
|
||||
|
||||
for i := 0; i < len(pds); i++ {
|
||||
if groupID != pds[i].Package.Name {
|
||||
data = append(data, createSearchResult(l, group))
|
||||
groupID = pds[i].Package.Name
|
||||
group = group[:0]
|
||||
}
|
||||
group = append(group, pds[i])
|
||||
}
|
||||
for _, group := range grouped {
|
||||
data = append(data, createSearchResult(l, group))
|
||||
}
|
||||
|
||||
@@ -273,15 +262,3 @@ func createSearchResult(l *linkBuilder, pds []*packages_model.PackageDescriptor)
|
||||
RegistrationIndexURL: l.GetRegistrationIndexURL(latest.Package.Name),
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeVersion removes the metadata
|
||||
func normalizeVersion(v *version.Version) string {
|
||||
var buf bytes.Buffer
|
||||
segments := v.Segments64()
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
pre := v.Prerelease()
|
||||
if pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", pre)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package
|
||||
// DownloadSymbolFile https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
|
||||
func DownloadSymbolFile(ctx *context.Context) {
|
||||
filename := ctx.Params("filename")
|
||||
guid := ctx.Params("guid")
|
||||
guid := ctx.Params("guid")[:32]
|
||||
filename2 := ctx.Params("filename2")
|
||||
|
||||
if filename != filename2 {
|
||||
|
||||
@@ -22,12 +22,19 @@ import (
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0503/#normalized-names
|
||||
// https://peps.python.org/pep-0426/#name
|
||||
var normalizer = strings.NewReplacer(".", "-", "_", "-")
|
||||
var nameMatcher = regexp.MustCompile(`\A[a-z0-9\.\-_]+\z`)
|
||||
var nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)
|
||||
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
var versionMatcher = regexp.MustCompile(`\Av?` +
|
||||
`(?:[0-9]+!)?` + // epoch
|
||||
`[0-9]+(?:\.[0-9]+)*` + // release segment
|
||||
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
|
||||
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
|
||||
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
|
||||
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
|
||||
`\z`)
|
||||
|
||||
func apiError(ctx *context.Context, status int, obj interface{}) {
|
||||
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
||||
@@ -123,7 +130,7 @@ func UploadPackageFile(ctx *context.Context) {
|
||||
|
||||
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
|
||||
packageVersion := ctx.Req.FormValue("version")
|
||||
if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
|
||||
if !isValidNameAndVersion(packageName, packageVersion) {
|
||||
apiError(ctx, http.StatusBadRequest, "invalid name or version")
|
||||
return
|
||||
}
|
||||
@@ -141,7 +148,7 @@ func UploadPackageFile(ctx *context.Context) {
|
||||
Name: packageName,
|
||||
Version: packageVersion,
|
||||
},
|
||||
SemverCompatible: true,
|
||||
SemverCompatible: false,
|
||||
Creator: ctx.Doer,
|
||||
Metadata: &pypi_module.Metadata{
|
||||
Author: ctx.Req.FormValue("author"),
|
||||
@@ -172,3 +179,7 @@ func UploadPackageFile(ctx *context.Context) {
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
func isValidNameAndVersion(packageName, packageVersion string) bool {
|
||||
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
|
||||
}
|
||||
|
||||
39
routers/api/packages/pypi/pypi_test.go
Normal file
39
routers/api/packages/pypi/pypi_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pypi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidNameAndVersion(t *testing.T) {
|
||||
// The test cases below were created from the following Python PEPs:
|
||||
// https://peps.python.org/pep-0426/#name
|
||||
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
|
||||
// Valid Cases
|
||||
assert.True(t, isValidNameAndVersion("A", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("Test.Name.1234", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test_name", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "v1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "2012.4"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1-alpha"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1a1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0b2.r345.dev456"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1!1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1+local.1"))
|
||||
|
||||
// Invalid Cases
|
||||
assert.False(t, isValidNameAndVersion(".test-name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test!name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("-test-name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name-", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "a1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta"))
|
||||
}
|
||||
@@ -75,7 +75,9 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo
|
||||
})
|
||||
}
|
||||
|
||||
ctx.SetServeHeaders(filename + ".gz")
|
||||
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||
Filename: filename + ".gz",
|
||||
})
|
||||
|
||||
zw := gzip.NewWriter(ctx.Resp)
|
||||
defer zw.Close()
|
||||
@@ -113,7 +115,9 @@ func ServePackageSpecification(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetServeHeaders(filename)
|
||||
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||
Filename: filename,
|
||||
})
|
||||
|
||||
zw := zlib.NewWriter(ctx.Resp)
|
||||
defer zw.Close()
|
||||
|
||||
@@ -230,13 +230,10 @@ func reqExploreSignIn() func(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
||||
func reqBasicAuth() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if ctx.IsSigned && setting.Service.EnableReverseProxyAuth && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
||||
return
|
||||
}
|
||||
if !ctx.Context.IsBasicAuth {
|
||||
ctx.Error(http.StatusUnauthorized, "reqBasicOrRevProxyAuth", "auth required")
|
||||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
|
||||
return
|
||||
}
|
||||
ctx.CheckForOTP()
|
||||
@@ -595,9 +592,6 @@ func buildAuthGroup() *auth.Group {
|
||||
&auth.HTTPSign{},
|
||||
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
||||
)
|
||||
if setting.Service.EnableReverseProxyAuth {
|
||||
group.Add(&auth.ReverseProxy{})
|
||||
}
|
||||
specialAdd(group)
|
||||
|
||||
return group
|
||||
@@ -681,7 +675,7 @@ func Routes() *web.Route {
|
||||
m.Combo("").Get(user.ListAccessTokens).
|
||||
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
|
||||
m.Combo("/{id}").Delete(user.DeleteAccessToken)
|
||||
}, reqBasicOrRevProxyAuth())
|
||||
}, reqBasicAuth())
|
||||
}, context_service.UserAssignmentAPI())
|
||||
})
|
||||
|
||||
@@ -890,7 +884,7 @@ func Routes() *web.Route {
|
||||
m.Group("/{index}", func() {
|
||||
m.Combo("").Get(repo.GetIssue).
|
||||
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
||||
Delete(reqToken(), reqAdmin(), repo.DeleteIssue)
|
||||
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
|
||||
m.Group("/comments", func() {
|
||||
m.Combo("").Get(repo.ListIssueComments).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||
|
||||
@@ -252,21 +252,26 @@ func ListBranches(ctx *context.APIContext) {
|
||||
// "200":
|
||||
// "$ref": "#/responses/BranchList"
|
||||
|
||||
var totalNumOfBranches int
|
||||
var apiBranches []*api.Branch
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
|
||||
skip, _ := listOptions.GetStartEnd()
|
||||
branches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiBranches := make([]*api.Branch, 0, len(branches))
|
||||
apiBranches = make([]*api.Branch, 0, len(branches))
|
||||
for i := range branches {
|
||||
c, err := branches[i].GetCommit()
|
||||
if err != nil {
|
||||
// Skip if this branch doesn't exist anymore.
|
||||
if git.IsErrNotExist(err) {
|
||||
totalNumOfBranches--
|
||||
total--
|
||||
continue
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
@@ -274,7 +279,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
}
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
||||
return
|
||||
}
|
||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
@@ -285,9 +290,12 @@ func ListBranches(ctx *context.APIContext) {
|
||||
apiBranches = append(apiBranches, apiBranch)
|
||||
}
|
||||
|
||||
totalNumOfBranches = total
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
|
||||
ctx.JSON(http.StatusOK, &apiBranches)
|
||||
ctx.JSON(http.StatusOK, apiBranches)
|
||||
}
|
||||
|
||||
// GetBranchProtection gets a branch protection
|
||||
|
||||
@@ -34,7 +34,7 @@ func GetGitAllRefs(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Reference"
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesnt support different output formats by ref
|
||||
// "$ref": "#/responses/ReferenceList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
@@ -67,7 +67,7 @@ func GetGitRefs(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Reference"
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesnt support different output formats by ref
|
||||
// "$ref": "#/responses/ReferenceList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user