Compare commits

...

23 Commits

Author SHA1 Message Date
6543
8769df117d Changelog v1.17.0 (#20541) 2022-07-30 21:08:25 +02:00
6543
09f2e1e1a2 WebAuthn CredentialID field needs to be increased in size (#20530) (#20555)
WebAuthn have updated their specification to set the maximum size of the
CredentialID to 1023 bytes. This is somewhat larger than our current
size and therefore we need to migrate.

The PR changes the struct to add CredentialIDBytes and migrates the CredentialID string
to the bytes field before another migration drops the old CredentialID field. Another migration
renames this field back.

Fix #20457

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2022-07-30 20:16:25 +02:00
silverwind
eeb490c7ab Rework raw file http header logic (#20484) (#20542)
- Always respect the user's configured mime type map
- Allow more types like image/pdf/video/audio to serve with correct content-type
- Shorten cache duration of raw files to 5 minutes, matching GitHub
- Don't set `content-disposition: attachment`, let the browser decide whether it wants to download or display a file directly
- Implement rfc5987 for filenames, remove previous hack. Confirmed it working in Safari.
- Make PDF attachment work in Safari by removing `sandbox` attribute.

This change will make a lot more file types open directly in browser now. Logic should generally be more readable than before with less `if` nesting and such.

Replaces: https://github.com/go-gitea/gitea/pull/20460
Replaces: https://github.com/go-gitea/gitea/pull/20455
Fixes: https://github.com/go-gitea/gitea/issues/20404
2022-07-30 18:37:02 +02:00
6543
97a8c96c5b Add Docker /v2/_catalog endpoint (#20469) (#20556)
* Added properties for packages.
* Fixed authenticate header format.
* Added _catalog endpoint.
* Check owner visibility.
* Extracted condition.
* Added test for _catalog.

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-07-30 17:52:04 +02:00
Gusted
d1e53bfd7f Update notification count for non-mobile version (#20544)
- Since #20108 we have two version of the notification bell, one for
mobile the other for non-mobile. However the code only accounts for one
notification count and thus was only updating the non-mobile one.
- This code fixes that by applying the code for all `.notification_count`s.
- Frontport will be in #20543
2022-07-30 10:28:48 +08:00
6543
fc7b5afd9b Add missing Tabs on organisation/package view (#20539)
hotfix #20106
2022-07-29 19:14:50 +02:00
6543
210b096da7 Ensure that all unmerged files are merged when conflict checking (#20528) (#20536)
There is a subtle bug in the code relating to collating the results of
`git ls-files -u -z` in `unmergedFiles()`. The code here makes the
mistake of assuming that every unmerged file will always have a stage 1
conflict, and this results in conflicts that occur in stage 3 only being
dropped.

This PR simply adjusts this code to ensure that any empty unmergedFile
will always be passed down the channel.

The PR also adds a lot of Trace commands to attempt to help find future
bugs in this code.

Fix #19527

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2022-07-29 15:37:18 +02:00
6543
d6bc1558c6 Update lunny/levelqueue to prevent NPE when reads are performed after close (#20534) (#20537)
Co-authored-by: zeripath <art27@cantab.net>
2022-07-29 20:58:56 +08:00
zeripath
6986e56791 Stop logging EOFs and exit(1)s in ssh handler (#20476) (#20529)
Backport #20476

The code in modules/ssh/ssh.go:sessionHandler() currently cause an error to be
logged if `gitea serv` exits with a exit(1). This logging is useless because the
accompanying stderr is not provided and in any case the exit(1) is most likely due
to permissions errors.

Further it then causes the EOF to be logged - even though this is not helpful.

This PR simply checks the errors returned and stops logging them.

In the case of misconfigurations causing `gitea serv` to fail with exit(1)
the current logging is not helpful at determining this and users should simply
review the message passed over the ssh connection.

Fix #20473

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-07-29 02:10:42 +02:00
6543
ae86a0bc9f packages/generic: Do not restrict package versions to SemVer (#20414) (#20531)
There are existing packages out there whose version do not conform to SemVer, yet, one would like to have them available in a generic package repository. To this end, remove the SemVer restriction on package versions when using the Generic package registry, and replace it with a check that simply makes sure the version isn't empty.

Signed-off-by: Gergely Nagy <me@gergo.csillger.hu>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Gergely Nagy <algernon@users.noreply.github.com>
2022-07-29 01:17:56 +02:00
6543
4b53a5c3a1 Add labels to two buttons that were missing them (#20419) (#20524)
Backport #20419

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-07-28 21:21:36 +01:00
6543
da10ce8b07 Allow non-semver packages in the Conan package registry (#20412) (#20523)
Backport #20412

A lot of existing packages do not conform to SemVer, yet, they should be allowed
in the Conan package registry as-is. To achieve this, remove the SemVer check
from `NewRecipeReference`, and replace it with a simple empty string check.

A unit test with a non-semver version is also included.

Fixes #20405.

Signed-off-by: Gergely Nagy <me@gergo.csillger.hu>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Gergely Nagy <algernon@users.noreply.github.com>
2022-07-28 21:19:56 +01:00
6543
4ed32e79b6 Fix possible panic when repository is empty (#20509) (#20526)
Backport #20509
2022-07-28 21:18:49 +01:00
6543
fa46d66835 Fix Ruby package parsing by removed unused email field (#20470) (#20525)
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2022-07-28 20:41:57 +02:00
Gusted
648ec3cfed Fix dashboard switching on Mobile (#20238) (#20239)
- This is a regression of improving mobile experience on Gitea, currently organization dashboard aren't readable and the popup won't show up when you want to switch between users/organization(as we saw in #19978). 
- This patch fixes that, by allowing the popup to allocate the required pixels(for some absurd reason, z-index doesn't work on the popup, so it's not able to render over the existing elements, we can investigate later of why this is). And also remove the additional dropdown menu for the pages link, so it's one unified list which then can be displayed as rows.
2022-07-28 19:04:29 +02:00
KN4CK3R
a9a440e600 Fix package permission checks for organizations (#20517) (#20520) 2022-07-28 22:05:59 +08:00
wxiaoguang
39b2ede930 Fix ROOT_URL detection for URLs without trailing slash (#20503) 2022-07-27 23:49:01 +08:00
Norwin
0a32bd56eb Show hint to link package to repo when viewing empty repo package list (#20504) (#20507) 2022-07-27 10:06:54 -04:00
Tyrone Yeh
e0f35ea00f Modify milestone search keywords to be case insensitive (#20266) (#20498)
* Modify milestone search keywords to be case insensitive (#20266)

Milestone search keywords are now sensitive, this modification is changed to insensitive

* Modify for #18437

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-07-27 19:55:09 +08:00
Tyrone Yeh
bed13bfa9e Add repository condition for issue count (#20496) 2022-07-27 07:09:25 +01:00
Norwin
435038b2c6 fix enabling repo packages when projects are off (#20486) (#20488) 2022-07-26 16:50:45 +02:00
silverwind
2fe0dab2d5 Add Cache-Control header to html and api responses, add no-transform (#20432) (#20459)
`no-transform` allegedly disables CloudFlare auto-minify and we did not
set caching headers on html or api requests, which seems good to have
regardless.

Transformation is still allowed for asset requests.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
2022-07-23 11:58:58 +01:00
6543
e930d66a9c Dismiss prior pull reviews if done via web in review dismiss (#20197) (#20407) 2022-07-19 17:46:33 +02:00
72 changed files with 1087 additions and 238 deletions
+38 -42
View File
@@ -4,44 +4,7 @@ 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.0-rc2](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc2) - 2022-07-13
* SECURITY
* Use git.HOME_PATH for Git HOME directory (#20114) (#20293)
* Add write check for creating Commit Statuses (#20332) (#20333)
* ENHANCEMENTS
* Make notification bell more prominent on mobile (#20108, #20236, #20251) (#20269)
* Adjust max-widths for the repository file table (#20243) (#20247)
* Display full name (#20171) (#20246)
* BUGFIXES
* Allow RSA 2047 bit keys (#20272) (#20396)
* Add missing return for when topic isn't found (#20351) (#20395)
* Fix commit status icon when in subdirectory (#20285) (#20385)
* Initialize cron last (#20373) (#20384)
* Set target on create release with existing tag (#20381) (#20382)
* Update xorm.io/xorm to fix a interpreting db column sizes issue on 32bit systems (#20371) (#20372)
* Make sure `repo_dir` is an empty directory or doesn't exist before 'dump-repo' (#20205) (#20370)
* Prevent context deadline error propagation in GetCommitsInfo (#20346) (#20361)
* Correctly handle draft releases without a tag (#20314) (#20335)
* Prevent "empty" scrollbars on Firefox (#20294) (#20308)
* Refactor SSH init code, fix directory creation for TrustedUserCAKeys file (#20299) (#20306)
* Bump goldmark to v1.4.13 (#20300) (#20301)
* Do not create empty ".ssh" directory when loading config (#20289) (#20298)
* Fix NPE when using non-numeric (#20277) (#20278)
* Store read access in access for team repositories (#20275) (#20276)
* EscapeFilter the group dn membership (#20200) (#20254)
* Only show Followers that current user can access (#20220) (#20252)
* Update Bluemonday to v1.0.19 (#20199) (#20209)
* Refix indices on actions table (#20158) (#20198)
* Check if project has the same repository id with issue when assign project to issue (#20133) (#20188)
* Fix remove file on initial comment (#20127) (#20128)
* Catch the error before the response is processed by goth (#20000) (#20102)
* Dashboard feed respect setting.UI.FeedPagingNum again (#20094) (#20099)
* Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041)
* Respond with a 401 on git push when password isn't changed yet (#20026) (#20027)
* Return 404 when tag is broken (#20017) (#20024)
## [1.17.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc1) - 2022-06-18
## [1.17.0](https://github.com/go-gitea/gitea/releases/tag/v1.17.0) - 2022-07-30
* BREAKING
* Require go1.18 for Gitea 1.17 (#19918)
@@ -66,6 +29,8 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Restrict email address validation (#17688)
* Refactor Router Logger (#17308)
* SECURITY
* Use git.HOME_PATH for Git HOME directory (#20114) (#20293)
* Add write check for creating Commit Statuses (#20332) (#20333)
* Remove deprecated SSH ciphers from default (#18697)
* FEDERATION
* Return statistic information for nodeinfo (#19561)
@@ -104,6 +69,9 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Return primary language and repository language stats API URL (#18396)
* Implement http signatures support for the API (#17565)
* ENHANCEMENTS
* Make notification bell more prominent on mobile (#20108, #20236, #20251) (#20269)
* Adjust max-widths for the repository file table (#20243) (#20247)
* Display full name (#20171) (#20246)
* Add dbconsistency checks for Stopwatches (#20010)
* Add fetch.writeCommitGraph to gitconfig (#20006)
* Add fgprof pprof profiler (#20005)
@@ -148,7 +116,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* PullService lock via pullID (#19520)
* Make repository file list useable on mobile (#19515)
* more context for models (#19511)
* Allow package dump skipping (#19506)
* Refactor readme file renderer (#19502)
* By default force vertical tabs on mobile (#19486)
* Github style following followers (#19482)
@@ -165,7 +132,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Move access and repo permission to models/perm/access (#19350)
* Disallow selecting the text of buttons (#19330)
* Allow custom redirect for landing page (#19324)
* Repository level enable package or disable (#19323)
* Remove dependent on session auth for api/v1 routers (#19321)
* Never use /api/v1 from Gitea UI Pages (#19318)
* Remove legacy unmaintained packages, refactor to support change default locale (#19308)
@@ -222,13 +188,44 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Allow custom default merge message with .gitea/default_merge_message/<merge_style>_TEMPLATE.md (#18177)
* Prettify number of issues (#17760)
* Add a "admin user generate-access-token" subcommand (#17722)
* Move project files into models/project sub package (#17704)
* Custom regexp external issues (#17624)
* Add smtp password to install page (#17564)
* Add config options to hide issue events (#17414)
* Prevent double click new issue/pull/comment button (#16157)
* Show issue assignee on project board (#15232)
* BUGFIXES
* WebAuthn CredentialID field needs to be increased in size (#20530) (#20555)
* Ensure that all unmerged files are merged when conflict checking (#20528) (#20536)
* Stop logging EOFs and exit(1)s in ssh handler (#20476) (#20529)
* Add labels to two buttons that were missing them (#20419) (#20524)
* Fix ROOT_URL detection for URLs without trailing slash (#20502) (#20503)
* Dismiss prior pull reviews if done via web in review dismiss (#20197) (#20407)
* Allow RSA 2047 bit keys (#20272) (#20396)
* Add missing return for when topic isn't found (#20351) (#20395)
* Fix commit status icon when in subdirectory (#20285) (#20385)
* Initialize cron last (#20373) (#20384)
* Set target on create release with existing tag (#20381) (#20382)
* Update xorm.io/xorm to fix a interpreting db column sizes issue on 32bit systems (#20371) (#20372)
* Make sure `repo_dir` is an empty directory or doesn't exist before 'dump-repo' (#20205) (#20370)
* Prevent context deadline error propagation in GetCommitsInfo (#20346) (#20361)
* Correctly handle draft releases without a tag (#20314) (#20335)
* Prevent "empty" scrollbars on Firefox (#20294) (#20308)
* Refactor SSH init code, fix directory creation for TrustedUserCAKeys file (#20299) (#20306)
* Bump goldmark to v1.4.13 (#20300) (#20301)
* Do not create empty ".ssh" directory when loading config (#20289) (#20298)
* Fix NPE when using non-numeric (#20277) (#20278)
* Store read access in access for team repositories (#20275) (#20276)
* EscapeFilter the group dn membership (#20200) (#20254)
* Only show Followers that current user can access (#20220) (#20252)
* Update Bluemonday to v1.0.19 (#20199) (#20209)
* Refix indices on actions table (#20158) (#20198)
* Check if project has the same repository id with issue when assign project to issue (#20133) (#20188)
* Fix remove file on initial comment (#20127) (#20128)
* Catch the error before the response is processed by goth (#20000) (#20102)
* Dashboard feed respect setting.UI.FeedPagingNum again (#20094) (#20099)
* Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041)
* Respond with a 401 on git push when password isn't changed yet (#20026) (#20027)
* Return 404 when tag is broken (#20017) (#20024)
* Alter hook_task TEXT fields to LONGTEXT (#20038) (#20041)
* Respond with a 401 on git push when password isn't changed yet (#20026) (#20027)
* Return 404 when tag is broken (#20017) (#20024)
@@ -303,7 +300,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* MISC
* Fix aria for logo (#19955)
* In code search, get code unit accessible repos in one (main) query (#19764)
* Enable packages by default again (#19746)
* Add tooltip to pending PR comments (#19662)
* Improve sync performance for pull-mirrors (#19125)
* Improve dashboard's repo list performance (#18963)
+1 -1
View File
@@ -37,7 +37,7 @@ PUT https://gitea.example.com/api/packages/{owner}/generic/{package_name}/{packa
| ----------------- | ----------- |
| `owner` | The owner of the package. |
| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
| `package_version` | The package version as described in the [SemVer](https://semver.org/) spec. |
| `package_version` | The package version, a non-empty string. |
| `file_name` | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). |
Example request using HTTP Basic authentication:
+1 -1
View File
@@ -9,7 +9,7 @@ require (
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
gitea.com/lunny/levelqueue v0.4.1
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.8.0
+2 -2
View File
@@ -78,8 +78,8 @@ gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 h1:J/1i8u40TbcLP/w2w
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5/go.mod h1:hQ9SYHKdOX968wJglb/NMQ+UqpOKwW4L+EYdvkWjHSo=
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 h1:tJQRXgZigkLeeW9LPlps9G9aMoE6LAmqigLA+wxmd1Q=
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8/go.mod h1:fc/pjt5EqNKgqQXYzcas1Z5L5whkZHyOvTA7OzWVJck=
gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw=
gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 h1:Zc3RQWC2xOVglLciQH/ZIC5IqSk3Jn96LflGQLv18Rg=
gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
+70 -14
View File
@@ -26,6 +26,7 @@ import (
func TestPackageContainer(t *testing.T) {
defer prepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
has := func(l packages_model.PackagePropertyList, name string) bool {
@@ -36,6 +37,15 @@ func TestPackageContainer(t *testing.T) {
}
return false
}
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
values := make([]string, 0, len(l))
for _, pp := range l {
if pp.Name == name {
values = append(values, pp.Value)
}
}
return values
}
images := []string{"test", "te/st"}
tags := []string{"latest", "main"}
@@ -66,7 +76,7 @@ func TestPackageContainer(t *testing.T) {
Token string `json:"token"`
}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token"`}
authenticate := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`}
t.Run("Anonymous", func(t *testing.T) {
defer PrintCurrentTest(t)()
@@ -236,7 +246,8 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
assert.Equal(t, tag, pd.Version.Version)
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
metadata := pd.Metadata.(*container_module.Metadata)
@@ -330,7 +341,8 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
assert.Equal(t, untaggedManifestDigest, pd.Version.Version)
assert.False(t, has(pd.Properties, container_module.PropertyManifestTagged))
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
assert.False(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
@@ -362,18 +374,10 @@ func TestPackageContainer(t *testing.T) {
assert.Nil(t, pd.SemVer)
assert.Equal(t, image, pd.Package.Name)
assert.Equal(t, multiTag, pd.Version.Version)
assert.True(t, has(pd.Properties, container_module.PropertyManifestTagged))
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
getAllByName := func(l packages_model.PackagePropertyList, name string) []string {
values := make([]string, 0, len(l))
for _, pp := range l {
if pp.Name == name {
values = append(values, pp.Value)
}
}
return values
}
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.Properties, container_module.PropertyManifestReference))
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
metadata := pd.Metadata.(*container_module.Metadata)
@@ -528,4 +532,56 @@ func TestPackageContainer(t *testing.T) {
})
})
}
t.Run("OwnerNameChange", func(t *testing.T) {
defer PrintCurrentTest(t)()
checkCatalog := func(owner string) func(t *testing.T) {
return func(t *testing.T) {
defer PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL))
addTokenAuthHeader(req, userToken)
resp := MakeRequest(t, req, http.StatusOK)
type RepositoryList struct {
Repositories []string `json:"repositories"`
}
repoList := &RepositoryList{}
DecodeJSON(t, resp, &repoList)
assert.Len(t, repoList.Repositories, len(images))
names := make([]string, 0, len(images))
for _, image := range images {
names = append(names, strings.ToLower(owner+"/"+image))
}
assert.ElementsMatch(t, names, repoList.Repositories)
}
}
t.Run(fmt.Sprintf("Catalog[%s]", user.LowerName), checkCatalog(user.LowerName))
session := loginUser(t, user.Name)
newOwnerName := "newUsername"
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"),
"name": newOwnerName,
"email": "user2@example.com",
"language": "en-US",
})
session.MakeRequest(t, req, http.StatusSeeOther)
t.Run(fmt.Sprintf("Catalog[%s]", newOwnerName), checkCatalog(newOwnerName))
req = NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"),
"name": user.Name,
"email": "user2@example.com",
"language": "en-US",
})
session.MakeRequest(t, req, http.StatusSeeOther)
})
}
@@ -42,7 +42,6 @@ func TestPackageGeneric(t *testing.T) {
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
assert.NoError(t, err)
assert.NotNil(t, pd.SemVer)
assert.Nil(t, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
+3 -3
View File
@@ -85,9 +85,9 @@ func TestPackageNpm(t *testing.T) {
assert.IsType(t, &npm.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
assert.Len(t, pd.Properties, 1)
assert.Equal(t, npm.TagProperty, pd.Properties[0].Name)
assert.Equal(t, packageTag, pd.Properties[0].Value)
assert.Len(t, pd.VersionProperties, 1)
assert.Equal(t, npm.TagProperty, pd.VersionProperties[0].Name)
assert.Equal(t, packageTag, pd.VersionProperties[0].Value)
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
assert.NoError(t, err)
+8 -10
View File
@@ -6,7 +6,6 @@ package auth
import (
"context"
"encoding/base32"
"fmt"
"strings"
@@ -20,14 +19,14 @@ import (
// ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
type ErrWebAuthnCredentialNotExist struct {
ID int64
CredentialID string
CredentialID []byte
}
func (err ErrWebAuthnCredentialNotExist) Error() string {
if err.CredentialID == "" {
if len(err.CredentialID) == 0 {
return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID)
}
return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %s]", err.CredentialID)
return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
}
// IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
@@ -43,7 +42,7 @@ type WebAuthnCredential struct {
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"`
CredentialID []byte `xorm:"INDEX VARBINARY(1024)"`
PublicKey []byte
AttestationType string
AAGUID []byte
@@ -94,9 +93,8 @@ type WebAuthnCredentialList []*WebAuthnCredential
func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
creds := make([]webauthn.Credential, 0, len(list))
for _, cred := range list {
credID, _ := base32.HexEncoding.DecodeString(cred.CredentialID)
creds = append(creds, webauthn.Credential{
ID: credID,
ID: cred.CredentialID,
PublicKey: cred.PublicKey,
AttestationType: cred.AttestationType,
Authenticator: webauthn.Authenticator{
@@ -164,11 +162,11 @@ func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) {
}
// GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
func GetWebAuthnCredentialByCredID(userID int64, credID string) (*WebAuthnCredential, error) {
func GetWebAuthnCredentialByCredID(userID int64, credID []byte) (*WebAuthnCredential, error) {
return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID)
}
func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID string) (*WebAuthnCredential, error) {
func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) {
cred := new(WebAuthnCredential)
if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
return nil, err
@@ -187,7 +185,7 @@ func createCredential(ctx context.Context, userID int64, name string, cred *weba
c := &WebAuthnCredential{
UserID: userID,
Name: name,
CredentialID: base32.HexEncoding.EncodeToString(cred.ID),
CredentialID: cred.ID,
PublicKey: cred.PublicKey,
AttestationType: cred.AttestationType,
AAGUID: cred.Authenticator.AAGUID,
+1 -4
View File
@@ -5,7 +5,6 @@
package auth
import (
"encoding/base32"
"testing"
"code.gitea.io/gitea/models/unittest"
@@ -61,9 +60,7 @@ func TestCreateCredential(t *testing.T) {
res, err := CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
assert.NoError(t, err)
assert.Equal(t, "WebAuthn Created Credential", res.Name)
bs, err := base32.HexEncoding.DecodeString(res.CredentialID)
assert.NoError(t, err)
assert.Equal(t, []byte("Test"), bs)
assert.Equal(t, []byte("Test"), res.CredentialID)
unittest.AssertExistsIf(t, true, &WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
}
+6 -1
View File
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
@@ -361,7 +362,11 @@ func (opts GetMilestonesOption) toCond() builder.Cond {
}
if len(opts.Name) != 0 {
cond = cond.And(builder.Like{"name", opts.Name})
if setting.Database.UseSQLite3 {
cond = cond.And(builder.Like{"UPPER(name)", util.ToUpperASCII(opts.Name)})
} else {
cond = cond.And(builder.Like{"UPPER(name)", strings.ToUpper(opts.Name)})
}
}
return cond
+30
View File
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
@@ -474,6 +475,35 @@ func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, co
return review, comm, committer.Commit()
}
// GetReviewOptions represent filter options for GetReviews
type GetReviewOptions struct {
IssueID int64
ReviewerID int64
Dismissed util.OptionalBool
}
// GetReviews return reviews based on GetReviewOptions
func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error) {
if opts == nil {
return nil, fmt.Errorf("opts are nil")
}
sess := db.GetEngine(ctx)
if opts.IssueID != 0 {
sess = sess.Where("issue_id=?", opts.IssueID)
}
if opts.ReviewerID != 0 {
sess = sess.Where("reviewer_id=?", opts.ReviewerID)
}
if !opts.Dismissed.IsNone() {
sess = sess.Where("dismissed=?", opts.Dismissed.IsTrue())
}
reviews := make([]*Review, 0, 4)
return reviews, sess.Find(&reviews)
}
// GetReviewersByIssueID gets the latest review of each reviewer for a pull request
func GetReviewersByIssueID(issueID int64) ([]*Review, error) {
reviews := make([]*Review, 0, 10)
@@ -0,0 +1,9 @@
-
id: 1
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
-
id: 2
credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR="
-
id: 4
credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
@@ -0,0 +1,31 @@
-
id: 1
lower_name: "u2fkey-correctly-migrated"
name: "u2fkey-correctly-migrated"
user_id: 1
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'fido-u2f'
sign_count: 1
clone_warning: false
-
id: 2
lower_name: "non-u2f-key"
name: "non-u2f-key"
user_id: 1
credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR"
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'none'
sign_count: 1
clone_warning: false
-
id: 4
lower_name: "packed-key"
name: "packed-key"
user_id: 1
credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'fido-u2f'
sign_count: 1
clone_warning: false
+10
View File
@@ -396,6 +396,16 @@ var migrations = []Migration{
NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText),
// v218 -> v219
NewMigration("Improve Action table indices v2", improveActionTableIndices),
// v219 -> v220
NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror),
// v220 -> v221
NewMigration("Add container repository property", addContainerRepositoryProperty),
// v221 -> v222
NewMigration("Store WebAuthentication CredentialID as bytes and increase size to at least 1024", storeWebauthnCredentialIDAsBytes),
// v222 -> v223
NewMigration("Drop old CredentialID column", dropOldCredentialIDColumn),
// v223 -> v224
NewMigration("Rename CredentialIDBytes column to CredentialID", renameCredentialIDBytes),
}
// GetCurrentDBVersion returns the current db version
+31
View File
@@ -0,0 +1,31 @@
// 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 migrations
import (
"time"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func addSyncOnCommitColForPushMirror(x *xorm.Engine) error {
type PushMirror struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *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"`
LastError string `xorm:"text"`
}
return x.Sync2(new(PushMirror))
}
+29
View File
@@ -0,0 +1,29 @@
// 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 migrations
import (
packages_model "code.gitea.io/gitea/models/packages"
container_module "code.gitea.io/gitea/modules/packages/container"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
func addContainerRepositoryProperty(x *xorm.Engine) error {
switch x.Dialect().URI().DBType {
case schemas.SQLITE:
_, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
if err != nil {
return err
}
default:
_, err := x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
if err != nil {
return err
}
}
return nil
}
+75
View File
@@ -0,0 +1,75 @@
// 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 migrations
import (
"encoding/base32"
"fmt"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func storeWebauthnCredentialIDAsBytes(x *xorm.Engine) error {
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"`
// Note the lack of INDEX here - these will be created once the column is renamed in v223.go
CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
var start int
creds := make([]*webauthnCredential, 0, 50)
for {
err := x.Select("id, credential_id").OrderBy("id").Limit(50, start).Find(&creds)
if err != nil {
return err
}
err = func() error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return fmt.Errorf("unable to allow start session. Error: %w", err)
}
for _, cred := range creds {
cred.CredentialIDBytes, err = base32.HexEncoding.DecodeString(cred.CredentialID)
if err != nil {
return fmt.Errorf("unable to parse credential id %s for credential[%d]: %w", cred.CredentialID, cred.ID, err)
}
count, err := sess.ID(cred.ID).Cols("credential_id_bytes").Update(cred)
if count != 1 || err != nil {
return fmt.Errorf("unable to update credential id bytes for credential[%d]: %d,%w", cred.ID, count, err)
}
}
return sess.Commit()
}()
if err != nil {
return err
}
if len(creds) < 50 {
break
}
start += 50
creds = creds[:0]
}
return nil
}
+65
View File
@@ -0,0 +1,65 @@
// 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 migrations
import (
"encoding/base32"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_storeWebauthnCredentialIDAsBytes(t *testing.T) {
// Create webauthnCredential table
type WebauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"`
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
}
type ExpectedWebauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
CredentialID string // CredentialID is at most 1023 bytes as per spec released 20 July 2022
}
type ConvertedWebauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
}
// Prepare and load the testing database
x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(ExpectedWebauthnCredential))
defer deferable()
if x == nil || t.Failed() {
return
}
if err := storeWebauthnCredentialIDAsBytes(x); err != nil {
assert.NoError(t, err)
return
}
expected := []ExpectedWebauthnCredential{}
if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
return
}
got := []ConvertedWebauthnCredential{}
if err := x.Table("webauthn_credential").Select("id, credential_id_bytes").Asc("id").Find(&got); !assert.NoError(t, err) {
return
}
for i, e := range expected {
credIDBytes, _ := base32.HexEncoding.DecodeString(e.CredentialID)
assert.Equal(t, credIDBytes, got[i].CredentialIDBytes)
}
}
+64
View File
@@ -0,0 +1,64 @@
// 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 migrations
import (
"context"
"fmt"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func dropOldCredentialIDColumn(x *xorm.Engine) error {
// This migration maybe rerun so that we should check if it has been run
credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
if err != nil {
return err
}
if !credentialIDExist {
// Column is already non-extant
return nil
}
credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
if err != nil {
return err
}
if !credentialIDBytesExists {
// looks like 221 hasn't properly run
return fmt.Errorf("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration")
}
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"`
// Note the lack of the INDEX on CredentialIDBytes - we will add this in v223.go
CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
// Drop the old credential ID
sess := x.NewSession()
defer sess.Close()
if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
return fmt.Errorf("unable to drop old credentialID column: %w", err)
}
return sess.Commit()
}
+103
View File
@@ -0,0 +1,103 @@
// 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 migrations
import (
"context"
"fmt"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func renameCredentialIDBytes(x *xorm.Engine) error {
// This migration maybe rerun so that we should check if it has been run
credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
if err != nil {
return err
}
if credentialIDExist {
credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
if err != nil {
return err
}
if !credentialIDBytesExists {
return nil
}
}
err = func() error {
// webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
// Note the lack of INDEX here
CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := sess.Sync2(new(webauthnCredential)); err != nil {
return fmt.Errorf("error on Sync2: %v", err)
}
if credentialIDExist {
// if both errors and message exist, drop message at first
if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
return err
}
}
switch {
case setting.Database.UseMySQL:
if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil {
return err
}
case setting.Database.UseMSSQL:
if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil {
return err
}
default:
if _, err := sess.Exec("ALTER TABLE `webauthn_credential` RENAME COLUMN credential_id_bytes TO credential_id"); err != nil {
return err
}
}
return sess.Commit()
}()
if err != nil {
return err
}
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID []byte `xorm:"INDEX VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
return x.Sync2(&webauthnCredential{})
}
+36
View File
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container"
"xorm.io/builder"
@@ -210,6 +211,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
return pvs, count, err
}
// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
var cond builder.Cond = builder.Eq{
"package_version.is_internal": true,
@@ -225,3 +227,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([
Where(cond).
Find(&pfs)
}
// GetRepositories gets a sorted list of all repositories
func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
var cond builder.Cond = builder.Eq{
"package.type": packages.TypeContainer,
"package_property.ref_type": packages.PropertyTypePackage,
"package_property.name": container_module.PropertyRepository,
}
cond = cond.And(builder.Exists(
builder.
Select("package_version.id").
Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
From("package_version"),
))
if last != "" {
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
}
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
sess := db.GetEngine(ctx).
Table("package").
Select("package_property.value").
Join("INNER", "user", "`user`.id = package.owner_id").
Join("INNER", "package_property", "package_property.ref_id = package.id").
Where(cond).
Asc("package_property.value").
Limit(n)
repositories := make([]string, 0, n)
return repositories, sess.Find(&repositories)
}
+24 -18
View File
@@ -40,15 +40,16 @@ func (l PackagePropertyList) GetByName(name string) string {
// PackageDescriptor describes a package
type PackageDescriptor struct {
Package *Package
Owner *user_model.User
Repository *repo_model.Repository
Version *PackageVersion
SemVer *version.Version
Creator *user_model.User
Properties PackagePropertyList
Metadata interface{}
Files []*PackageFileDescriptor
Package *Package
Owner *user_model.User
Repository *repo_model.Repository
Version *PackageVersion
SemVer *version.Version
Creator *user_model.User
PackageProperties PackagePropertyList
VersionProperties PackagePropertyList
Metadata interface{}
Files []*PackageFileDescriptor
}
// PackageFileDescriptor describes a package file
@@ -102,6 +103,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
return nil, err
}
}
pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
if err != nil {
return nil, err
}
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
if err != nil {
return nil, err
@@ -152,15 +157,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
}
return &PackageDescriptor{
Package: p,
Owner: o,
Repository: repository,
Version: pv,
SemVer: semVer,
Creator: creator,
Properties: PackagePropertyList(pvps),
Metadata: metadata,
Files: pfds,
Package: p,
Owner: o,
Repository: repository,
Version: pv,
SemVer: semVer,
Creator: creator,
PackageProperties: PackagePropertyList(pps),
VersionProperties: PackagePropertyList(pvps),
Metadata: metadata,
Files: pfds,
}, nil
}
+11 -6
View File
@@ -131,6 +131,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
return p, nil
}
// DeletePackageByID deletes a package by id
func DeletePackageByID(ctx context.Context, packageID int64) error {
_, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
return err
}
// SetRepositoryLink sets the linked repository
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
@@ -192,21 +198,20 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]
Find(&ps)
}
// DeletePackagesIfUnreferenced deletes a package if there are no associated versions
func DeletePackagesIfUnreferenced(ctx context.Context) error {
// FindUnreferencedPackages gets all packages without associated versions
func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
in := builder.
Select("package.id").
From("package").
LeftJoin("package_version", "package_version.package_id = package.id").
Where(builder.Expr("package_version.id IS NULL"))
_, err := db.GetEngine(ctx).
ps := make([]*Package, 0, 10)
return ps, db.GetEngine(ctx).
// double select workaround for MySQL
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
Delete(&Package{})
return err
Find(&ps)
}
// HasOwnerPackages tests if a user/org has packages
+9 -1
View File
@@ -21,9 +21,11 @@ const (
PropertyTypeVersion PropertyType = iota // 0
// PropertyTypeFile means the reference is a package file
PropertyTypeFile // 1
// PropertyTypePackage means the reference is a package
PropertyTypePackage // 2
)
// PackageProperty represents a property of a package version or file
// PackageProperty represents a property of a package, version or file
type PackageProperty struct {
ID int64 `xorm:"pk autoincr"`
RefType PropertyType `xorm:"INDEX NOT NULL"`
@@ -68,3 +70,9 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
return err
}
// DeletePropertyByName deletes properties by name
func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
return err
}
+24 -25
View File
@@ -58,31 +58,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
cond = cond.And(builder.In("visibility", opts.Visible))
}
if opts.Actor != nil {
var exprCond builder.Cond = builder.Expr("org_user.org_id = `user`.id")
// If Admin - they see all users!
if !opts.Actor.IsAdmin {
// Force visibility for privacy
var accessCond builder.Cond
if !opts.Actor.IsRestricted {
accessCond = builder.Or(
builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
} else {
// restricted users only see orgs they are a member of
accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID})))
}
// Don't forget about self
accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID})
cond = cond.And(accessCond)
}
} else {
// Force visibility for privacy
// Not logged in - only public users
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
}
cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
if opts.UID > 0 {
cond = cond.And(builder.Eq{"id": opts.UID})
@@ -170,3 +146,26 @@ func IterateUser(f func(user *User) error) error {
}
}
}
// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
func BuildCanSeeUserCondition(actor *User) builder.Cond {
if actor != nil {
// If Admin - they see all users!
if !actor.IsAdmin {
// Users can see an organization they are a member of
cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
if !actor.IsRestricted {
// Not-Restricted users can see public and limited users/organizations
cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
}
// Don't forget about self
return cond.Or(builder.Eq{"`user`.id": actor.ID})
}
return nil
}
// Force visibility for privacy
// Not logged in - only public users
return builder.In("`user`.visibility", structs.VisibleTypePublic)
}
+2
View File
@@ -16,6 +16,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
@@ -268,6 +269,7 @@ func APIContexter() func(http.Handler) http.Handler {
}
}
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["Context"] = &ctx
+2
View File
@@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/base"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -767,6 +768,7 @@ func Contexter() func(next http.Handler) http.Handler {
}
}
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
+18 -1
View File
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
"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/structs"
)
@@ -52,14 +53,30 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
}
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 = organization.OrgFromUser(ctx.Package.Owner).GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
ctx.Package.AccessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
if err != nil {
errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", 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) {
+12 -5
View File
@@ -17,16 +17,23 @@ import (
)
// AddCacheControlToHeader adds suitable cache-control headers to response
func AddCacheControlToHeader(h http.Header, d time.Duration) {
func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
directives := make([]string, 0, 2+len(additionalDirectives))
if setting.IsProd {
h.Set("Cache-Control", "private, max-age="+strconv.Itoa(int(d.Seconds())))
if maxAge == 0 {
directives = append(directives, "no-store")
} else {
directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
}
} else {
h.Set("Cache-Control", "no-store")
directives = append(directives, "no-store")
// to remind users they are using non-prod setting.
// some users may be confused by "Cache-Control: no-store" in their setup if they did wrong to `RUN_MODE` in `app.ini`.
h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
h.Add("X-Gitea-Debug", "CacheControl=no-store")
}
h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
}
// generateETag generates an ETag based on size, filename and file modification time
+5 -4
View File
@@ -8,10 +8,9 @@ import (
"errors"
"fmt"
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
goversion "github.com/hashicorp/go-version"
)
const (
@@ -56,7 +55,9 @@ func NewRecipeReference(name, version, user, channel, revision string) (*RecipeR
if !namePattern.MatchString(name) {
return nil, ErrValidation
}
if _, err := goversion.NewSemver(version); err != nil {
v := strings.TrimSpace(version)
if v == "" {
return nil, ErrValidation
}
if user != "" && !namePattern.MatchString(user) {
@@ -69,7 +70,7 @@ func NewRecipeReference(name, version, user, channel, revision string) (*RecipeR
return nil, ErrValidation
}
return &RecipeReference{name, version, user, channel, revision}, nil
return &RecipeReference{name, v, user, channel, revision}, nil
}
func (r *RecipeReference) RevisionOrDefault() string {
+1
View File
@@ -34,6 +34,7 @@ func TestNewRecipeReference(t *testing.T) {
{"name", "1.0", "_", "_", "", true},
{"name", "1.0", "_", "_", "0", true},
{"name", "1.0", "", "", "0", true},
{"name", "1.0.0q", "", "", "0", true},
{"name", "1.0", "", "", "000000000000000000000000000000000000000000000000000000000000", false},
}
+1
View File
@@ -16,6 +16,7 @@ import (
)
const (
PropertyRepository = "container.repository"
PropertyDigest = "container.digest"
PropertyMediaType = "container.mediatype"
PropertyManifestTagged = "container.manifest.tagged"
-1
View File
@@ -80,7 +80,6 @@ type gemspec struct {
VersionRequirements requirement `yaml:"version_requirements"`
} `yaml:"dependencies"`
Description string `yaml:"description"`
Email string `yaml:"email"`
Executables []string `yaml:"executables"`
Extensions []interface{} `yaml:"extensions"`
ExtraRdocFiles []string `yaml:"extra_rdoc_files"`
+7 -2
View File
@@ -11,6 +11,7 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
@@ -142,10 +143,14 @@ func sessionHandler(session ssh.Session) {
// Wait for the command to exit and log any errors we get
err = cmd.Wait()
if err != nil {
log.Error("SSH: Wait: %v", err)
// Cannot use errors.Is here because ExitError doesn't implement Is
// Thus errors.Is will do equality test NOT type comparison
if _, ok := err.(*exec.ExitError); !ok {
log.Error("SSH: Wait: %v", err)
}
}
if err := session.Exit(getExitStatusFromError(err)); err != nil {
if err := session.Exit(getExitStatusFromError(err)); err != nil && !errors.Is(err, io.EOF) {
log.Error("Session failed to exit. %s", err)
}
}
+1
View File
@@ -97,6 +97,7 @@ type SubmitPullReviewOptions struct {
// DismissPullReviewOptions are options to dismiss a pull review
type DismissPullReviewOptions struct {
Message string `json:"message"`
Priors bool `json:"priors"`
}
// PullReviewRequestOptions are options to add or remove pull review requests
+10
View File
@@ -70,6 +70,16 @@ func (ct SniffedType) IsRepresentableAsText() bool {
return ct.IsText() || ct.IsSvgImage()
}
// IsBrowsableType returns whether a non-text type can be displayed in a browser
func (ct SniffedType) IsBrowsableBinaryType() bool {
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio()
}
// GetMimeType returns the mime type
func (ct SniffedType) GetMimeType() string {
return strings.SplitN(ct.contentType, ";", 2)[0]
}
// DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty.
func DetectContentType(data []byte) SniffedType {
if len(data) == 0 {
+1
View File
@@ -3038,6 +3038,7 @@ title = Packages
desc = Manage repository packages.
empty = There are no packages yet.
empty.documentation = For more information on the package registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">the documentation</a>.
empty.repo = Did you upload a package, but it's not shown here? Go to <a href="%[1]s">package settings</a> and link it to this repo.
filter.type = Type
filter.type.all = All
filter.no_result = Your filter produced no results.
+1
View File
@@ -257,6 +257,7 @@ func ContainerRoutes() *web.Route {
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Get("/token", container.Authenticate)
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {
r.Group("/blobs/uploads", func() {
+1 -1
View File
@@ -88,7 +88,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
for _, pd := range pds {
packageType := ""
for _, pvp := range pd.Properties {
for _, pvp := range pd.VersionProperties {
if pvp.Name == composer_module.TypeProperty {
packageType = pvp.Value
break
+1 -1
View File
@@ -225,7 +225,7 @@ func UploadPackage(ctx *context.Context) {
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: cp.Metadata,
Properties: map[string]string{
VersionProperties: map[string]string{
composer_module.TypeProperty: cp.Type,
},
},
+11 -1
View File
@@ -29,6 +29,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
contentStore := packages_module.NewContentStore()
err := db.WithTx(func(ctx context.Context) error {
created := true
p := &packages_model.Package{
OwnerID: pi.Owner.ID,
Type: packages_model.TypeContainer,
@@ -37,12 +38,21 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
}
var err error
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if err != packages_model.ErrDuplicatePackage {
if err == packages_model.ErrDuplicatePackage {
created = false
} else {
log.Error("Error inserting package: %v", err)
return err
}
}
if created {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(pi.Owner.LowerName+"/"+pi.Name)); err != nil {
log.Error("Error setting package property: %v", err)
return err
}
}
pv := &packages_model.PackageVersion{
PackageID: p.ID,
CreatorID: pi.Owner.ID,
+34 -1
View File
@@ -112,7 +112,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost for anonymous access)
func ReqContainerAccess(ctx *context.Context) {
if ctx.Doer == nil {
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token"`)
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
apiErrorDefined(ctx, errUnauthorized)
}
}
@@ -151,6 +151,39 @@ func Authenticate(ctx *context.Context) {
})
}
// https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n")
if n <= 0 || n > 100 {
n = 100
}
last := ctx.FormTrim("last")
repositories, err := container_model.GetRepositories(ctx, ctx.Doer, n, last)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
type RepositoryList struct {
Repositories []string `json:"repositories"`
}
if len(repositories) == n {
v := url.Values{}
if n > 0 {
v.Add("n", strconv.Itoa(n))
}
v.Add("last", repositories[len(repositories)-1])
ctx.Resp.Header().Set("Link", fmt.Sprintf(`</v2/_catalog?%s>; rel="next"`, v.Encode()))
}
jsonResponse(ctx, http.StatusOK, RepositoryList{
Repositories: repositories,
})
}
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#single-post
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
+11 -1
View File
@@ -267,6 +267,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
}
func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, metadata *container_module.Metadata) (*packages_model.PackageVersion, error) {
created := true
p := &packages_model.Package{
OwnerID: mci.Owner.ID,
Type: packages_model.TypeContainer,
@@ -275,12 +276,21 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
}
var err error
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if err != packages_model.ErrDuplicatePackage {
if err == packages_model.ErrDuplicatePackage {
created = false
} else {
log.Error("Error inserting package: %v", err)
return nil, err
}
}
if created {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil {
log.Error("Error setting package property: %v", err)
return nil, err
}
}
metadata.IsTagged = mci.IsTagged
metadataJSON, err := json.Marshal(metadata)
+6 -8
View File
@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
"regexp"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
@@ -15,8 +16,6 @@ import (
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version"
)
var (
@@ -97,8 +96,7 @@ func UploadPackage(ctx *context.Context) {
Name: packageName,
Version: packageVersion,
},
SemverCompatible: true,
Creator: ctx.Doer,
Creator: ctx.Doer,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
@@ -157,10 +155,10 @@ func sanitizeParameters(ctx *context.Context) (string, string, string, error) {
return "", "", "", errors.New("Invalid package name or filename")
}
v, err := version.NewSemver(ctx.Params("packageversion"))
if err != nil {
return "", "", "", err
packageVersion := strings.TrimSpace(ctx.Params("packageversion"))
if packageVersion == "" {
return "", "", "", errors.New("Invalid package version")
}
return packageName, v.String(), filename, nil
return packageName, packageVersion, filename, nil
}
+1 -1
View File
@@ -25,7 +25,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac
for _, pd := range pds {
versions[pd.SemVer.String()] = createPackageMetadataVersion(registryURL, pd)
for _, pvp := range pd.Properties {
for _, pvp := range pd.VersionProperties {
if pvp.Name == npm_module.TagProperty {
distTags[pvp.Value] = pd.Version.Version
}
+4 -4
View File
@@ -823,7 +823,7 @@ func DismissPullReview(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
opts := web.GetForm(ctx).(*api.DismissPullReviewOptions)
dismissReview(ctx, opts.Message, true)
dismissReview(ctx, opts.Message, true, opts.Priors)
}
// UnDismissPullReview cancel to dismiss a review for a pull request
@@ -863,10 +863,10 @@ func UnDismissPullReview(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
dismissReview(ctx, "", false)
dismissReview(ctx, "", false, false)
}
func dismissReview(ctx *context.APIContext, msg string, isDismiss bool) {
func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
if !ctx.Repo.IsAdmin() {
ctx.Error(http.StatusForbidden, "", "Must be repo admin")
return
@@ -886,7 +886,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss bool) {
return
}
_, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss)
_, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors)
if err != nil {
ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
return
+57 -39
View File
@@ -7,12 +7,13 @@ package common
import (
"fmt"
"io"
"net/url"
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/modules/charset"
charsetModule "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
@@ -42,7 +43,7 @@ func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) err
}
// ServeData download file from io.Reader
func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error {
func ServeData(ctx *context.Context, filePath string, size int64, reader io.Reader) error {
buf := make([]byte, 1024)
n, err := util.ReadAtMost(reader, buf)
if err != nil {
@@ -52,56 +53,73 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
buf = buf[:n]
}
ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute)
if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", name, size)
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}
name = path.Base(name)
// Google Chrome dislike commas in filenames, so let's change it to a space
name = strings.ReplaceAll(name, ",", " ")
fileName := path.Base(filePath)
sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")
mimeType := ""
charset := ""
st := typesniffer.DetectContentType(buf)
mappedMimeType := ""
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(name))
mappedMimeType = setting.MimeTypeMap.Map[fileExtension]
fileExtension := strings.ToLower(filepath.Ext(fileName))
mimeType = setting.MimeTypeMap.Map[fileExtension]
}
if st.IsText() || ctx.FormBool("render") {
cs, err := charset.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err)
cs = "utf-8"
}
if mappedMimeType == "" {
mappedMimeType = "text/plain"
}
ctx.Resp.Header().Set("Content-Type", mappedMimeType+"; charset="+strings.ToLower(cs))
} else {
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
if mappedMimeType != "" {
ctx.Resp.Header().Set("Content-Type", mappedMimeType)
}
if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
if st.IsSvgImage() || st.IsPDF() {
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
if st.IsSvgImage() {
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType)
} else {
ctx.Resp.Header().Set("Content-Type", typesniffer.ApplicationOctetStream)
}
}
if mimeType == "" {
if sniffedType.IsBrowsableBinaryType() {
mimeType = sniffedType.GetMimeType()
} else if isPlain {
mimeType = "text/plain"
} else {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
mimeType = typesniffer.ApplicationOctetStream
}
}
if isPlain {
charset, err = charsetModule.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
charset = "utf-8"
}
}
if charset != "" {
ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset))
} else {
ctx.Resp.Header().Set("Content-Type", mimeType)
}
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
isSVG := sniffedType.IsSvgImage()
// serve types that can present a security risk with CSP
if isSVG {
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
} else if sniffedType.IsPDF() {
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
// should generally be safe as scripts inside PDF can not escape the PDF document
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
}
disposition := "inline"
if isSVG && !setting.UI.SVG.Enabled {
disposition = "attachment"
}
// encode filename per https://datatracker.ietf.org/doc/html/rfc5987
encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName)
ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName)
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
_, err = ctx.Resp.Write(buf)
if err != nil {
return err
+2
View File
@@ -9,6 +9,7 @@ import (
"net/http"
"path"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
@@ -62,6 +63,7 @@ func installRecovery() func(next http.Handler) http.Handler {
"SignedUserName": "",
}
httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
if !setting.IsProd {
+1 -2
View File
@@ -5,7 +5,6 @@
package auth
import (
"encoding/base32"
"errors"
"net/http"
@@ -132,7 +131,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
}
// Success! Get the credential and update the sign count with the new value we received.
dbCred, err := auth.GetWebAuthnCredentialByCredID(user.ID, base32.HexEncoding.EncodeToString(cred.ID))
dbCred, err := auth.GetWebAuthnCredentialByCredID(user.ID, cred.ID)
if err != nil {
ctx.ServerError("GetWebAuthnCredentialByCredID", err)
return
+1
View File
@@ -158,6 +158,7 @@ func Recovery() func(next http.Handler) http.Handler {
store["SignedUserName"] = ""
}
httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
if !setting.IsProd {
+7
View File
@@ -24,6 +24,7 @@ import (
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/org"
container_service "code.gitea.io/gitea/services/packages/container"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
)
@@ -88,6 +89,12 @@ func SettingsPost(ctx *context.Context) {
}
return
}
if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil {
ctx.ServerError("UpdateRepositoryNames", err)
return
}
// reset ctx.org.OrgLink with new name
ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name)
log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
+4
View File
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
@@ -60,6 +61,9 @@ func Packages(ctx *context.Context) {
ctx.Data["Query"] = query
ctx.Data["PackageType"] = packageType
ctx.Data["HasPackages"] = hasPackages
if ctx.Repo != nil {
ctx.Data["CanWritePackages"] = ctx.IsUserRepoWriter([]unit.Type{unit.TypePackages}) || ctx.IsUserSiteAdmin()
}
ctx.Data["PackageDescriptors"] = pds
ctx.Data["Total"] = total
ctx.Data["RepositoryAccessMap"] = map[int64]bool{ctx.Repo.Repository.ID: true} // There is only the current repository
+1 -1
View File
@@ -242,7 +242,7 @@ func SubmitReview(ctx *context.Context) {
// DismissReview dismissing stale review by repo admin
func DismissReview(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.DismissReviewForm)
comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true)
comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true)
if err != nil {
ctx.ServerError("pull_service.DismissReview", err)
return
+1 -1
View File
@@ -474,7 +474,7 @@ func SettingsPost(ctx *context.Context) {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
}
if form.EnablePackages && !unit_model.TypeProjects.UnitGlobalDisabled() {
if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
Type: unit_model.TypePackages,
+8 -4
View File
@@ -902,10 +902,14 @@ func renderCode(ctx *context.Context) {
ctx.Data["PageIsViewCode"] = true
if ctx.Repo.Repository.IsEmpty {
reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty()
if err != nil {
ctx.ServerError("GitRepo.IsEmpty", err)
return
reallyEmpty := true
var err error
if ctx.Repo.GitRepo != nil {
reallyEmpty, err = ctx.Repo.GitRepo.IsEmpty()
if err != nil {
ctx.ServerError("GitRepo.IsEmpty", err)
return
}
}
if reallyEmpty {
ctx.HTML(http.StatusOK, tplRepoEMPTY)
+1
View File
@@ -591,6 +591,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
LabelIDs: opts.LabelIDs,
Org: org,
Team: team,
RepoCond: opts.RepoCond,
}
issueStats, err = issues_model.GetUserIssueStats(statsOpts)
+16
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/perm"
@@ -91,6 +92,21 @@ func ListPackages(ctx *context.Context) {
ctx.Data["Total"] = total
ctx.Data["RepositoryAccessMap"] = repositoryAccessMap
// TODO: context/org -> HandleOrgAssignment() can not be used
if ctx.ContextUser.IsOrganization() {
org := org_model.OrgFromUser(ctx.ContextUser)
ctx.Data["Org"] = org
ctx.Data["OrgLink"] = ctx.ContextUser.OrganisationLink()
if ctx.Doer != nil {
ctx.Data["IsOrganizationMember"], _ = org_model.IsOrganizationMember(ctx, org.ID, ctx.Doer.ID)
ctx.Data["IsOrganizationOwner"], _ = org_model.IsOrganizationOwner(ctx, org.ID, ctx.Doer.ID)
} else {
ctx.Data["IsOrganizationMember"] = false
ctx.Data["IsOrganizationOwner"] = false
}
}
pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
pager.AddParam(ctx, "q", "Query")
pager.AddParam(ctx, "type", "PackageType")
+6
View File
@@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/agit"
"code.gitea.io/gitea/services/forms"
container_service "code.gitea.io/gitea/services/packages/container"
user_service "code.gitea.io/gitea/services/user"
)
@@ -90,6 +91,11 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
return err
}
if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
ctx.ServerError("UpdateRepositoryNames", err)
return err
}
log.Trace("User name changed: %s -> %s", user.Name, newName)
return nil
}
+25
View File
@@ -6,10 +6,13 @@ package container
import (
"context"
"strings"
"time"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util"
)
@@ -78,3 +81,25 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
return nil
}
// UpdateRepositoryNames updates the repository name property for all packages of the specific owner
func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwnerName string) error {
ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeContainer)
if err != nil {
return err
}
newOwnerName = strings.ToLower(newOwnerName)
for _, p := range ps {
if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
return err
}
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, newOwnerName+"/"+p.LowerName); err != nil {
return err
}
}
return nil
}
+34 -12
View File
@@ -32,10 +32,11 @@ type PackageInfo struct {
// PackageCreationInfo describes a package to create
type PackageCreationInfo struct {
PackageInfo
SemverCompatible bool
Creator *user_model.User
Metadata interface{}
Properties map[string]string
SemverCompatible bool
Creator *user_model.User
Metadata interface{}
PackageProperties map[string]string
VersionProperties map[string]string
}
// PackageFileInfo describes a package file
@@ -108,8 +109,9 @@ func createPackageAndAddFile(pvci *PackageCreationInfo, pfci *PackageFileCreatio
}
func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, bool, error) {
log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.Properties, allowDuplicate)
log.Trace("Creating package: %v, %v, %v, %s, %s, %+v, %+v, %v", pvci.Creator.ID, pvci.Owner.ID, pvci.PackageType, pvci.Name, pvci.Version, pvci.PackageProperties, pvci.VersionProperties, allowDuplicate)
packageCreated := true
p := &packages_model.Package{
OwnerID: pvci.Owner.ID,
Type: pvci.PackageType,
@@ -119,18 +121,29 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
var err error
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
if err != packages_model.ErrDuplicatePackage {
if err == packages_model.ErrDuplicatePackage {
packageCreated = false
} else {
log.Error("Error inserting package: %v", err)
return nil, false, err
}
}
if packageCreated {
for name, value := range pvci.PackageProperties {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, name, value); err != nil {
log.Error("Error setting package property: %v", err)
return nil, false, err
}
}
}
metadataJSON, err := json.Marshal(pvci.Metadata)
if err != nil {
return nil, false, err
}
created := true
versionCreated := true
pv := &packages_model.PackageVersion{
PackageID: p.ID,
CreatorID: pvci.Creator.ID,
@@ -140,7 +153,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
if err == packages_model.ErrDuplicatePackageVersion {
created = false
versionCreated = false
}
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
log.Error("Error inserting package: %v", err)
@@ -148,8 +161,8 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
}
if created {
for name, value := range pvci.Properties {
if versionCreated {
for name, value := range pvci.VersionProperties {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, name, value); err != nil {
log.Error("Error setting package version property: %v", err)
return nil, false, err
@@ -157,7 +170,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}
}
return pv, created, nil
return pv, versionCreated, nil
}
// AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned
@@ -348,9 +361,18 @@ func Cleanup(unused context.Context, olderThan time.Duration) error {
return err
}
if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil {
ps, err := packages_model.FindUnreferencedPackages(ctx)
if err != nil {
return err
}
for _, p := range ps {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil {
return err
}
if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil {
return err
}
}
pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
if err != nil {
+3 -1
View File
@@ -124,6 +124,7 @@ func (e *errMergeConflict) Error() string {
}
func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, gitRepo *git.Repository) error {
log.Trace("Attempt to merge:\n%v", file)
switch {
case file.stage1 != nil && (file.stage2 == nil || file.stage3 == nil):
// 1. Deleted in one or both:
@@ -295,7 +296,8 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
var treeHash string
treeHash, _, err = git.NewCommand(ctx, "write-tree").RunStdString(&git.RunOpts{Dir: tmpBasePath})
if err != nil {
return false, err
lsfiles, _, _ := git.NewCommand(ctx, "ls-files", "-u").RunStdString(&git.RunOpts{Dir: tmpBasePath})
return false, fmt.Errorf("unable to write unconflicted tree: %w\n`git ls-files -u`:\n%s", err, lsfiles)
}
treeHash = strings.TrimSpace(treeHash)
baseTree, err := gitRepo.GetTree("base")
+24 -1
View File
@@ -42,6 +42,17 @@ func (line *lsFileLine) SameAs(other *lsFileLine) bool {
line.path == other.path
}
// String provides a string representation for logging
func (line *lsFileLine) String() string {
if line == nil {
return "<nil>"
}
if line.err != nil {
return fmt.Sprintf("%d %s %s %s %v", line.stage, line.mode, line.path, line.sha, line.err)
}
return fmt.Sprintf("%d %s %s %s", line.stage, line.mode, line.path, line.sha)
}
// readUnmergedLsFileLines calls git ls-files -u -z and parses the lines into mode-sha-stage-path quadruplets
// it will push these to the provided channel closing it at the end
func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan chan *lsFileLine) {
@@ -118,6 +129,17 @@ type unmergedFile struct {
err error
}
// String provides a string representation of the an unmerged file for logging
func (u *unmergedFile) String() string {
if u == nil {
return "<nil>"
}
if u.err != nil {
return fmt.Sprintf("error: %v\n%v\n%v\n%v", u.err, u.stage1, u.stage2, u.stage3)
}
return fmt.Sprintf("%v\n%v\n%v", u.stage1, u.stage2, u.stage3)
}
// unmergedFiles will collate the output from readUnstagedLsFileLines in to file triplets and send them
// to the provided channel, closing at the end.
func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmergedFile) {
@@ -138,6 +160,7 @@ func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmer
next := &unmergedFile{}
for line := range lsFileLineChan {
log.Trace("Got line: %v Current State:\n%v", line, next)
if line.err != nil {
log.Error("Unable to run ls-files -u -z! Error: %v", line.err)
unmerged <- &unmergedFile{err: fmt.Errorf("unable to run ls-files -u -z! Error: %v", line.err)}
@@ -149,7 +172,7 @@ func unmergedFiles(ctx context.Context, tmpBasePath string, unmerged chan *unmer
case 0:
// Should not happen as this represents successfully merged file - we will tolerate and ignore though
case 1:
if next.stage1 != nil {
if next.stage1 != nil || next.stage2 != nil || next.stage3 != nil {
// We need to handle the unstaged file stage1,stage2,stage3
unmerged <- next
}
+18 -1
View File
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// CreateCodeComment creates a comment on the code line
@@ -271,7 +272,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
}
// DismissReview dismissing stale review by repo admin
func DismissReview(ctx context.Context, reviewID, repoID int64, message string, doer *user_model.User, isDismiss bool) (comment *issues_model.Comment, err error) {
func DismissReview(ctx context.Context, reviewID, repoID int64, message string, doer *user_model.User, isDismiss, dismissPriors bool) (comment *issues_model.Comment, err error) {
review, err := issues_model.GetReviewByID(ctx, reviewID)
if err != nil {
return
@@ -295,6 +296,22 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
return
}
if dismissPriors {
reviews, err := issues_model.GetReviews(ctx, &issues_model.GetReviewOptions{
IssueID: review.IssueID,
ReviewerID: review.ReviewerID,
Dismissed: util.OptionalBoolFalse,
})
if err != nil {
return nil, err
}
for _, oldReview := range reviews {
if err = issues_model.DismissReview(oldReview, true); err != nil {
return nil, err
}
}
}
if !isDismiss {
return nil, nil
}
+1 -1
View File
@@ -114,7 +114,7 @@
</div>
</div>
<a href="{{AppSubUrl}}/notifications" class="item tooltip not-mobile" data-content='{{.i18n.Tr "notifications"}}'>
<a href="{{AppSubUrl}}/notifications" class="item tooltip not-mobile" data-content="{{.i18n.Tr "notifications"}}" aria-label="{{.i18n.Tr "notifications"}}">
<span class="text">
<span class="fitted">{{svg "octicon-bell"}}</span>
<span class="ui red label {{if not $notificationUnreadCount}}hidden{{end}} notification_count">
+4
View File
@@ -47,6 +47,10 @@
<div class="empty center">
{{svg "octicon-package" 32}}
<h2>{{.i18n.Tr "packages.empty"}}</h2>
{{if and .Repository .CanWritePackages}}
{{$packagesUrl := URLJoin .Owner.HTMLURL "-" "packages" }}
<p>{{.i18n.Tr "packages.empty.repo" $packagesUrl | Safe}}</p>
{{end}}
<p>{{.i18n.Tr "packages.empty.documentation" | Safe}}</p>
</div>
{{else}}
+1 -1
View File
@@ -19,6 +19,6 @@
document.getElementById('repo-clone-url').value = btn ? btn.getAttribute('data-link') : '';
})();
</script>
<button class="ui basic icon button tooltip" id="clipboard-btn" data-content="{{.i18n.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url">
<button class="ui basic icon button tooltip" id="clipboard-btn" data-content="{{.i18n.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{.i18n.Tr "copy_url"}}">
{{svg "octicon-paste"}}
</button>
+4
View File
@@ -14803,6 +14803,10 @@
"message": {
"type": "string",
"x-go-name": "Message"
},
"priors": {
"type": "boolean",
"x-go-name": "Priors"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
+41 -7
View File
@@ -1,14 +1,22 @@
<div class="header-wrapper">
<div class="ui container">
<div class="repo-header">
<div class="repo-title-wrap df fc">
<div class="repo-title">
{{avatar .ContextUser 32}}
<a href="{{.ContextUser.HTMLURL}}">{{.ContextUser.Name}}</a>
<!-- TODO: make template org and user can share -->
{{with .ContextUser}}
<div class="ui container">
<div class="ui vertically grid head">
<div class="column">
<div class="ui header">
{{avatar . 100}}
<span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span>
<span class="org-visibility">
{{if .Visibility.IsLimited}}<div class="ui medium orange horizontal label">{{$.i18n.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
{{if .Visibility.IsPrivate}}<div class="ui medium red horizontal label">{{$.i18n.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
</span>
</div>
</div>
</div>
</div>
</div>
{{end}}
<div class="ui tabs container">
<div class="ui tabular stackable menu navbar">
<a class="item" href="{{.ContextUser.HomeLink}}">
@@ -19,6 +27,32 @@
{{svg "octicon-package"}} {{.i18n.Tr "packages.title"}}
</a>
{{end}}
{{if .ContextUser.IsOrganization}}
{{if .IsOrganizationMember}}
<a class="item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization"}}&nbsp;{{$.i18n.Tr "org.people"}}
{{if .NumMembers}}
<div class="ui primary label">{{.NumMembers}}</div>
{{end}}
</a>
<a class="item" href="{{$.OrgLink}}/teams">
{{svg "octicon-people"}}&nbsp;{{$.i18n.Tr "org.teams"}}
{{if .NumTeams}}
<div class="ui black label">{{.NumTeams}}</div>
{{end}}
</a>
{{end}}
{{if .IsOrganizationOwner}}
<div class="right menu">
<a class="item" href="{{.OrgLink}}/settings">
{{svg "octicon-tools"}} {{.i18n.Tr "repo.settings"}}
</a>
</div>
{{end}}
{{end}}
</div>
</div>
<div class="ui tabs divider"></div>
+2 -1
View File
@@ -389,7 +389,8 @@ export function initGlobalButtons() {
*/
export function checkAppUrl() {
const curUrl = window.location.href;
if (curUrl.startsWith(appUrl)) {
// some users visit "https://domain/gitea" while appUrl is "https://domain/gitea/", there should be no warning
if (curUrl.startsWith(appUrl) || `${curUrl}/` === appUrl) {
return;
}
if (document.querySelector('.page-content.install')) {
+4 -6
View File
@@ -28,14 +28,12 @@ async function receiveUpdateCount(event) {
try {
const data = JSON.parse(event.data);
const notificationCount = document.querySelector('.notification_count');
if (data.Count > 0) {
notificationCount.classList.remove('hidden');
} else {
notificationCount.classList.add('hidden');
const notificationCounts = document.querySelectorAll('.notification_count');
for (const count of notificationCounts) {
count.classList.toggle('hidden', data.Count === 0);
count.textContent = `${data.Count}`;
}
notificationCount.textContent = `${data.Count}`;
await updateNotificationTable();
} catch (error) {
console.error(error, event);
+4
View File
@@ -2204,5 +2204,9 @@ table th[data-sortt-desc] {
> .dropdown.item {
position: initial;
}
.menu {
flex-direction: row;
}
}
}
+6
View File
@@ -81,6 +81,12 @@
.ui.dropdown {
max-width: 100%;
@media @mediaSm {
> .menu {
position: static;
}
}
}
}
}