mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-10 15:32:55 +09:00
Compare commits
175 Commits
v1.17.0-rc
...
v1.17.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
32eef4aa2e | ||
|
|
449b39ea0e | ||
|
|
06f968d662 | ||
|
|
084797b4dc | ||
|
|
7888a55e8c | ||
|
|
ea416d7d0e | ||
|
|
0db6add5c0 | ||
|
|
0ecbb71bee | ||
|
|
ea38455e1f | ||
|
|
8fc80b34a0 | ||
|
|
71aa64ae25 | ||
|
|
3aba72c613 | ||
|
|
bd1412c3af | ||
|
|
3973ce36d9 | ||
|
|
fbde31fb1e | ||
|
|
2f0a1eb0d5 | ||
|
|
e3697efbb0 | ||
|
|
989dd5502c | ||
|
|
54c0fe62cc | ||
|
|
2e2133d33f | ||
|
|
0d869c574e | ||
|
|
04105dbb7c | ||
|
|
0a0cd75071 | ||
|
|
85f829fb3c | ||
|
|
5ebd26d306 | ||
|
|
bc7a4375be | ||
|
|
fbcb42488f | ||
|
|
0230f1e1aa | ||
|
|
6779c351b1 | ||
|
|
c1889f5b01 | ||
|
|
c0754e9d19 | ||
|
|
bf41958c16 | ||
|
|
033178f2fc | ||
|
|
ebc8801fb2 | ||
|
|
37458bffbf | ||
|
|
ec9b43ba16 | ||
|
|
e6ec411491 | ||
|
|
17d3a474e0 | ||
|
|
9e8b1c6630 | ||
|
|
eee51d8366 | ||
|
|
c61ed6fad4 | ||
|
|
b88a4b4854 | ||
|
|
399917a2d4 | ||
|
|
68cceb5321 | ||
|
|
15b61dac98 | ||
|
|
35ca651c80 | ||
|
|
737486152c | ||
|
|
c40c753613 | ||
|
|
7a9b01a2dd | ||
|
|
b43d7e1254 | ||
|
|
987798a3a9 | ||
|
|
13b74accda | ||
|
|
79fa1c15a4 | ||
|
|
78dabdd9ae | ||
|
|
e5d2031828 | ||
|
|
c3b4f3f7e9 | ||
|
|
9bccfe9856 | ||
|
|
85034564c2 | ||
|
|
eacab6b10d | ||
|
|
ac9792c0c7 | ||
|
|
f7c874cb1a | ||
|
|
d19c2c9fcb | ||
|
|
59228d8a71 | ||
|
|
67701771af | ||
|
|
113d13a026 | ||
|
|
9ec1c8812e | ||
|
|
e1e43333cf | ||
|
|
cedf4fef0a | ||
|
|
a04fc567b4 | ||
|
|
92d79b556b | ||
|
|
65176fdaf3 | ||
|
|
aac905dcfb | ||
|
|
5ce8fdbc37 | ||
|
|
76accb51ed | ||
|
|
bd2218e14c | ||
|
|
0747592865 | ||
|
|
07d140625e | ||
|
|
a6c2a1a117 | ||
|
|
56b99551ae | ||
|
|
51c8c0f3fe | ||
|
|
8769df117d | ||
|
|
09f2e1e1a2 | ||
|
|
eeb490c7ab | ||
|
|
97a8c96c5b | ||
|
|
d1e53bfd7f | ||
|
|
fc7b5afd9b | ||
|
|
210b096da7 | ||
|
|
d6bc1558c6 | ||
|
|
6986e56791 | ||
|
|
ae86a0bc9f | ||
|
|
4b53a5c3a1 | ||
|
|
da10ce8b07 | ||
|
|
4ed32e79b6 | ||
|
|
fa46d66835 | ||
|
|
648ec3cfed | ||
|
|
a9a440e600 | ||
|
|
39b2ede930 | ||
|
|
0a32bd56eb | ||
|
|
e0f35ea00f | ||
|
|
bed13bfa9e | ||
|
|
435038b2c6 | ||
|
|
2fe0dab2d5 | ||
|
|
e930d66a9c | ||
|
|
2c93bd79f2 | ||
|
|
90b4a9e929 | ||
|
|
c16f0d2a19 | ||
|
|
3f5d72709f | ||
|
|
95a27eb662 | ||
|
|
c91b8c8089 | ||
|
|
975a962a2f | ||
|
|
4c1f4ee84c | ||
|
|
780b198997 | ||
|
|
f4e219f668 | ||
|
|
92a43d577d | ||
|
|
66686f6d0e | ||
|
|
26f4fe2b44 | ||
|
|
b8ab9298e1 | ||
|
|
54ef658861 | ||
|
|
c556a83c35 | ||
|
|
317c565e77 | ||
|
|
1d02a9c9fb | ||
|
|
d371ced49d | ||
|
|
5e5ff77ed7 | ||
|
|
039a60225a | ||
|
|
654c173b9d | ||
|
|
a92d247fdd | ||
|
|
42be548ecc | ||
|
|
76ba23a14f | ||
|
|
c88a59bb23 | ||
|
|
01a4fb0ae6 | ||
|
|
f42fc3b287 | ||
|
|
35fd55c7df | ||
|
|
e321b40bb0 | ||
|
|
d22826a28e | ||
|
|
bf43db10a9 | ||
|
|
3e4fe009e7 | ||
|
|
1ffc700777 | ||
|
|
0dab13884a | ||
|
|
0b7b342ab0 | ||
|
|
fb5ca1bf64 | ||
|
|
764e75d9b9 | ||
|
|
05464ac2a5 | ||
|
|
dbafb4f4d4 | ||
|
|
29ac31628c |
162
CHANGELOG.md
162
CHANGELOG.md
@@ -4,7 +4,126 @@ 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-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.17.0-rc1) - 2022-06-18
|
||||
## [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
|
||||
* Double check CloneURL is acceptable (#20869) (#20892)
|
||||
* Add more checks in migration code (#21011) (#21050)
|
||||
* ENHANCEMENTS
|
||||
* Fix hard-coded timeout and error panic in API archive download endpoint (#20925) (#21051)
|
||||
* Improve arc-green code theme (#21039) (#21042)
|
||||
* Enable contenthash in filename for dynamic assets (#20813) (#20932)
|
||||
* Don't open new page for ext wiki on same repository (#20725) (#20910)
|
||||
* Disable doctor logging on panic (#20847) (#20898)
|
||||
* Remove calls to load Mirrors in user.Dashboard (#20855) (#20897)
|
||||
* Update codemirror to 5.65.8 (#20875)
|
||||
* Rework repo buttons (#20602, #20718) (#20719)
|
||||
* BUGFIXES
|
||||
* Ensure delete user deletes all comments (#21067) (#21068)
|
||||
* Delete unreferenced packages when deleting a package version (#20977) (#21060)
|
||||
* Redirect if user does not exist on admin pages (#20981) (#21059)
|
||||
* Set uploadpack.allowFilter etc on gitea serv to enable partial clones with ssh (#20902) (#21058)
|
||||
* Fix 500 on time in timeline API (#21052) (#21057)
|
||||
* Fill the specified ref in webhook test payload (#20961) (#21055)
|
||||
* Add another index for Action table on postgres (#21033) (#21054)
|
||||
* Fix broken insecureskipverify handling in redis connection uris (#20967) (#21053)
|
||||
* Add Dev, Peer and Optional dependencies to npm PackageMetadataVersion (#21017) (#21044)
|
||||
* Do not add links to Posters or Assignees with ID < 0 (#20577) (#21037)
|
||||
* Fix modified due date message (#20388) (#21032)
|
||||
* Fix missed sort bug (#21006)
|
||||
* Fix input.value attr for RequiredClaimName/Value (#20946) (#21001)
|
||||
* Change review buttons to icons to make space for text (#20934) (#20978)
|
||||
* Fix download archiver of a commit (#20962) (#20971)
|
||||
* Return 404 NotFound if requested attachment does not exist (#20886) (#20941)
|
||||
* Set no-tags in git fetch on compare (#20893) (#20936)
|
||||
* Allow multiple metadata files for Maven packages (#20674) (#20916)
|
||||
* Increase Content field size of gpg_key and public_key to MEDIUMTEXT (#20896) (#20911)
|
||||
* Fix mirror address setting not working (#20850) (#20904)
|
||||
* Fix push mirror address backend get error Address cause setting page display error (#20593) (#20901)
|
||||
* Fix panic when an invalid oauth2 name is passed (#20820) (#20900)
|
||||
* In PushMirrorsIterate and MirrorsIterate if limit is negative do not set it (#20837) (#20899)
|
||||
* Ensure that graceful start-up is informed of unused SSH listener (#20877) (#20888)
|
||||
* Pad GPG Key ID with preceding zeroes (#20878) (#20885)
|
||||
* Fix SQL Query for `SearchTeam` (#20844) (#20872)
|
||||
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
|
||||
* Fix UI mis-align for PR commit history (#20845) (#20859)
|
||||
|
||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
|
||||
|
||||
* SECURITY
|
||||
* Correctly escape within tribute.js (#20831) (#20832)
|
||||
* ENHANCEMENTS
|
||||
* Add support for NuGet API keys (#20721) (#20734)
|
||||
* Display project in issue list (#20583)
|
||||
* Add disable download source configuration (#20548) (#20579)
|
||||
* Add username check to doctor (#20140) (#20671)
|
||||
* Enable Wire 2 for Internal SSH Server (#20616) (#20617)
|
||||
* BUGFIXES
|
||||
* Use the total issue count for UI (#20785) (#20827)
|
||||
* Add proxy host into allow list (#20798) (#20819)
|
||||
* Add missing translation for queue flush workers (#20791) (#20792)
|
||||
* Improve comment header for mobile (#20781) (#20789)
|
||||
* Fix git.Init for doctor sub-command (#20782) (#20783)
|
||||
* Check webhooks slice length before calling xorm (#20642) (#20768)
|
||||
* Remove manual rollback for failed generated repositories (#20639) (#20762)
|
||||
* Use correct field name in npm template (#20675) (#20760)
|
||||
* Keep download count on Container tag overwrite (#20728) (#20735)
|
||||
* Fix v220 migration to be compatible for MSSQL 2008 r2 (#20702) (#20707)
|
||||
* Use request timeout for git service rpc (#20689) (#20693)
|
||||
* Send correct NuGet status codes (#20647) (#20677)
|
||||
* Use correct context to get package content (#20673) (#20676)
|
||||
* Fix the JS error "EventSource is not defined" caused by some non-standard browsers (#20584) (#20663)
|
||||
* Add default commit messages to PR for squash merge (#20618) (#20645)
|
||||
* Fix package upload for files >32mb (#20622) (#20635)
|
||||
* Fix the new-line copy-paste for rendered code (#20612)
|
||||
* Clean up and fix clone button script (#20415 & #20600) (#20599)
|
||||
* Fix default merge style (#20564) (#20565)
|
||||
* Add repository condition for issue count (#20454) (#20496)
|
||||
* Make branch icon stand out more (#20726) (#20774)
|
||||
* Fix loading button with invalid form (#20754) (#20759)
|
||||
* Fix SecToTime edge-cases (#20610) (#20611)
|
||||
* Executable check always returns true for windows (#20637) (#20835)
|
||||
* Check issue labels slice length before calling xorm Insert (#20655) (#20836)
|
||||
* Fix owners cannot create organization repos bug (#20841) (#20854)
|
||||
* Prevent 500 is head repo does not have PullRequest unit in IsUserAllowedToUpdate (#20839) (#20848)
|
||||
|
||||
## [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)
|
||||
@@ -29,6 +148,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)
|
||||
@@ -67,6 +188,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)
|
||||
@@ -111,7 +235,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)
|
||||
@@ -128,7 +251,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)
|
||||
@@ -185,13 +307,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)
|
||||
@@ -266,7 +419,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)
|
||||
|
||||
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
|
||||
`
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
golog "log"
|
||||
"os"
|
||||
@@ -123,6 +124,47 @@ func runRecreateTable(ctx *cli.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func setDoctorLogger(ctx *cli.Context) {
|
||||
logFile := ctx.String("log-file")
|
||||
if !ctx.IsSet("log-file") {
|
||||
logFile = "doctor.log"
|
||||
}
|
||||
colorize := log.CanColorStdout
|
||||
if ctx.IsSet("color") {
|
||||
colorize = ctx.Bool("color")
|
||||
}
|
||||
|
||||
if len(logFile) == 0 {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err, ok := recovered.(error)
|
||||
if !ok {
|
||||
panic(recovered)
|
||||
}
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n", logFile, err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file: %s\n %v\n", logFile, err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n")
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
}()
|
||||
|
||||
if logFile == "-" {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
} else {
|
||||
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
|
||||
}
|
||||
}
|
||||
|
||||
func runDoctor(ctx *cli.Context) error {
|
||||
// Silence the default loggers
|
||||
log.DelNamedLogger("console")
|
||||
@@ -132,24 +174,13 @@ func runDoctor(ctx *cli.Context) error {
|
||||
defer cancel()
|
||||
|
||||
// Now setup our own
|
||||
logFile := ctx.String("log-file")
|
||||
if !ctx.IsSet("log-file") {
|
||||
logFile = "doctor.log"
|
||||
}
|
||||
setDoctorLogger(ctx)
|
||||
|
||||
colorize := log.CanColorStdout
|
||||
if ctx.IsSet("color") {
|
||||
colorize = ctx.Bool("color")
|
||||
}
|
||||
|
||||
if len(logFile) == 0 {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
} else if logFile == "-" {
|
||||
log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize))
|
||||
} else {
|
||||
log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile))
|
||||
}
|
||||
|
||||
// Finally redirect the default golog to here
|
||||
golog.SetFlags(0)
|
||||
golog.SetPrefix("")
|
||||
|
||||
@@ -7,13 +7,17 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
@@ -83,6 +87,11 @@ func runDumpRepository(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// migrations.GiteaLocalUploader depends on git module
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
@@ -128,7 +137,9 @@ func runDumpRepository(ctx *cli.Context) error {
|
||||
} else {
|
||||
units := strings.Split(ctx.String("units"), ",")
|
||||
for _, unit := range units {
|
||||
switch strings.ToLower(unit) {
|
||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||
case "":
|
||||
continue
|
||||
case "wiki":
|
||||
opts.Wiki = true
|
||||
case "issues":
|
||||
@@ -145,13 +156,29 @@ func runDumpRepository(ctx *cli.Context) error {
|
||||
opts.Comments = true
|
||||
case "pull_requests":
|
||||
opts.PullRequests = true
|
||||
default:
|
||||
return errors.New("invalid unit: " + unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the repo_dir will be removed if error occurs in DumpRepository
|
||||
// make sure the directory doesn't exist or is empty, prevent from deleting user files
|
||||
repoDir := ctx.String("repo_dir")
|
||||
if exists, err := util.IsExist(repoDir); err != nil {
|
||||
return fmt.Errorf("unable to stat repo_dir %q: %v", repoDir, err)
|
||||
} else if exists {
|
||||
if isDir, _ := util.IsDir(repoDir); !isDir {
|
||||
return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir)
|
||||
}
|
||||
if dir, _ := os.ReadDir(repoDir); len(dir) > 0 {
|
||||
return fmt.Errorf("repo_dir %q is not empty", repoDir)
|
||||
}
|
||||
}
|
||||
|
||||
if err := migrations.DumpRepository(
|
||||
context.Background(),
|
||||
ctx.String("repo_dir"),
|
||||
repoDir,
|
||||
ctx.String("owner_name"),
|
||||
opts,
|
||||
); err != nil {
|
||||
|
||||
23
cmd/main_test.go
Normal file
23
cmd/main_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setting.LoadForTest()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
GiteaRootPath: "..",
|
||||
})
|
||||
}
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
@@ -25,13 +27,13 @@ import (
|
||||
var CmdMigrateStorage = cli.Command{
|
||||
Name: "migrate-storage",
|
||||
Usage: "Migrate the storage",
|
||||
Description: "This is a command for migrating storage.",
|
||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||
Action: runMigrateStorage,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "type, t",
|
||||
Value: "",
|
||||
Usage: "Kinds of files to migrate, currently only 'attachments' is supported",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages'",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "storage, s",
|
||||
@@ -80,34 +82,50 @@ var CmdMigrateStorage = cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func migrateAttachments(dstStorage storage.ObjectStorage) error {
|
||||
return repo_model.IterateAttachment(func(attach *repo_model.Attachment) error {
|
||||
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(attach *repo_model.Attachment) error {
|
||||
_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateLFS(dstStorage storage.ObjectStorage) error {
|
||||
return git_model.IterateLFS(func(mo *git_model.LFSMetaObject) error {
|
||||
func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(mo *git_model.LFSMetaObject) error {
|
||||
_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateAvatars(dstStorage storage.ObjectStorage) error {
|
||||
return user_model.IterateUser(func(user *user_model.User) error {
|
||||
func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(user *user_model.User) error {
|
||||
_, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateRepoAvatars(dstStorage storage.ObjectStorage) error {
|
||||
return repo_model.IterateRepository(func(repo *repo_model.Repository) error {
|
||||
func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(repo *repo_model.Repository) error {
|
||||
_, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath())
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migrateRepoArchivers(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(archiver *repo_model.RepoArchiver) error {
|
||||
p := archiver.RelativePath()
|
||||
_, err := storage.Copy(dstStorage, p, storage.RepoArchives, p)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func migratePackages(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.IterateObjects(ctx, func(pb *packages_model.PackageBlob) error {
|
||||
p := packages_module.KeyToRelativePath(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
_, err := storage.Copy(dstStorage, p, storage.Packages, p)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func runMigrateStorage(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
@@ -127,8 +145,6 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
goCtx := context.Background()
|
||||
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,13 +161,13 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
dstStorage, err = storage.NewLocalStorage(
|
||||
goCtx,
|
||||
stdCtx,
|
||||
storage.LocalStorageConfig{
|
||||
Path: p,
|
||||
})
|
||||
case string(storage.MinioStorageType):
|
||||
dstStorage, err = storage.NewMinioStorage(
|
||||
goCtx,
|
||||
stdCtx,
|
||||
storage.MinioStorageConfig{
|
||||
Endpoint: ctx.String("minio-endpoint"),
|
||||
AccessKeyID: ctx.String("minio-access-key-id"),
|
||||
@@ -162,35 +178,29 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||
UseSSL: ctx.Bool("minio-use-ssl"),
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("Unsupported storage type: %s", ctx.String("storage"))
|
||||
return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tp := strings.ToLower(ctx.String("type"))
|
||||
switch tp {
|
||||
case "attachments":
|
||||
if err := migrateAttachments(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
case "lfs":
|
||||
if err := migrateLFS(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
case "avatars":
|
||||
if err := migrateAvatars(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
case "repo-avatars":
|
||||
if err := migrateRepoAvatars(dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Unsupported storage: %s", ctx.String("type"))
|
||||
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
}
|
||||
|
||||
log.Warn("All files have been copied to the new placement but old files are still on the original placement.")
|
||||
tp := strings.ToLower(ctx.String("type"))
|
||||
if m, ok := migratedMethods[tp]; ok {
|
||||
if err := m(stdCtx, dstStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("%s files have successfully been copied to the new storage.", tp)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
|
||||
}
|
||||
|
||||
74
cmd/migrate_storage_test.go
Normal file
74
cmd/migrate_storage_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigratePackages(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
|
||||
|
||||
content := "package main\n\nfunc main() {\nfmt.Println(\"hi\")\n}\n"
|
||||
buf, err := packages_module.CreateHashedBufferFromReader(strings.NewReader(content), 1024)
|
||||
assert.NoError(t, err)
|
||||
defer buf.Close()
|
||||
|
||||
v, f, err := packages_service.CreatePackageAndAddFile(&packages_service.PackageCreationInfo{
|
||||
PackageInfo: packages_service.PackageInfo{
|
||||
Owner: creator,
|
||||
PackageType: packages.TypeGeneric,
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Creator: creator,
|
||||
SemverCompatible: true,
|
||||
VersionProperties: map[string]string{},
|
||||
}, &packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{
|
||||
Filename: "a.go",
|
||||
},
|
||||
Data: buf,
|
||||
IsLead: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, v)
|
||||
assert.NotNil(t, f)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
p, err := os.MkdirTemp(os.TempDir(), "migrated_packages")
|
||||
assert.NoError(t, err)
|
||||
|
||||
dstStorage, err := storage.NewLocalStorage(
|
||||
ctx,
|
||||
storage.LocalStorageConfig{
|
||||
Path: p,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = migratePackages(ctx, dstStorage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
entries, err := os.ReadDir(p)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, len(entries))
|
||||
assert.EqualValues(t, "01", entries[0].Name())
|
||||
assert.EqualValues(t, "tmp", entries[1].Name())
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package cmd
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
@@ -37,10 +38,10 @@ var CmdRestoreRepository = cli.Command{
|
||||
Value: "",
|
||||
Usage: "Restore destination repository name",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
cli.StringFlag{
|
||||
Name: "units",
|
||||
Value: nil,
|
||||
Usage: `Which items will be restored, one or more units should be repeated with this flag.
|
||||
Value: "",
|
||||
Usage: `Which items will be restored, one or more units should be separated as comma.
|
||||
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
@@ -55,13 +56,16 @@ func runRestoreRepository(c *cli.Context) error {
|
||||
defer cancel()
|
||||
|
||||
setting.LoadFromExisting()
|
||||
|
||||
var units []string
|
||||
if s := c.String("units"); s != "" {
|
||||
units = strings.Split(s, ",")
|
||||
}
|
||||
statusCode, errStr := private.RestoreRepo(
|
||||
ctx,
|
||||
c.String("repo_dir"),
|
||||
c.String("owner_name"),
|
||||
c.String("repo_name"),
|
||||
c.StringSlice("units"),
|
||||
units,
|
||||
c.Bool("validation"),
|
||||
)
|
||||
if statusCode == http.StatusOK {
|
||||
|
||||
@@ -617,7 +617,10 @@ ROUTER = console
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; The path of git executable. If empty, Gitea searches through the PATH environment.
|
||||
PATH =
|
||||
;PATH =
|
||||
;;
|
||||
;; The HOME directory for Git
|
||||
;HOME_PATH = %(APP_DATA_PATH)/home
|
||||
;;
|
||||
;; Disables highlight of added and removed changes
|
||||
;DISABLE_DIFF_HIGHLIGHT = false
|
||||
@@ -889,6 +892,9 @@ PATH =
|
||||
;; Allow deletion of unadopted repositories
|
||||
;ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES = false
|
||||
|
||||
;; Don't allow download source archive files from UI
|
||||
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[repository.editor]
|
||||
@@ -1242,7 +1248,7 @@ PATH =
|
||||
;; Define allowed algorithms and their minimum key length (use -1 to disable a type)
|
||||
;ED25519 = 256
|
||||
;ECDSA = 256
|
||||
;RSA = 2048
|
||||
;RSA = 2047 ; we allow 2047 here because an otherwise valid 2048 bit RSA key can be reported as having 2047 bit length
|
||||
;DSA = -1 ; set to 1024 to switch on
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -1687,7 +1693,7 @@ PATH =
|
||||
;ENABLED = true
|
||||
;;
|
||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
;ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip
|
||||
;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
|
||||
;;
|
||||
;; Max size of each file. Defaults to 4MB
|
||||
;MAX_SIZE = 4
|
||||
|
||||
@@ -5,7 +5,7 @@ mkdir -p ${HOME} && chmod 0700 ${HOME}
|
||||
if [ ! -w ${HOME} ]; then echo "${HOME} is not writable"; exit 1; fi
|
||||
|
||||
# Prepare custom folder
|
||||
mkdir -p ${GITEA_CUSTOM} && chmod 0500 ${GITEA_CUSTOM}
|
||||
mkdir -p ${GITEA_CUSTOM} && chmod 0700 ${GITEA_CUSTOM}
|
||||
|
||||
# Prepare temp folder
|
||||
mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP}
|
||||
|
||||
@@ -78,6 +78,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||
- `DEFAULT_BRANCH`: **main**: Default branch name of all repositories.
|
||||
- `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
|
||||
- `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
|
||||
- `DISABLE_DOWNLOAD_SOURCE_ARCHIVES`: **false**: Don't allow download source archive files from UI
|
||||
|
||||
### Repository - Editor (`repository.editor`)
|
||||
|
||||
@@ -620,7 +621,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
|
||||
|
||||
- `ED25519`: **256**
|
||||
- `ECDSA`: **256**
|
||||
- `RSA`: **2048**
|
||||
- `RSA`: **2047**: We set 2047 here because an otherwise valid 2048 RSA key can be reported as 2047 length.
|
||||
- `DSA`: **-1**: DSA is now disabled by default. Set to **1024** to re-enable but ensure you may need to reconfigure your SSHD provider
|
||||
|
||||
## Webhook (`webhook`)
|
||||
@@ -741,7 +742,7 @@ Default templates for project boards:
|
||||
## Issue and pull request attachments (`attachment`)
|
||||
|
||||
- `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
|
||||
- `ALLOWED_TYPES`: **.docx,.gif,.gz,.jpeg,.jpg,mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
- `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||
- `MAX_SIZE`: **4**: Maximum size (MB).
|
||||
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
|
||||
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||
@@ -947,6 +948,8 @@ Default templates for project boards:
|
||||
## Git (`git`)
|
||||
|
||||
- `PATH`: **""**: The path of Git executable. If empty, Gitea searches through the PATH environment.
|
||||
- `HOME_PATH`: **%(APP_DATA_PATH)/home**: The HOME directory for Git.
|
||||
This directory will be used to contain the `.gitconfig` and possible `.gnupg` directories that Gitea's git calls will use. If you can confirm Gitea is the only application running in this environment, you can set it to the normal home directory for Gitea user.
|
||||
- `DISABLE_DIFF_HIGHLIGHT`: **false**: Disables highlight of added and removed changes.
|
||||
- `MAX_GIT_DIFF_LINES`: **1000**: Max number of lines allowed of a single file in diff view.
|
||||
- `MAX_GIT_DIFF_LINE_CHARACTERS`: **5000**: Max character count per line highlighted in diff view.
|
||||
|
||||
@@ -97,10 +97,11 @@ repositories, `SIGNING_KEY=default` could be used to provide different
|
||||
signing keys on a per-repository basis. However, this is clearly not an
|
||||
ideal UI and therefore subject to change.
|
||||
|
||||
**Since 1.17**, Gitea runs git in its own home directory `[repository].ROOT` and uses its own config `{[repository].ROOT}/.gitconfig`.
|
||||
**Since 1.17**, Gitea runs git in its own home directory `[git].HOME_PATH` (default to `%(APP_DATA_PATH)/home`)
|
||||
and uses its own config `{[git].HOME_PATH}/.gitconfig`.
|
||||
If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`)
|
||||
or the Gitea internal git config `{[repository].ROOT}/.gitconfig`.
|
||||
Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[repository].ROOT`.
|
||||
or the Gitea internal git config `{[git].HOME_PATH}/.gitconfig`.
|
||||
Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[git].HOME_PATH`.
|
||||
|
||||
|
||||
### `INITIAL_COMMIT`
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -47,6 +47,8 @@ For example:
|
||||
dotnet nuget add source --name gitea --username testuser --password password123 https://gitea.example.com/api/packages/testuser/nuget/index.json
|
||||
```
|
||||
|
||||
You can add the source without credentials and use the [`--api-key`](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push) parameter when publishing packages. In this case you need to provide a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}).
|
||||
|
||||
## Publish a package
|
||||
|
||||
Publish a package by running the following command:
|
||||
|
||||
19
go.mod
19
go.mod
@@ -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
|
||||
@@ -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.18
|
||||
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
|
||||
@@ -85,24 +85,24 @@ require (
|
||||
github.com/urfave/cli v1.22.9
|
||||
github.com/xanzy/go-gitlab v0.64.0
|
||||
github.com/yohcop/openid-go v1.0.0
|
||||
github.com/yuin/goldmark v1.4.12
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||
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-20220425223048-2871e0cb64e4
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
|
||||
golang.org/x/text v0.3.8
|
||||
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
|
||||
mvdan.cc/xurls/v2 v2.4.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.11
|
||||
xorm.io/xorm v1.3.1
|
||||
xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -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
|
||||
|
||||
40
go.sum
40
go.sum
@@ -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=
|
||||
@@ -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.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
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=
|
||||
@@ -1540,8 +1540,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
|
||||
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU=
|
||||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||
@@ -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=
|
||||
@@ -1783,14 +1783,13 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
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=
|
||||
@@ -1825,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=
|
||||
@@ -1937,8 +1936,8 @@ 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-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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=
|
||||
@@ -1951,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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
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=
|
||||
@@ -2046,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=
|
||||
@@ -2414,5 +2412,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/builder v0.3.11 h1:naLkJitGyYW7ZZdncsh/JW+HF4HshmvTHTyUyPwJS00=
|
||||
xorm.io/builder v0.3.11/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.1 h1:z5egKrDoOLqZFhMjcGF4FBHiTmE5/feQoHclfhNidfM=
|
||||
xorm.io/xorm v1.3.1/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||
xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f h1:3NvNsM4lnttTsHpk8ODHqrwN1MCEjsO3bD/rpd8A47k=
|
||||
xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -264,11 +275,23 @@ func TestPackageContainer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite existing tag
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/manifests/%s", url, tag))
|
||||
addTokenAuthHeader(req, userToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, pv.DownloadCount)
|
||||
|
||||
// Overwrite existing tag should keep the download count
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent))
|
||||
addTokenAuthHeader(req, userToken)
|
||||
req.Header.Set("Content-Type", oci.MediaTypeDockerManifest)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
pv, err = packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, tag)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, pv.DownloadCount)
|
||||
})
|
||||
|
||||
t.Run("HeadManifest", func(t *testing.T) {
|
||||
@@ -330,7 +353,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 +386,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 +544,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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -42,7 +43,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)
|
||||
@@ -80,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)()
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ func TestPackageMaven(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusCreated)
|
||||
putFile(t, fmt.Sprintf("/%s/%s", packageVersion, filename), "test", http.StatusBadRequest)
|
||||
putFile(t, "/maven-metadata.xml", "test", http.StatusOK)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeMaven)
|
||||
@@ -135,12 +136,14 @@ func TestPackageMaven(t *testing.T) {
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pfs, 2)
|
||||
i := 0
|
||||
if strings.HasSuffix(pfs[1].Name, ".pom") {
|
||||
i = 1
|
||||
for _, pf := range pfs {
|
||||
if strings.HasSuffix(pf.Name, ".pom") {
|
||||
assert.Equal(t, filename+".pom", pf.Name)
|
||||
assert.True(t, pf.IsLead)
|
||||
} else {
|
||||
assert.False(t, pf.IsLead)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, filename+".pom", pfs[i].Name)
|
||||
assert.True(t, pfs[i].IsLead)
|
||||
})
|
||||
|
||||
t.Run("DownloadPOM", func(t *testing.T) {
|
||||
@@ -202,4 +205,13 @@ func TestPackageMaven(t *testing.T) {
|
||||
assert.Equal(t, checksum, resp.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UploadSnapshot", func(t *testing.T) {
|
||||
snapshotVersion := packageVersion + "-SNAPSHOT"
|
||||
|
||||
putFile(t, fmt.Sprintf("/%s/%s", snapshotVersion, filename), "test", http.StatusCreated)
|
||||
putFile(t, "/maven-metadata.xml", "test", http.StatusOK)
|
||||
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test", http.StatusCreated)
|
||||
putFile(t, fmt.Sprintf("/%s/maven-metadata.xml", snapshotVersion), "test-overwrite", http.StatusCreated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,14 +19,22 @@ 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"
|
||||
)
|
||||
|
||||
func addNuGetAPIKeyHeader(request *http.Request, token string) *http.Request {
|
||||
request.Header.Set("X-NuGet-ApiKey", token)
|
||||
return request
|
||||
}
|
||||
|
||||
func TestPackageNuGet(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
token := getUserToken(t, user.Name)
|
||||
|
||||
packageName := "test.package"
|
||||
packageVersion := "1.0.3"
|
||||
@@ -58,35 +66,58 @@ func TestPackageNuGet(t *testing.T) {
|
||||
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)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}).(*user_model.User)
|
||||
|
||||
var result nuget.ServiceIndexResponse
|
||||
DecodeJSON(t, resp, &result)
|
||||
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},
|
||||
}
|
||||
|
||||
assert.Equal(t, "3.0.0", result.Version)
|
||||
assert.NotEmpty(t, result.Resources)
|
||||
for _, c := range cases {
|
||||
url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
|
||||
|
||||
root := setting.AppURL + url[1:]
|
||||
for _, r := range result.Resources {
|
||||
switch r.Type {
|
||||
case "SearchQueryService":
|
||||
fallthrough
|
||||
case "SearchQueryService/3.0.0-beta":
|
||||
fallthrough
|
||||
case "SearchQueryService/3.0.0-rc":
|
||||
assert.Equal(t, root+"/query", r.ID)
|
||||
case "RegistrationsBaseUrl":
|
||||
fallthrough
|
||||
case "RegistrationsBaseUrl/3.0.0-beta":
|
||||
fallthrough
|
||||
case "RegistrationsBaseUrl/3.0.0-rc":
|
||||
assert.Equal(t, root+"/registration", r.ID)
|
||||
case "PackageBaseAddress/3.0.0":
|
||||
assert.Equal(t, root+"/package", r.ID)
|
||||
case "PackagePublish/2.0.0":
|
||||
assert.Equal(t, root, r.ID)
|
||||
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
|
||||
DecodeJSON(t, resp, &result)
|
||||
|
||||
assert.Equal(t, "3.0.0", result.Version)
|
||||
assert.NotEmpty(t, result.Resources)
|
||||
|
||||
root := setting.AppURL + url[1:]
|
||||
for _, r := range result.Resources {
|
||||
switch r.Type {
|
||||
case "SearchQueryService":
|
||||
fallthrough
|
||||
case "SearchQueryService/3.0.0-beta":
|
||||
fallthrough
|
||||
case "SearchQueryService/3.0.0-rc":
|
||||
assert.Equal(t, root+"/query", r.ID)
|
||||
case "RegistrationsBaseUrl":
|
||||
fallthrough
|
||||
case "RegistrationsBaseUrl/3.0.0-beta":
|
||||
fallthrough
|
||||
case "RegistrationsBaseUrl/3.0.0-rc":
|
||||
assert.Equal(t, root+"/registration", r.ID)
|
||||
case "PackageBaseAddress/3.0.0":
|
||||
assert.Equal(t, root+"/package", r.ID)
|
||||
case "PackagePublish/2.0.0":
|
||||
assert.Equal(t, root, r.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -122,7 +153,7 @@ func TestPackageNuGet(t *testing.T) {
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
|
||||
t.Run("SymbolPackage", func(t *testing.T) {
|
||||
@@ -208,7 +239,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -352,7 +383,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -223,7 +223,7 @@ func TestAPITeamSearch(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}).(*user_model.User)
|
||||
|
||||
var results TeamSearchResults
|
||||
|
||||
|
||||
@@ -26,8 +26,19 @@ func TestUserOrgs(t *testing.T) {
|
||||
orgs := getUserOrgs(t, adminUsername, normalUsername)
|
||||
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User)
|
||||
user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}).(*user_model.User)
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
ID: 17,
|
||||
UserName: user17.Name,
|
||||
FullName: user17.FullName,
|
||||
AvatarURL: user17.AvatarLink(),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
UserName: user3.Name,
|
||||
@@ -82,8 +93,19 @@ func TestMyOrgs(t *testing.T) {
|
||||
var orgs []*api.Organization
|
||||
DecodeJSON(t, resp, &orgs)
|
||||
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}).(*user_model.User)
|
||||
user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}).(*user_model.User)
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
ID: 17,
|
||||
UserName: user17.Name,
|
||||
FullName: user17.FullName,
|
||||
AvatarURL: user17.AvatarLink(),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
UserName: user3.Name,
|
||||
|
||||
@@ -179,8 +179,8 @@ func TestOrgRestrictedUser(t *testing.T) {
|
||||
func TestTeamSearch(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User)
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}).(*user_model.User)
|
||||
|
||||
var results TeamSearchResults
|
||||
|
||||
@@ -190,9 +190,9 @@ func TestTeamSearch(t *testing.T) {
|
||||
req.Header.Add("X-Csrf-Token", csrf)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &results)
|
||||
assert.NotEmpty(t, results.Data)
|
||||
assert.Len(t, results.Data, 1)
|
||||
assert.Equal(t, "test_team", results.Data[0].Name)
|
||||
assert.Len(t, results.Data, 2)
|
||||
assert.Equal(t, "review_team", results.Data[0].Name)
|
||||
assert.Equal(t, "test_team", results.Data[1].Name)
|
||||
|
||||
// no access if not organization member
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
|
||||
|
||||
@@ -105,7 +105,11 @@ func doAPICreateCommitStatus(ctx APITestContext, commitID string, status api.Com
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullCreate_EmptyChangesWithCommits(t *testing.T) {
|
||||
func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
// Merge must continue if commits SHA are different, even if content is same
|
||||
// Reason: gitflow and merging master back into develop, where is high possiblity, there are no changes
|
||||
// but just commit saying "Merge branch". And this meta commit can be also tagged,
|
||||
// so we need to have this meta commit also in develop branch.
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
@@ -126,6 +130,28 @@ func TestPullCreate_EmptyChangesWithCommits(t *testing.T) {
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
||||
assert.Contains(t, text, "This branch is equal with the target branch.")
|
||||
assert.Contains(t, text, "This pull request can be merged automatically.")
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther)
|
||||
url := path.Join("user1", "repo1", "compare", "master...status1")
|
||||
req := NewRequestWithValues(t, "POST", url,
|
||||
map[string]string{
|
||||
"_csrf": GetCSRF(t, session, url),
|
||||
"title": "pull request from status1",
|
||||
},
|
||||
)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
||||
assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
rootDir: 'web_src',
|
||||
setupFilesAfterEnv: ['jest-extended/all'],
|
||||
testEnvironment: '@happy-dom/jest-environment',
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
testMatch: ['<rootDir>/**/*.test.js'],
|
||||
testTimeout: 20000,
|
||||
transform: {
|
||||
|
||||
@@ -92,13 +92,20 @@ func init() {
|
||||
|
||||
// TableIndices implements xorm's TableIndices interface
|
||||
func (a *Action) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
|
||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||
if setting.Database.UsePostgreSQL {
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
indices = append(indices, cudIndex)
|
||||
}
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex}
|
||||
return indices
|
||||
}
|
||||
|
||||
// GetOpType gets the ActionType of this action.
|
||||
@@ -211,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
|
||||
@@ -275,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.SHAPattern.MatchString(a.RefName):
|
||||
case len(a.RefName) == 40 && 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) {
|
||||
|
||||
@@ -33,7 +33,7 @@ type GPGKey struct {
|
||||
OwnerID int64 `xorm:"INDEX NOT NULL"`
|
||||
KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
|
||||
PrimaryKeyID string `xorm:"CHAR(16)"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Content string `xorm:"MEDIUMTEXT NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
ExpiredUnix timeutil.TimeStamp
|
||||
AddedUnix timeutil.TimeStamp
|
||||
@@ -63,6 +63,15 @@ 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
|
||||
}
|
||||
zeros := "0000000000000000"
|
||||
return zeros[0:16-len(key.KeyID)] + key.KeyID
|
||||
}
|
||||
|
||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||
func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) {
|
||||
sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)
|
||||
|
||||
@@ -41,7 +41,7 @@ type PublicKey struct {
|
||||
OwnerID int64 `xorm:"INDEX NOT NULL"`
|
||||
Name string `xorm:"NOT NULL"`
|
||||
Fingerprint string `xorm:"INDEX NOT NULL"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Content string `xorm:"MEDIUMTEXT NOT NULL"`
|
||||
Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
|
||||
Type KeyType `xorm:"NOT NULL DEFAULT 1"`
|
||||
LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -512,10 +525,14 @@ func GetActiveOAuth2ProviderSources() ([]*Source, error) {
|
||||
func GetActiveOAuth2SourceByName(name string) (*Source, error) {
|
||||
authSource := new(Source)
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
|
||||
if !has || err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
|
||||
}
|
||||
|
||||
return authSource, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
34
models/db/iterate.go
Normal file
34
models/db/iterate.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// IterateObjects iterate all the Bean object
|
||||
func IterateObjects[Object any](ctx context.Context, f func(repo *Object) error) error {
|
||||
var start int
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
sess := GetEngine(ctx)
|
||||
for {
|
||||
repos := make([]*Object, 0, batchSize)
|
||||
if err := sess.Limit(batchSize, start).Find(&repos); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(repos)
|
||||
|
||||
for _, repo := range repos {
|
||||
if err := f(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,3 +63,9 @@
|
||||
uid: 29
|
||||
org_id: 17
|
||||
is_public: true
|
||||
|
||||
-
|
||||
id: 12
|
||||
uid: 2
|
||||
org_id: 17
|
||||
is_public: true
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
avatar_email: user17@example.com
|
||||
num_repos: 2
|
||||
is_active: true
|
||||
num_members: 3
|
||||
num_members: 4
|
||||
num_teams: 3
|
||||
|
||||
-
|
||||
|
||||
@@ -278,29 +278,6 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// IterateLFS iterates lfs object
|
||||
func IterateLFS(f func(mo *LFSMetaObject) error) error {
|
||||
var start int
|
||||
const batchSize = 100
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
for {
|
||||
mos := make([]*LFSMetaObject, 0, batchSize)
|
||||
if err := e.Limit(batchSize, start).Find(&mos); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(mos) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(mos)
|
||||
|
||||
for _, mo := range mos {
|
||||
if err := f(mo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CopyLFS copies LFS data from one repo to another
|
||||
func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error {
|
||||
var lfsObjects []*LFSMetaObject
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
@@ -222,6 +223,46 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issues IssueList) getProjectIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(issues))
|
||||
for _, issue := range issues {
|
||||
projectID := issue.ProjectID()
|
||||
if _, ok := ids[projectID]; !ok {
|
||||
ids[projectID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
}
|
||||
|
||||
func (issues IssueList) loadProjects(ctx context.Context) error {
|
||||
projectIDs := issues.getProjectIDs()
|
||||
if len(projectIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
projectMaps := make(map[int64]*project_model.Project, len(projectIDs))
|
||||
left := len(projectIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
err := db.GetEngine(ctx).
|
||||
In("id", projectIDs[:limit]).
|
||||
Find(&projectMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
left -= limit
|
||||
projectIDs = projectIDs[limit:]
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
issue.Project = projectMaps[issue.ProjectID()]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issues IssueList) loadAssignees(ctx context.Context) error {
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
@@ -495,6 +536,10 @@ func (issues IssueList) loadAttributes(ctx context.Context) error {
|
||||
return fmt.Errorf("issue.loadAttributes: loadMilestones: %v", err)
|
||||
}
|
||||
|
||||
if err := issues.loadProjects(ctx); err != nil {
|
||||
return fmt.Errorf("issue.loadAttributes: loadProjects: %v", err)
|
||||
}
|
||||
|
||||
if err := issues.loadAssignees(ctx); err != nil {
|
||||
return fmt.Errorf("issue.loadAttributes: loadAssignees: %v", err)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
|
||||
issues, err := Issues(&IssuesOptions{
|
||||
ProjectBoardID: b.ID,
|
||||
ProjectID: b.ProjectID,
|
||||
SortType: "project-column-sorting",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -79,6 +80,7 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
|
||||
issues, err := Issues(&IssuesOptions{
|
||||
ProjectBoardID: -1, // Issues without ProjectBoardID
|
||||
ProjectID: b.ProjectID,
|
||||
SortType: "project-column-sorting",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -124,6 +126,17 @@ func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64
|
||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
oldProjectID := issue.projectID(ctx)
|
||||
|
||||
// Only check if we add a new project and not remove it.
|
||||
if newProjectID > 0 {
|
||||
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newProject.RepoID != issue.RepoID {
|
||||
return fmt.Errorf("issue's repository is not the same as project's repository")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -107,6 +108,7 @@ func (label *Label) CalOpenOrgIssues(repoID, labelID int64) {
|
||||
counts, _ := CountIssuesByRepo(&IssuesOptions{
|
||||
RepoID: repoID,
|
||||
LabelIDs: []int64{labelID},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
})
|
||||
|
||||
for _, count := range counts {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -124,6 +125,11 @@ func NewMilestone(m *Milestone) (err error) {
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// HasMilestoneByRepoID returns if the milestone exists in the repository.
|
||||
func HasMilestoneByRepoID(ctx context.Context, repoID, id int64) (bool, error) {
|
||||
return db.GetEngine(ctx).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone))
|
||||
}
|
||||
|
||||
// GetMilestoneByRepoID returns the milestone in a repository.
|
||||
func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) {
|
||||
m := new(Milestone)
|
||||
@@ -356,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
|
||||
|
||||
@@ -122,6 +122,7 @@ const (
|
||||
PullRequestStatusManuallyMerged
|
||||
PullRequestStatusError
|
||||
PullRequestStatusEmpty
|
||||
PullRequestStatusAncestor
|
||||
)
|
||||
|
||||
// PullRequestFlow the flow of pull request
|
||||
@@ -423,6 +424,11 @@ func (pr *PullRequest) IsEmpty() bool {
|
||||
return pr.Status == PullRequestStatusEmpty
|
||||
}
|
||||
|
||||
// IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit
|
||||
func (pr *PullRequest) IsAncestor() bool {
|
||||
return pr.Status == PullRequestStatusAncestor
|
||||
}
|
||||
|
||||
// SetMerged sets a pull request to merged and closes the corresponding issue
|
||||
func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
|
||||
if pr.HasMerged {
|
||||
|
||||
@@ -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,16 +260,23 @@ 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
|
||||
}
|
||||
|
||||
// DeleteIssueReaction deletes a reaction on issue.
|
||||
func DeleteIssueReaction(doerID, issueID int64, content string) error {
|
||||
return DeleteReaction(db.DefaultContext, &ReactionOptions{
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
CommentID: -1,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -56,6 +56,9 @@ type Version struct {
|
||||
Version int64
|
||||
}
|
||||
|
||||
// Use noopMigration when there is a migration that has been no-oped
|
||||
var noopMigration = func(_ *xorm.Engine) error { return nil }
|
||||
|
||||
// This is a sequence of migrations. Add new migrations to the bottom of the list.
|
||||
// If you want to "retire" a migration, remove it from the top of the list and
|
||||
// update minDBVersion accordingly
|
||||
@@ -351,7 +354,7 @@ var migrations = []Migration{
|
||||
// v198 -> v199
|
||||
NewMigration("Add issue content history table", addTableIssueContentHistory),
|
||||
// v199 -> v200
|
||||
NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop),
|
||||
NewMigration("No-op (remote version is using AppState now)", noopMigration),
|
||||
// v200 -> v201
|
||||
NewMigration("Add table app_state", addTableAppState),
|
||||
// v201 -> v202
|
||||
@@ -388,9 +391,21 @@ var migrations = []Migration{
|
||||
// v215 -> v216
|
||||
NewMigration("allow to view files in PRs", addReviewViewedFiles),
|
||||
// v216 -> v217
|
||||
NewMigration("Improve Action table indices", improveActionTableIndices),
|
||||
NewMigration("No-op (Improve Action table indices v1)", noopMigration),
|
||||
// v217 -> v218
|
||||
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
|
||||
|
||||
@@ -4,11 +4,4 @@
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRemoteVersionTableNoop(x *xorm.Engine) error {
|
||||
// we used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
|
||||
return nil
|
||||
}
|
||||
// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
|
||||
|
||||
@@ -4,43 +4,5 @@
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type improveActionTableIndicesAction struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 // Receiver user id.
|
||||
OpType int
|
||||
ActUserID int64 // Action user id.
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
// TableName sets the name of this table
|
||||
func (a *improveActionTableIndicesAction) TableName() string {
|
||||
return "action"
|
||||
}
|
||||
|
||||
// TableIndices implements xorm's TableIndices interface
|
||||
func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex}
|
||||
}
|
||||
|
||||
func improveActionTableIndices(x *xorm.Engine) error {
|
||||
return x.Sync2(&improveActionTableIndicesAction{})
|
||||
}
|
||||
// This migration added non-ideal indices to the action table which on larger datasets slowed things down
|
||||
// it has been superceded by v218.go
|
||||
|
||||
53
models/migrations/v218.go
Normal file
53
models/migrations/v218.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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 (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type improveActionTableIndicesAction struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 // Receiver user id.
|
||||
OpType int
|
||||
ActUserID int64 // Action user id.
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
// TableName sets the name of this table
|
||||
func (*improveActionTableIndicesAction) TableName() string {
|
||||
return "action"
|
||||
}
|
||||
|
||||
// TableIndices implements xorm's TableIndices interface
|
||||
func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
indices := []*schemas.Index{actUserIndex, repoIndex}
|
||||
if setting.Database.UsePostgreSQL {
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
indices = append(indices, cudIndex)
|
||||
}
|
||||
|
||||
return indices
|
||||
}
|
||||
|
||||
func improveActionTableIndices(x *xorm.Engine) error {
|
||||
return x.Sync2(&improveActionTableIndicesAction{})
|
||||
}
|
||||
31
models/migrations/v219.go
Normal file
31
models/migrations/v219.go
Normal 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))
|
||||
}
|
||||
28
models/migrations/v220.go
Normal file
28
models/migrations/v220.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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) (err 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)
|
||||
case schemas.MSSQL:
|
||||
_, 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)
|
||||
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)
|
||||
}
|
||||
return err
|
||||
}
|
||||
75
models/migrations/v221.go
Normal file
75
models/migrations/v221.go
Normal 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
models/migrations/v221_test.go
Normal file
65
models/migrations/v221_test.go
Normal 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
models/migrations/v222.go
Normal file
64
models/migrations/v222.go
Normal 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
models/migrations/v223.go
Normal file
103
models/migrations/v223.go
Normal 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{})
|
||||
}
|
||||
@@ -96,16 +96,7 @@ type SearchTeamOptions struct {
|
||||
IncludeDesc bool
|
||||
}
|
||||
|
||||
// SearchTeam search for teams. Caller is responsible to check permissions.
|
||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
if opts.Page <= 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
// Default limit
|
||||
opts.PageSize = 10
|
||||
}
|
||||
|
||||
func (opts *SearchTeamOptions) toCond() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
|
||||
if len(opts.Keyword) > 0 {
|
||||
@@ -117,10 +108,28 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
cond = cond.And(keywordCond)
|
||||
}
|
||||
|
||||
cond = cond.And(builder.Eq{"org_id": opts.OrgID})
|
||||
if opts.OrgID > 0 {
|
||||
cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
|
||||
}
|
||||
|
||||
if opts.UserID > 0 {
|
||||
cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// SearchTeam search for teams. Caller is responsible to check permissions.
|
||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext)
|
||||
|
||||
opts.SetDefaultValues()
|
||||
cond := opts.toCond()
|
||||
|
||||
if opts.UserID > 0 {
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
|
||||
}
|
||||
|
||||
count, err := sess.
|
||||
Where(cond).
|
||||
Count(new(Team))
|
||||
@@ -128,6 +137,10 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts.UserID > 0 {
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
|
||||
}
|
||||
|
||||
sess = sess.Where(cond)
|
||||
if opts.PageSize == -1 {
|
||||
opts.PageSize = int(count)
|
||||
@@ -137,6 +150,7 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
|
||||
teams := make([]*Team, 0, opts.PageSize)
|
||||
if err = sess.
|
||||
Where(cond).
|
||||
OrderBy("lower_name").
|
||||
Find(&teams); err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,26 +198,32 @@ 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
|
||||
// HasOwnerPackages tests if a user/org has accessible packages
|
||||
func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Where("owner_id = ?", ownerID).Exist(&Package{})
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(builder.Eq{
|
||||
"package_version.is_internal": false,
|
||||
"package.owner_id": ownerID,
|
||||
}).
|
||||
Exist(&PackageVersion{})
|
||||
}
|
||||
|
||||
// HasRepositoryPackages tests if a repository has packages
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
69
models/packages/package_test.go
Normal file
69
models/packages/package_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 packages_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
_ "code.gitea.io/gitea/models"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
GiteaRootPath: filepath.Join("..", ".."),
|
||||
})
|
||||
}
|
||||
|
||||
func TestHasOwnerPackages(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
|
||||
|
||||
p, err := packages_model.TryInsertPackage(db.DefaultContext, &packages_model.Package{
|
||||
OwnerID: owner.ID,
|
||||
LowerName: "package",
|
||||
})
|
||||
assert.NotNil(t, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package without package versions gets automatically cleaned up and should return false
|
||||
has, err := packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
|
||||
assert.False(t, has)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pv, err := packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
LowerVersion: "internal",
|
||||
IsInternal: true,
|
||||
})
|
||||
assert.NotNil(t, pv)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package with an internal package version gets automaticaly cleaned up and should return false
|
||||
has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
|
||||
assert.False(t, has)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pv, err = packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
LowerVersion: "normal",
|
||||
IsInternal: false,
|
||||
})
|
||||
assert.NotNil(t, pv)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package with a normal package version should return true
|
||||
has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
|
||||
assert.True(t, has)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -86,7 +86,13 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo
|
||||
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
|
||||
func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
|
||||
minMode := perm.AccessModeRead
|
||||
if !repo.IsPrivate {
|
||||
if err := repo.GetOwner(ctx); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
// If the repo isn't private and isn't owned by a organization,
|
||||
// increase the minMode to Write.
|
||||
if !repo.IsPrivate && !repo.Owner.IsOrganization() {
|
||||
minMode = perm.AccessModeWrite
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -385,8 +385,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
|
||||
|
||||
archivePaths := make([]string, 0, len(archives))
|
||||
for _, v := range archives {
|
||||
p, _ := v.RelativePath()
|
||||
archivePaths = append(archivePaths, p)
|
||||
archivePaths = append(archivePaths, v.RelativePath())
|
||||
}
|
||||
|
||||
if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
|
||||
@@ -606,15 +605,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_closed=? AND is_pull=?)", false, 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_closed=? AND is_pull=?)", false, 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'",
|
||||
},
|
||||
|
||||
@@ -39,9 +39,9 @@ func init() {
|
||||
db.RegisterModel(new(RepoArchiver))
|
||||
}
|
||||
|
||||
// RelativePath returns relative path
|
||||
func (archiver *RepoArchiver) RelativePath() (string, error) {
|
||||
return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String()), nil
|
||||
// RelativePath returns the archive path relative to the archive storage root.
|
||||
func (archiver *RepoArchiver) RelativePath() string {
|
||||
return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String())
|
||||
}
|
||||
|
||||
var delRepoArchiver = new(RepoArchiver)
|
||||
|
||||
@@ -226,28 +226,6 @@ func DeleteAttachmentsByRelease(releaseID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IterateAttachment iterates attachments; it should not be used when Gitea is servicing users.
|
||||
func IterateAttachment(f func(attach *Attachment) error) error {
|
||||
var start int
|
||||
const batchSize = 100
|
||||
for {
|
||||
attachments := make([]*Attachment, 0, batchSize)
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(attachments) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(attachments)
|
||||
|
||||
for _, attach := range attachments {
|
||||
if err := f(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CountOrphanedAttachments returns the number of bad attachments
|
||||
func CountOrphanedAttachments() (int64, error) {
|
||||
return db.GetEngine(db.DefaultContext).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
|
||||
|
||||
@@ -8,7 +8,6 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -108,12 +107,14 @@ func DeleteMirrorByRepoID(repoID int64) error {
|
||||
|
||||
// MirrorsIterate iterates all mirror repositories.
|
||||
func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
|
||||
return db.GetEngine(db.DefaultContext).
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Where("next_update_unix<=?", time.Now().Unix()).
|
||||
And("next_update_unix!=0").
|
||||
OrderBy("updated_unix ASC").
|
||||
Limit(limit).
|
||||
Iterate(new(Mirror), f)
|
||||
OrderBy("updated_unix ASC")
|
||||
if limit > 0 {
|
||||
sess = sess.Limit(limit)
|
||||
}
|
||||
return sess.Iterate(new(Mirror), f)
|
||||
}
|
||||
|
||||
// InsertMirror inserts a mirror to database
|
||||
@@ -121,53 +122,3 @@ func InsertMirror(ctx context.Context, mirror *Mirror) error {
|
||||
_, err := db.GetEngine(ctx).Insert(mirror)
|
||||
return err
|
||||
}
|
||||
|
||||
// MirrorRepositoryList contains the mirror repositories
|
||||
type MirrorRepositoryList []*Repository
|
||||
|
||||
func (repos MirrorRepositoryList) loadAttributes(ctx context.Context) error {
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load mirrors.
|
||||
repoIDs := make([]int64, 0, len(repos))
|
||||
for i := range repos {
|
||||
if !repos[i].IsMirror {
|
||||
continue
|
||||
}
|
||||
|
||||
repoIDs = append(repoIDs, repos[i].ID)
|
||||
}
|
||||
mirrors := make([]*Mirror, 0, len(repoIDs))
|
||||
if err := db.GetEngine(ctx).
|
||||
Where("id > 0").
|
||||
In("repo_id", repoIDs).
|
||||
Find(&mirrors); err != nil {
|
||||
return fmt.Errorf("find mirrors: %v", err)
|
||||
}
|
||||
|
||||
set := make(map[int64]*Mirror)
|
||||
for i := range mirrors {
|
||||
set[mirrors[i].RepoID] = mirrors[i]
|
||||
}
|
||||
for i := range repos {
|
||||
repos[i].Mirror = set[repos[i].ID]
|
||||
repos[i].Mirror.Repo = repos[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAttributes loads the attributes for the given MirrorRepositoryList
|
||||
func (repos MirrorRepositoryList) LoadAttributes() error {
|
||||
return repos.loadAttributes(db.DefaultContext)
|
||||
}
|
||||
|
||||
// GetUserMirrorRepositories returns a list of mirror repositories of given user.
|
||||
func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, 10)
|
||||
return repos, db.GetEngine(db.DefaultContext).
|
||||
Where("owner_id = ?", userID).
|
||||
And("is_mirror = ?", true).
|
||||
Find(&repos)
|
||||
}
|
||||
|
||||
@@ -95,10 +95,12 @@ func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) {
|
||||
|
||||
// PushMirrorsIterate iterates all push-mirror repositories.
|
||||
func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
|
||||
return db.GetEngine(db.DefaultContext).
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
|
||||
And("`interval` != 0").
|
||||
OrderBy("last_update ASC").
|
||||
Limit(limit).
|
||||
Iterate(new(PushMirror), f)
|
||||
OrderBy("last_update ASC")
|
||||
if limit > 0 {
|
||||
sess = sess.Limit(limit)
|
||||
}
|
||||
return sess.Iterate(new(PushMirror), f)
|
||||
}
|
||||
|
||||
@@ -14,36 +14,12 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// IterateRepository iterate repositories
|
||||
func IterateRepository(f func(repo *Repository) error) error {
|
||||
var start int
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
sess := db.GetEngine(db.DefaultContext)
|
||||
for {
|
||||
repos := make([]*Repository, 0, batchSize)
|
||||
if err := sess.Limit(batchSize, start).Find(&repos); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(repos)
|
||||
|
||||
for _, repo := range repos {
|
||||
if err := f(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindReposMapByIDs find repos as map
|
||||
func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error {
|
||||
return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res)
|
||||
|
||||
@@ -107,6 +107,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||
|
||||
setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages")
|
||||
|
||||
setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home")
|
||||
|
||||
if err = storage.Init(); err != nil {
|
||||
fatalTestError("storage.Init: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -100,9 +100,9 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
|
||||
// Delete Comments
|
||||
const batchSize = 50
|
||||
for start := 0; ; start += batchSize {
|
||||
for {
|
||||
comments := make([]*issues_model.Comment, 0, batchSize)
|
||||
if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, start).Find(&comments); err != nil {
|
||||
if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, 0).Find(&comments); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(comments) == 0 {
|
||||
@@ -200,7 +200,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
// ***** END: ExternalLoginUser *****
|
||||
|
||||
if _, err = e.ID(u.ID).Delete(new(user_model.User)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
return fmt.Errorf("delete: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -58,31 +57,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})
|
||||
@@ -149,24 +124,25 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
|
||||
return users, count, sessQuery.Find(&users)
|
||||
}
|
||||
|
||||
// IterateUser iterate users
|
||||
func IterateUser(f func(user *User) error) error {
|
||||
var start int
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
for {
|
||||
users := make([]*User, 0, batchSize)
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(users)
|
||||
|
||||
for _, user := range users {
|
||||
if err := f(user); err != nil {
|
||||
return err
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -316,37 +316,45 @@ func (u *User) GenerateEmailActivateCode(email string) string {
|
||||
}
|
||||
|
||||
// GetUserFollowers returns range of user's followers.
|
||||
func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Select("`user`.*").
|
||||
Join("LEFT", "follow", "`user`.id=follow.user_id").
|
||||
Where("follow.follow_id=?", u.ID).
|
||||
Join("LEFT", "follow", "`user`.id=follow.user_id")
|
||||
And(isUserVisibleToViewerCond(viewer))
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
users := make([]*User, 0, listOptions.PageSize)
|
||||
return users, sess.Find(&users)
|
||||
count, err := sess.FindAndCount(&users)
|
||||
return users, count, err
|
||||
}
|
||||
|
||||
users := make([]*User, 0, 8)
|
||||
return users, sess.Find(&users)
|
||||
count, err := sess.FindAndCount(&users)
|
||||
return users, count, err
|
||||
}
|
||||
|
||||
// GetUserFollowing returns range of user's following.
|
||||
func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) {
|
||||
func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
|
||||
sess := db.GetEngine(db.DefaultContext).
|
||||
Select("`user`.*").
|
||||
Join("LEFT", "follow", "`user`.id=follow.follow_id").
|
||||
Where("follow.user_id=?", u.ID).
|
||||
Join("LEFT", "follow", "`user`.id=follow.follow_id")
|
||||
And(isUserVisibleToViewerCond(viewer))
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
users := make([]*User, 0, listOptions.PageSize)
|
||||
return users, sess.Find(&users)
|
||||
count, err := sess.FindAndCount(&users)
|
||||
return users, count, err
|
||||
}
|
||||
|
||||
users := make([]*User, 0, 8)
|
||||
return users, sess.Find(&users)
|
||||
count, err := sess.FindAndCount(&users)
|
||||
return users, count, err
|
||||
}
|
||||
|
||||
// NewGitSig generates and returns the signature of given user.
|
||||
@@ -485,6 +493,9 @@ func (u *User) GitName() string {
|
||||
|
||||
// ShortName ellipses username to length
|
||||
func (u *User) ShortName(length int) string {
|
||||
if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
|
||||
return base.EllipsisString(u.FullName, length)
|
||||
}
|
||||
return base.EllipsisString(u.Name, length)
|
||||
}
|
||||
|
||||
@@ -1219,9 +1230,42 @@ func GetAdminUser() (*User, error) {
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
func isUserVisibleToViewerCond(viewer *User) builder.Cond {
|
||||
if viewer != nil && viewer.IsAdmin {
|
||||
return builder.NewCond()
|
||||
}
|
||||
|
||||
if viewer == nil || viewer.IsRestricted {
|
||||
return builder.Eq{
|
||||
"`user`.visibility": structs.VisibleTypePublic,
|
||||
}
|
||||
}
|
||||
|
||||
return builder.Neq{
|
||||
"`user`.visibility": structs.VisibleTypePrivate,
|
||||
}.Or(
|
||||
builder.In("`user`.id",
|
||||
builder.
|
||||
Select("`follow`.user_id").
|
||||
From("follow").
|
||||
Where(builder.Eq{"`follow`.follow_id": viewer.ID})),
|
||||
builder.In("`user`.id",
|
||||
builder.
|
||||
Select("`team_user`.uid").
|
||||
From("team_user").
|
||||
Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id").
|
||||
Where(builder.Eq{"`t2`.uid": viewer.ID})),
|
||||
builder.In("`user`.id",
|
||||
builder.
|
||||
Select("`team_user`.uid").
|
||||
From("team_user").
|
||||
Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id").
|
||||
Where(builder.Eq{"`t2`.uid": viewer.ID})))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1260,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
|
||||
}
|
||||
|
||||
@@ -399,6 +399,10 @@ func CreateWebhook(ctx context.Context, w *Webhook) error {
|
||||
|
||||
// CreateWebhooks creates multiple web hooks
|
||||
func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
|
||||
// xorm returns err "no element on slice when insert" for empty slices.
|
||||
if len(ws) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < len(ws); i++ {
|
||||
ws[i].Type = strings.TrimSpace(ws[i].Type)
|
||||
}
|
||||
|
||||
@@ -36,20 +36,20 @@ func drawBlock(img *image.Paletted, x, y, size, angle int, points []int) {
|
||||
|
||||
// blank
|
||||
//
|
||||
// --------
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// --------
|
||||
// --------
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// --------
|
||||
func b0(img *image.Paletted, x, y, size, angle int) {}
|
||||
|
||||
// full-filled
|
||||
//
|
||||
// --------
|
||||
// |######|
|
||||
// |######|
|
||||
// |######|
|
||||
// --------
|
||||
// --------
|
||||
// |######|
|
||||
// |######|
|
||||
// |######|
|
||||
// --------
|
||||
func b1(img *image.Paletted, x, y, size, angle int) {
|
||||
for i := x; i < x+size; i++ {
|
||||
for j := y; j < y+size; j++ {
|
||||
@@ -59,12 +59,13 @@ func b1(img *image.Paletted, x, y, size, angle int) {
|
||||
}
|
||||
|
||||
// a small block
|
||||
// ----------
|
||||
// | |
|
||||
// | #### |
|
||||
// | #### |
|
||||
// | |
|
||||
// ----------
|
||||
//
|
||||
// ----------
|
||||
// | |
|
||||
// | #### |
|
||||
// | #### |
|
||||
// | |
|
||||
// ----------
|
||||
func b2(img *image.Paletted, x, y, size, angle int) {
|
||||
l := size / 4
|
||||
x += l
|
||||
@@ -79,15 +80,15 @@ func b2(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// diamond
|
||||
//
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// | ##### |
|
||||
// | ### |
|
||||
// | # |
|
||||
// ---------
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// | ##### |
|
||||
// | ### |
|
||||
// | # |
|
||||
// ---------
|
||||
func b3(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, 0, []int{
|
||||
@@ -101,13 +102,13 @@ func b3(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b4
|
||||
//
|
||||
// -------
|
||||
// |#####|
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// |------
|
||||
// -------
|
||||
// |#####|
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// |------
|
||||
func b4(img *image.Paletted, x, y, size, angle int) {
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
0, 0,
|
||||
@@ -119,11 +120,11 @@ func b4(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b5
|
||||
//
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
func b5(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -136,11 +137,11 @@ func b5(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b6
|
||||
//
|
||||
// --------
|
||||
// |### |
|
||||
// |### |
|
||||
// |### |
|
||||
// --------
|
||||
// --------
|
||||
// |### |
|
||||
// |### |
|
||||
// |### |
|
||||
// --------
|
||||
func b6(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -154,12 +155,12 @@ func b6(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b7 italic cone
|
||||
//
|
||||
// ---------
|
||||
// | # |
|
||||
// | ## |
|
||||
// | #####|
|
||||
// | ####|
|
||||
// |--------
|
||||
// ---------
|
||||
// | # |
|
||||
// | ## |
|
||||
// | #####|
|
||||
// | ####|
|
||||
// |--------
|
||||
func b7(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -173,14 +174,14 @@ func b7(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b8 three small triangles
|
||||
//
|
||||
// -----------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// | # # |
|
||||
// | ### ### |
|
||||
// |#########|
|
||||
// -----------
|
||||
// -----------
|
||||
// | # |
|
||||
// | ### |
|
||||
// | ##### |
|
||||
// | # # |
|
||||
// | ### ### |
|
||||
// |#########|
|
||||
// -----------
|
||||
func b8(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
mm := m / 2
|
||||
@@ -212,13 +213,13 @@ func b8(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b9 italic triangle
|
||||
//
|
||||
// ---------
|
||||
// |# |
|
||||
// | #### |
|
||||
// | #####|
|
||||
// | #### |
|
||||
// | # |
|
||||
// ---------
|
||||
// ---------
|
||||
// |# |
|
||||
// | #### |
|
||||
// | #####|
|
||||
// | #### |
|
||||
// | # |
|
||||
// ---------
|
||||
func b9(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -231,16 +232,16 @@ func b9(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b10
|
||||
//
|
||||
// ----------
|
||||
// | ####|
|
||||
// | ### |
|
||||
// | ## |
|
||||
// | # |
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
// ----------
|
||||
// | ####|
|
||||
// | ### |
|
||||
// | ## |
|
||||
// | # |
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
func b10(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -260,13 +261,13 @@ func b10(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b11
|
||||
//
|
||||
// ----------
|
||||
// |#### |
|
||||
// |#### |
|
||||
// |#### |
|
||||
// | |
|
||||
// | |
|
||||
// ----------
|
||||
// ----------
|
||||
// |#### |
|
||||
// |#### |
|
||||
// |#### |
|
||||
// | |
|
||||
// | |
|
||||
// ----------
|
||||
func b11(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -280,13 +281,13 @@ func b11(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b12
|
||||
//
|
||||
// -----------
|
||||
// | |
|
||||
// | |
|
||||
// |#########|
|
||||
// | ##### |
|
||||
// | # |
|
||||
// -----------
|
||||
// -----------
|
||||
// | |
|
||||
// | |
|
||||
// |#########|
|
||||
// | ##### |
|
||||
// | # |
|
||||
// -----------
|
||||
func b12(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -299,13 +300,13 @@ func b12(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b13
|
||||
//
|
||||
// -----------
|
||||
// | |
|
||||
// | |
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#########|
|
||||
// -----------
|
||||
// -----------
|
||||
// | |
|
||||
// | |
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#########|
|
||||
// -----------
|
||||
func b13(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -318,13 +319,13 @@ func b13(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b14
|
||||
//
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// |#### |
|
||||
// | |
|
||||
// | |
|
||||
// ---------
|
||||
// ---------
|
||||
// | # |
|
||||
// | ### |
|
||||
// |#### |
|
||||
// | |
|
||||
// | |
|
||||
// ---------
|
||||
func b14(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -337,13 +338,13 @@ func b14(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b15
|
||||
//
|
||||
// ----------
|
||||
// |##### |
|
||||
// |### |
|
||||
// |# |
|
||||
// | |
|
||||
// | |
|
||||
// ----------
|
||||
// ----------
|
||||
// |##### |
|
||||
// |### |
|
||||
// |# |
|
||||
// | |
|
||||
// | |
|
||||
// ----------
|
||||
func b15(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -356,14 +357,14 @@ func b15(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b16
|
||||
//
|
||||
// ---------
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// ---------
|
||||
// ---------
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// | # |
|
||||
// | ##### |
|
||||
// |#######|
|
||||
// ---------
|
||||
func b16(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
drawBlock(img, x, y, size, angle, []int{
|
||||
@@ -383,13 +384,13 @@ func b16(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b17
|
||||
//
|
||||
// ----------
|
||||
// |##### |
|
||||
// |### |
|
||||
// |# |
|
||||
// | ##|
|
||||
// | ##|
|
||||
// ----------
|
||||
// ----------
|
||||
// |##### |
|
||||
// |### |
|
||||
// |# |
|
||||
// | ##|
|
||||
// | ##|
|
||||
// ----------
|
||||
func b17(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
|
||||
@@ -412,13 +413,13 @@ func b17(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b18
|
||||
//
|
||||
// ----------
|
||||
// |##### |
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
// ----------
|
||||
// |##### |
|
||||
// |#### |
|
||||
// |### |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
func b18(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
|
||||
@@ -432,13 +433,13 @@ func b18(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b19
|
||||
//
|
||||
// ----------
|
||||
// |########|
|
||||
// |### ###|
|
||||
// |# #|
|
||||
// |### ###|
|
||||
// |########|
|
||||
// ----------
|
||||
// ----------
|
||||
// |########|
|
||||
// |### ###|
|
||||
// |# #|
|
||||
// |### ###|
|
||||
// |########|
|
||||
// ----------
|
||||
func b19(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
|
||||
@@ -473,13 +474,13 @@ func b19(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b20
|
||||
//
|
||||
// ----------
|
||||
// | ## |
|
||||
// |### |
|
||||
// |## |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
// ----------
|
||||
// | ## |
|
||||
// |### |
|
||||
// |## |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
func b20(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -494,13 +495,13 @@ func b20(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b21
|
||||
//
|
||||
// ----------
|
||||
// | #### |
|
||||
// |## #####|
|
||||
// |## ##|
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
// ----------
|
||||
// | #### |
|
||||
// |## #####|
|
||||
// |## ##|
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
func b21(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -522,13 +523,13 @@ func b21(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b22
|
||||
//
|
||||
// ----------
|
||||
// | #### |
|
||||
// |## ### |
|
||||
// |## ##|
|
||||
// |## ##|
|
||||
// |# #|
|
||||
// ----------
|
||||
// ----------
|
||||
// | #### |
|
||||
// |## ### |
|
||||
// |## ##|
|
||||
// |## ##|
|
||||
// |# #|
|
||||
// ----------
|
||||
func b22(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -550,13 +551,13 @@ func b22(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b23
|
||||
//
|
||||
// ----------
|
||||
// | #######|
|
||||
// |### #|
|
||||
// |## |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
// ----------
|
||||
// | #######|
|
||||
// |### #|
|
||||
// |## |
|
||||
// |## |
|
||||
// |# |
|
||||
// ----------
|
||||
func b23(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -578,13 +579,13 @@ func b23(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b24
|
||||
//
|
||||
// ----------
|
||||
// | ## ###|
|
||||
// |### ###|
|
||||
// |## ## |
|
||||
// |## ## |
|
||||
// |# # |
|
||||
// ----------
|
||||
// ----------
|
||||
// | ## ###|
|
||||
// |### ###|
|
||||
// |## ## |
|
||||
// |## ## |
|
||||
// |# # |
|
||||
// ----------
|
||||
func b24(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -606,13 +607,13 @@ func b24(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b25
|
||||
//
|
||||
// ----------
|
||||
// |# #|
|
||||
// |## ###|
|
||||
// |## ## |
|
||||
// |###### |
|
||||
// |#### |
|
||||
// ----------
|
||||
// ----------
|
||||
// |# #|
|
||||
// |## ###|
|
||||
// |## ## |
|
||||
// |###### |
|
||||
// |#### |
|
||||
// ----------
|
||||
func b25(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -634,13 +635,13 @@ func b25(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b26
|
||||
//
|
||||
// ----------
|
||||
// |# #|
|
||||
// |### ###|
|
||||
// | #### |
|
||||
// |### ###|
|
||||
// |# #|
|
||||
// ----------
|
||||
// ----------
|
||||
// |# #|
|
||||
// |### ###|
|
||||
// | #### |
|
||||
// |### ###|
|
||||
// |# #|
|
||||
// ----------
|
||||
func b26(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
@@ -676,13 +677,13 @@ func b26(img *image.Paletted, x, y, size, angle int) {
|
||||
|
||||
// b27
|
||||
//
|
||||
// ----------
|
||||
// |########|
|
||||
// |## ###|
|
||||
// |# #|
|
||||
// |### ##|
|
||||
// |########|
|
||||
// ----------
|
||||
// ----------
|
||||
// |########|
|
||||
// |## ###|
|
||||
// |# #|
|
||||
// |### ##|
|
||||
// |########|
|
||||
// ----------
|
||||
func b27(img *image.Paletted, x, y, size, angle int) {
|
||||
m := size / 2
|
||||
q := size / 4
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -223,7 +224,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
||||
ctx.Data["TemplateLoadTimes"] = func() string {
|
||||
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
|
||||
}
|
||||
if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
|
||||
if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
|
||||
if status == http.StatusInternalServerError && name == base.TplName("status/500") {
|
||||
ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
|
||||
return
|
||||
@@ -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()
|
||||
|
||||
@@ -11,7 +11,9 @@ 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/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
@@ -51,31 +53,11 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
|
||||
Owner: ctx.ContextUser,
|
||||
}
|
||||
|
||||
if ctx.Package.Owner.IsOrganization() {
|
||||
// 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)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
var err error
|
||||
ctx.Package.AccessMode, err = determineAccessMode(ctx)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, "determineAccessMode", err)
|
||||
return
|
||||
}
|
||||
|
||||
packageType := ctx.Params("type")
|
||||
@@ -100,6 +82,57 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
|
||||
}
|
||||
}
|
||||
|
||||
func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
|
||||
accessMode := perm.AccessModeNone
|
||||
|
||||
if setting.Service.RequireSignInView && ctx.Doer == nil {
|
||||
return accessMode, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -118,7 +118,7 @@ type CanCommitToBranchResults struct {
|
||||
}
|
||||
|
||||
// CanCommitToBranch returns true if repository is editable and user has proper access level
|
||||
// and branch is not protected for push
|
||||
// and branch is not protected for push
|
||||
func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
|
||||
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
|
||||
if err != nil {
|
||||
@@ -524,7 +524,9 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
||||
}
|
||||
|
||||
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
@@ -986,6 +988,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
}
|
||||
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["RefName"] = ctx.Repo.RefName
|
||||
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["TagName"] = ctx.Repo.TagName
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
|
||||
@@ -101,6 +101,12 @@ func ToTimelineComment(c *issues_model.Comment, doer *user_model.User) *api.Time
|
||||
}
|
||||
|
||||
if c.Time != nil {
|
||||
err = c.Time.LoadAttributes()
|
||||
if err != nil {
|
||||
log.Error("Time.LoadAttributes: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
comment.TrackedTime = ToTrackedTime(c.Time)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,6 +58,29 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// From time to time Gitea makes changes to the reserved usernames and which symbols
|
||||
// are allowed for various reasons. This check helps with detecting users that, according
|
||||
// to our reserved names, don't have a valid username.
|
||||
func checkUserName(ctx context.Context, logger log.Logger, _ bool) error {
|
||||
var invalidUserCount int64
|
||||
if err := iterateUserAccounts(ctx, func(u *user.User) error {
|
||||
if err := user.IsUsableUsername(u.Name); err != nil {
|
||||
invalidUserCount++
|
||||
logger.Warn("User[id=%d] does not have a valid username: %v", u.ID, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("iterateUserAccounts: %v", err)
|
||||
}
|
||||
|
||||
if invalidUserCount == 0 {
|
||||
logger.Info("All users have a valid username.")
|
||||
} else {
|
||||
logger.Warn("%d user(s) have a non-valid username.", invalidUserCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&Check{
|
||||
Title: "Check if users has an valid email address",
|
||||
@@ -66,4 +89,11 @@ func init() {
|
||||
Run: checkUserEmail,
|
||||
Priority: 9,
|
||||
})
|
||||
Register(&Check{
|
||||
Title: "Check if users have a valid username",
|
||||
Name: "check-user-names",
|
||||
IsDefault: false,
|
||||
Run: checkUserName,
|
||||
Priority: 9,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@@ -49,7 +50,11 @@ func initDBDisableConsole(ctx context.Context, disableConsole bool) error {
|
||||
|
||||
setting.NewXORMLogService(disableConsole)
|
||||
if err := db.InitEngine(ctx); err != nil {
|
||||
return fmt.Errorf("models.SetEngine: %v", err)
|
||||
return fmt.Errorf("db.InitEngine: %w", err)
|
||||
}
|
||||
// some doctor sub-commands need to use git command
|
||||
if err := git.InitOnceWithSync(ctx); err != nil {
|
||||
return fmt.Errorf("git.InitOnceWithSync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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,43 +93,79 @@ 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
|
||||
}
|
||||
|
||||
// RunOpts represents parameters to run the command
|
||||
// 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
|
||||
Timeout time.Duration
|
||||
Dir string
|
||||
Stdout, Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
PipelineFunc func(context.Context, context.CancelFunc) error
|
||||
Env []string
|
||||
Timeout time.Duration
|
||||
UseContextTimeout bool
|
||||
Dir string
|
||||
Stdout, Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
PipelineFunc func(context.Context, context.CancelFunc) error
|
||||
}
|
||||
|
||||
func commonBaseEnvs() []string {
|
||||
// at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
|
||||
envs := []string{
|
||||
"HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config
|
||||
"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
|
||||
}
|
||||
|
||||
// some environment variables should be passed to git command
|
||||
passThroughEnvKeys := []string{
|
||||
"GNUPGHOME", // git may call gnupg to do commit signing
|
||||
}
|
||||
for _, key := range passThroughEnvKeys {
|
||||
if val, ok := os.LookupEnv(key); ok {
|
||||
envs = append(envs, key+"="+val)
|
||||
}
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// CommonGitCmdEnvs returns the common environment variables for a "git" command.
|
||||
func CommonGitCmdEnvs() []string {
|
||||
// at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
|
||||
return []string{
|
||||
fmt.Sprintf("LC_ALL=%s", DefaultLocale),
|
||||
"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
|
||||
"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
|
||||
"HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config
|
||||
}
|
||||
return append(commonBaseEnvs(), []string{
|
||||
"LC_ALL=" + DefaultLocale,
|
||||
"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
|
||||
}...)
|
||||
}
|
||||
|
||||
// CommonCmdServEnvs is like CommonGitCmdEnvs but it only returns minimal required environment variables for the "gitea serv" command
|
||||
func CommonCmdServEnvs() []string {
|
||||
return []string{
|
||||
"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
|
||||
"HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config
|
||||
}
|
||||
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{}
|
||||
}
|
||||
@@ -158,7 +198,15 @@ func (c *Command) Run(opts *RunOpts) error {
|
||||
desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), opts.Dir)
|
||||
}
|
||||
|
||||
ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
var finished context.CancelFunc
|
||||
|
||||
if opts.UseContextTimeout {
|
||||
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
||||
} else {
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
||||
}
|
||||
defer finished()
|
||||
|
||||
cmd := exec.CommandContext(ctx, c.name, c.args...)
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -68,8 +68,7 @@ func NewParser(r io.Reader, format Format) *Parser {
|
||||
//
|
||||
// It could, for example return something like:
|
||||
//
|
||||
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
||||
//
|
||||
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
||||
func (p *Parser) Next() map[string]string {
|
||||
if !p.scanner.Scan() {
|
||||
return nil
|
||||
@@ -89,8 +88,7 @@ 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"
|
||||
//
|
||||
// "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
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -126,8 +127,8 @@ func VersionInfo() string {
|
||||
}
|
||||
|
||||
func checkInit() error {
|
||||
if setting.RepoRootPath == "" {
|
||||
return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
|
||||
if setting.Git.HomePath == "" {
|
||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
}
|
||||
if DefaultContext != nil {
|
||||
log.Warn("git module has been initialized already, duplicate init should be fixed")
|
||||
@@ -137,14 +138,14 @@ func checkInit() error {
|
||||
|
||||
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
||||
func HomeDir() string {
|
||||
if setting.RepoRootPath == "" {
|
||||
if setting.Git.HomePath == "" {
|
||||
// strict check, make sure the git module is initialized correctly.
|
||||
// attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users.
|
||||
// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
|
||||
log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
|
||||
log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
return ""
|
||||
}
|
||||
return setting.RepoRootPath
|
||||
return setting.Git.HomePath
|
||||
}
|
||||
|
||||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||
@@ -175,11 +176,15 @@ func InitOnceWithSync(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
initOnce.Do(func() {
|
||||
err = InitSimple(ctx)
|
||||
if err != nil {
|
||||
if err = InitSimple(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||
}
|
||||
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
@@ -206,7 +211,7 @@ func InitOnceWithSync(ctx context.Context) (err error) {
|
||||
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
||||
func syncGitConfig() (err error) {
|
||||
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err)
|
||||
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
|
||||
}
|
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||
@@ -282,7 +287,20 @@ func syncGitConfig() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
err = configSet("uploadpack.allowAnySHA1InWant", "true")
|
||||
} else {
|
||||
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckGitVersionAtLeast check git version is at least the constraint version
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
func testRun(m *testing.M) error {
|
||||
_ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`)
|
||||
|
||||
repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
|
||||
gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp dir: %w", err)
|
||||
}
|
||||
defer util.RemoveAll(repoRootPath)
|
||||
setting.RepoRootPath = repoRootPath
|
||||
defer util.RemoveAll(gitHomePath)
|
||||
setting.Git.HomePath = gitHomePath
|
||||
|
||||
if err = InitOnceWithSync(context.Background()); err != nil {
|
||||
return fmt.Errorf("failed to call Init: %w", err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
@@ -62,9 +63,10 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
|
||||
})
|
||||
if err != nil {
|
||||
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
|
||||
} else {
|
||||
_ = stdoutWriter.Close()
|
||||
return
|
||||
}
|
||||
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
|
||||
// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
|
||||
@@ -354,7 +356,7 @@ heaploop:
|
||||
}
|
||||
current, err := g.Next(treepath, path2idx, changed, maxpathlen)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
break heaploop
|
||||
}
|
||||
g.Close()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
package git
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// RemotePrefix is the base directory of the remotes information of git.
|
||||
@@ -15,6 +18,29 @@ const (
|
||||
pullLen = len(PullPrefix)
|
||||
)
|
||||
|
||||
// refNamePatternInvalid is regular expression with unallowed characters in git reference name
|
||||
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
|
||||
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
|
||||
var refNamePatternInvalid = regexp.MustCompile(
|
||||
`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
|
||||
`(?:^[/.])|` + // Not HasPrefix("/") or "."
|
||||
`(?:/\.)|` + // no "/."
|
||||
`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
|
||||
`(?:\.\.)|` + // no ".." anywhere
|
||||
`(?://)|` + // no "//" anywhere
|
||||
`(?:@{)|` + // no "@{"
|
||||
`(?:[/.]$)|` + // no terminal '/' or '.'
|
||||
`(?:^@$)`) // Not "@"
|
||||
|
||||
// IsValidRefPattern ensures that the provided string could be a valid reference
|
||||
func IsValidRefPattern(name string) bool {
|
||||
return !refNamePatternInvalid.MatchString(name)
|
||||
}
|
||||
|
||||
func SanitizeRefPattern(name string) string {
|
||||
return refNamePatternInvalid.ReplaceAllString(name, "_")
|
||||
}
|
||||
|
||||
// Reference represents a Git ref.
|
||||
type Reference struct {
|
||||
Name string
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -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,14 +208,15 @@ 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{
|
||||
Dir: repo.Path,
|
||||
Stdout: stdoutWriter,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
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,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user