forked from mirrors/gitea
Compare commits
240 Commits
main
...
release/v1
Author | SHA1 | Date | |
---|---|---|---|
|
9aa13c585e | ||
|
eeaa9250e0 | ||
|
713bc6c8dc | ||
|
6b7e860b0f | ||
|
0f89417d75 | ||
|
7c80a0b630 | ||
|
b42df3105d | ||
|
6162fb0a19 | ||
|
df0b330af7 | ||
|
51db7b03dd | ||
|
a7b1e20b76 | ||
|
de79d2a235 | ||
|
4b7f0c6c38 | ||
|
ae91913132 | ||
|
0e7791174d | ||
|
736b7b25a4 | ||
|
daf14b275a | ||
|
cf6694e815 | ||
|
704f809e90 | ||
|
0e9499ada7 | ||
|
675f658721 | ||
|
ccc11c1e77 | ||
|
336e1ac779 | ||
|
be99eb26a2 | ||
|
fe9458591a | ||
|
57e816311b | ||
|
09b76295f1 | ||
|
38acce2f3f | ||
|
8f44d00f22 | ||
|
4386eb751f | ||
|
d6aab069ed | ||
|
f4fb8dbc87 | ||
|
c7c18e0eb2 | ||
|
0a2d618d85 | ||
|
c8a83ace59 | ||
|
59d132f0b3 | ||
|
18dd49a4ab | ||
|
46637b1164 | ||
|
7b18c67ac9 | ||
|
6eb3c05cb7 | ||
|
82f24bedc2 | ||
|
88da50674f | ||
|
35a7db49b4 | ||
|
f4729e2418 | ||
|
f7330fd027 | ||
|
755d8e21ad | ||
|
7c0bf06d96 | ||
|
0d196e29e8 | ||
|
b86606fa38 | ||
|
74602bb487 | ||
|
1465e0cbb2 | ||
|
928b603d19 | ||
|
8ff542c1a2 | ||
|
39a0db6ecf | ||
|
9cc93c05cd | ||
|
b31418edd9 | ||
|
242f7f1a52 | ||
|
8d7f1e430a | ||
|
a6b32adc45 | ||
|
1f0dca4614 | ||
|
1d665da32f | ||
|
09adc26eb6 | ||
|
297346a762 | ||
|
acd648061d | ||
|
c5fe0a096d | ||
|
0c7bf6801f | ||
|
5863f7e048 | ||
|
a785c46ca8 | ||
|
6bddfd3086 | ||
|
dd8a726b25 | ||
|
08eecba32b | ||
|
9c2212df15 | ||
|
9b4746967c | ||
|
00da1facc4 | ||
|
b461993775 | ||
|
b885e57762 | ||
|
081449d7a5 | ||
|
ee3a21a537 | ||
|
61c7732e12 | ||
|
57c2ca7f26 | ||
|
0704009dd7 | ||
|
14a6aafb50 | ||
|
471a1e8111 | ||
|
123c254b84 | ||
|
db43f63c53 | ||
|
3ecd520f8e | ||
|
e9935d358c | ||
|
8d653b148b | ||
|
b702f2dac3 | ||
|
d59b8541f2 | ||
|
efd34d0d7d | ||
|
2ec2935f78 | ||
|
540541caa2 | ||
|
a13d64bf98 | ||
|
bab7d885aa | ||
|
42229dc0b8 | ||
|
e3d8e92bdc | ||
|
6fc73a8433 | ||
|
b1a0a78a51 | ||
|
9c7d8b3096 | ||
|
93feb1a666 | ||
|
bb0e2121a3 | ||
|
d21b7fd3af | ||
|
743553f3e9 | ||
|
a3ccbb5b7f | ||
|
4b7cb813e6 | ||
|
23b8214549 | ||
|
08feb6b664 | ||
|
1aa5dc75df | ||
|
ee234aff61 | ||
|
a3f3e310fb | ||
|
ea56bdca5f | ||
|
45c836badc | ||
|
f9ea4ab69a | ||
|
e6d46eeb55 | ||
|
5bb0c92b6c | ||
|
c1e6be47d7 | ||
|
79a5e68816 | ||
|
9bcbbd419f | ||
|
f460b7543e | ||
|
1cb649525d | ||
|
99861e3e06 | ||
|
66b8a43e5f | ||
|
d285905826 | ||
|
4df2320ba6 | ||
|
0fe99cc00c | ||
|
580401ecbf | ||
|
7aa29720f0 | ||
|
3e5c844a77 | ||
|
4047c5c068 | ||
|
03d924238c | ||
|
bc1248ed9e | ||
|
dd52c08b74 | ||
|
b811b819e2 | ||
|
da985b25ce | ||
|
ae9c51df7c | ||
|
ff1c5815bb | ||
|
87f8d37be5 | ||
|
f4b96c1041 | ||
|
a3f72303d1 | ||
|
4317806ade | ||
|
578f19a682 | ||
|
f9b6404950 | ||
|
52517e3e23 | ||
|
36e96e3481 | ||
|
a765410d0f | ||
|
43fc2e528c | ||
|
cb90eda213 | ||
|
5f9c18b2b3 | ||
|
4384b85046 | ||
|
e0973a84a0 | ||
|
054bc55a1c | ||
|
4fb718d405 | ||
|
df35049196 | ||
|
ce75461380 | ||
|
cea85c30a4 | ||
|
6039138323 | ||
|
eb43e73785 | ||
|
c077a0361a | ||
|
6f21a94d18 | ||
|
8ebf0e68ec | ||
|
3685cc7660 | ||
|
9d9ccdbe43 | ||
|
81b29d6263 | ||
|
6591f87b28 | ||
|
efc78c18c1 | ||
|
f5a3c0dd6c | ||
|
382101ecc7 | ||
|
86c3481eff | ||
|
039eb66c8c | ||
|
36148ed083 | ||
|
db4c7dcf15 | ||
|
bec566282e | ||
|
fa9be55018 | ||
|
458239b46d | ||
|
ae85ee1c6f | ||
|
08d5a836ef | ||
|
ad789542b8 | ||
|
1f7802db97 | ||
|
c876124efe | ||
|
3a78ac4b32 | ||
|
7ebc3da7cb | ||
|
2e36ba0a00 | ||
|
69a158dcc2 | ||
|
913d6f3ff3 | ||
|
044cb09ae8 | ||
|
9da8e478dd | ||
|
c8f3672a88 | ||
|
edf85b820d | ||
|
c04a4afac1 | ||
|
65ad6362d7 | ||
|
f9a0ae1dd4 | ||
|
fb26b01688 | ||
|
63628fdf1c | ||
|
2e317d3f6e | ||
|
ce69882180 | ||
|
649abeda40 | ||
|
4cfd62cddf | ||
|
38fc6c75f3 | ||
|
8671602ba9 | ||
|
3d08e3a08c | ||
|
d4a075d738 | ||
|
bb77e6c12d | ||
|
fabc0ad157 | ||
|
a13fb154ae | ||
|
36c66303df | ||
|
f65e29c077 | ||
|
a97c8a8966 | ||
|
69b7776af5 | ||
|
18c1edf15c | ||
|
70ffec4509 | ||
|
bc196a35e1 | ||
|
8d31cfbfff | ||
|
e84a432f76 | ||
|
1fc9f11253 | ||
|
0dfe5fa2d6 | ||
|
1d17313949 | ||
|
9c318a17f5 | ||
|
72fa108cbc | ||
|
db134c5d71 | ||
|
73b68015de | ||
|
e4919e414f | ||
|
f7606de13a | ||
|
483bda4b2d | ||
|
edd57028a1 | ||
|
083b85c655 | ||
|
d5027b6c09 | ||
|
a044ec8b53 | ||
|
f93d72c09b | ||
|
2f22337125 | ||
|
781ad8a79e | ||
|
cada7202aa | ||
|
0b331e2213 | ||
|
0734ca0132 | ||
|
0b83cc21be | ||
|
b68e605d56 | ||
|
42991dc89a | ||
|
160de9fbda | ||
|
d644289fcb | ||
|
fd9ff7cd6f |
507
.drone.yml
507
.drone.yml
File diff suppressed because it is too large
Load Diff
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,6 +36,8 @@ _testmain.go
|
||||
coverage.all
|
||||
cpu.out
|
||||
|
||||
/modules/migration/bindata.go
|
||||
/modules/migration/bindata.go.hash
|
||||
/modules/options/bindata.go
|
||||
/modules/options/bindata.go.hash
|
||||
/modules/public/bindata.go
|
||||
|
@ -13,7 +13,7 @@ linters:
|
||||
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
|
||||
- gofmt
|
||||
- misspell
|
||||
- gocritic
|
||||
#- gocritic # TODO: disabled until fixed with go 1.18
|
||||
- bidichk
|
||||
- ineffassign
|
||||
- revive
|
||||
@ -22,7 +22,11 @@ linters:
|
||||
fast: false
|
||||
|
||||
run:
|
||||
timeout: 3m
|
||||
timeout: 10m
|
||||
skip-dirs:
|
||||
- node_modules
|
||||
- public
|
||||
- web_src
|
||||
|
||||
linters-settings:
|
||||
gocritic:
|
||||
@ -57,6 +61,9 @@ linters-settings:
|
||||
- name: errorf
|
||||
- name: duplicated-imports
|
||||
- name: modifies-value-receiver
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
lang-version: 1.18
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
@ -144,3 +151,11 @@ issues:
|
||||
- path: models/user/openid.go
|
||||
linters:
|
||||
- golint
|
||||
- linters: staticcheck
|
||||
text: "strings.Title is deprecated: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead."
|
||||
- linters: staticcheck
|
||||
text: "util.FindClosure is deprecated: This function can not handle newlines. Many elements can be existed over multiple lines(e.g. link labels). Use text.Reader.FindClosure."
|
||||
- linters: staticcheck
|
||||
text: "gossh.SigAlgoRSASHA2256 is deprecated: use KeyAlgoRSASHA256."
|
||||
- linters: staticcheck
|
||||
text: "gossh.SigAlgoRSASHA2512 is deprecated: use KeyAlgoRSASHA512."
|
||||
|
288
CHANGELOG.md
288
CHANGELOG.md
@ -4,13 +4,275 @@ 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.16.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.16.0-rc1) - 2022-01-19
|
||||
## [1.16.9](https://github.com/go-gitea/gitea/releases/tag/v1.16.9) - 2022-07-12
|
||||
|
||||
* SECURITY
|
||||
* Add write check for creating Commit status (#20332) (#20334)
|
||||
* Check for permission when fetching user controlled issues (#20133) (#20196)
|
||||
* BUGFIXES
|
||||
* Hide notify mail setting ui if not enabled (#20138) (#20337)
|
||||
* Add write check for creating Commit status (#20332) (#20334)
|
||||
* Only show Followers that current user can access (#20220) (#20253)
|
||||
* Release page show all tags in compare dropdown (#20070) (#20071)
|
||||
* Fix permission check for delete tag (#19985) (#20001)
|
||||
* Only log non ErrNotExist errors in git.GetNote (#19884) (#19905)
|
||||
* Use exact search instead of fuzzy search for branch filter dropdown (#19885) (#19893)
|
||||
* Set Setpgid on child git processes (#19865) (#19881)
|
||||
* Import git from alpine 3.16 repository as 2.30.4 is needed for `safe.directory = '*'` to work but alpine 3.13 has 2.30.3 (#19876)
|
||||
* Ensure responses are context.ResponseWriters (#19843) (#19859)
|
||||
* Fix incorrect usage of `Count` function (#19850)
|
||||
* Fix raw endpoint PDF file headers (#19825) (#19826)
|
||||
* Make WIP prefixes case insensitive, e.g. allow `Draft` as a WIP prefix (#19780) (#19811)
|
||||
* Don't return 500 on NotificationUnreadCount (#19802)
|
||||
* Prevent NPE when cache service is disabled (#19703) (#19783)
|
||||
* Detect truncated utf-8 characters at the end of content as still representing utf-8 (#19773) (#19774)
|
||||
* Fix doctor pq: syntax error at or near "." quote user table name (#19765) (#19770)
|
||||
* Fix bug with assigneees (#19757)
|
||||
|
||||
## [1.16.8](https://github.com/go-gitea/gitea/releases/tag/v1.16.8) - 2022-05-16
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Add doctor check/fix for bogus action rows (#19656) (#19669)
|
||||
* Make .cs highlighting legible on dark themes. (#19604) (#19605)
|
||||
* BUGFIXES
|
||||
* Fix oauth setting list bug (#19681)
|
||||
* Delete user related oauth stuff on user deletion too (#19677) (#19680)
|
||||
* Fix new release from tags list UI (#19670) (#19673)
|
||||
* Prevent NPE when checking repo units if the user is nil (#19625) (#19630)
|
||||
* GetFeeds must always discard actions with dangling repo_id (#19598) (#19629)
|
||||
* Call MultipartForm.RemoveAll when request finishes (#19606) (#19607)
|
||||
* Avoid MoreThanOne error when creating a branch whose name conflicts with other ref names (#19557) (#19591)
|
||||
* Fix sending empty notifications (#19589) (#19590)
|
||||
* Ignore DNS error when doing migration allow/block check (#19566) (#19567)
|
||||
* Fix issue overview for teams (#19652) (#19653)
|
||||
|
||||
## [1.16.7](https://github.com/go-gitea/gitea/releases/tag/v1.16.7) - 2022-05-02
|
||||
|
||||
* SECURITY
|
||||
* Escape git fetch remote (#19487) (#19490)
|
||||
* BUGFIXES
|
||||
* Don't overwrite err with nil (#19572) (#19574)
|
||||
* On Migrations, only write commit-graph if wiki clone was successful (#19563) (#19568)
|
||||
* Respect DefaultUserIsRestricted system default when creating new user (#19310) (#19560)
|
||||
* Don't error when branch's commit doesn't exist (#19547) (#19548)
|
||||
* Support `hostname:port` to pass host matcher's check (#19543) (#19544)
|
||||
* Prevent intermittent race in attribute reader close (#19537) (#19539)
|
||||
* Fix 64-bit atomic operations on 32-bit machines (#19531) (#19532)
|
||||
* Prevent dangling archiver goroutine (#19516) (#19526)
|
||||
* Fix migrate release from github (#19510) (#19523)
|
||||
* When view _Siderbar or _Footer, just display once (#19501) (#19522)
|
||||
* Fix blame page select range error and some typos (#19503)
|
||||
* Fix name of doctor fix "authorized-keys" in hints (#19464) (#19484)
|
||||
* User specific repoID or xorm builder conditions for issue search (#19475) (#19476)
|
||||
* Prevent dangling cat-file calls (goroutine alternative) (#19454) (#19466)
|
||||
* RepoAssignment ensure to close before overwrite (#19449) (#19460)
|
||||
* Set correct PR status on 3way on conflict checking (#19457) (#19458)
|
||||
* Mark TemplateLoading error as "UnprocessableEntity" (#19445) (#19446)
|
||||
|
||||
## [1.16.6](https://github.com/go-gitea/gitea/releases/tag/v1.16.6) - 2022-04-20
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Only request write when necessary (#18657) (#19422)
|
||||
* Disable service worker by default (#18914) (#19342)
|
||||
* BUGFIXES
|
||||
* When dumping trim the standard suffices instead of a random suffix (#19440) (#19447)
|
||||
* Fix DELETE request for non-existent public key (#19443) (#19444)
|
||||
* Don't panic on ErrEmailInvalid (#19441) (#19442)
|
||||
* Add uploadpack.allowAnySHA1InWant to allow --filter=blob:none with older git clients (#19430) (#19438)
|
||||
* Warn on SSH connection for incorrect configuration (#19317) (#19437)
|
||||
* Search Issues via API, dont show 500 if filter result in empty list (#19244) (#19436)
|
||||
* When updating mirror repo intervals by API reschedule next update too (#19429) (#19433)
|
||||
* Fix nil error when some pages are rendered outside request context (#19427) (#19428)
|
||||
* Fix double blob-hunk on diff page (#19404) (#19405)
|
||||
* Don't allow merging PR's which are being conflict checked (#19357) (#19358)
|
||||
* Fix middleware function's placements (#19377) (#19378)
|
||||
* Fix invalid CSRF token bug, make sure CSRF tokens can be up-to-date (#19338)
|
||||
* Restore user autoregistration with email addresses (#19261) (#19312)
|
||||
* Move checks for pulls before merge into own function (#19271) (#19277)
|
||||
* Granular webhook events in editHook (#19251) (#19257)
|
||||
* Only send webhook events to active system webhooks and only deliver to active hooks (#19234) (#19248)
|
||||
* Use full output of git show-ref --tags to get tags for PushUpdateAddTag (#19235) (#19236)
|
||||
* Touch mirrors on even on fail to update (#19217) (#19233)
|
||||
* Hide sensitive content on admin panel progress monitor (#19218 & #19226) (#19231)
|
||||
* Fix clone url JS error for the empty repo page (#19209)
|
||||
* Bump goldmark to v1.4.11 (#19201) (#19203)
|
||||
* TESTING
|
||||
* Prevent intermittent failures in RepoIndexerTest (#19225 #19229) (#19228)
|
||||
* BUILD
|
||||
* Revert the minimal golang version requirement from 1.17 to 1.16 and add a warning in Makefile (#19319)
|
||||
* MISC
|
||||
* Performance improvement for add team user when org has more than 1000 repositories (#19227) (#19289)
|
||||
* Check go and nodejs version by go.mod and package.json (#19197) (#19254)
|
||||
|
||||
## [1.16.5](https://github.com/go-gitea/gitea/releases/tag/v1.16.5) - 2022-03-23
|
||||
|
||||
* BREAKING
|
||||
* Bump to build with go1.18 (#19120 et al) (#19127)
|
||||
* SECURITY
|
||||
* Prevent redirect to Host (2) (#19175) (#19186)
|
||||
* Try to prevent autolinking of displaynames by email readers (#19169) (#19183)
|
||||
* Clean paths when looking in Storage (#19124) (#19179)
|
||||
* Do not send notification emails to inactive users (#19131) (#19139)
|
||||
* Do not send activation email if manual confirm is set (#19119) (#19122)
|
||||
* ENHANCEMENTS
|
||||
* Use the new/choose link for New Issue on project page (#19172) (#19176)
|
||||
* BUGFIXES
|
||||
* Fix showing issues in your repositories (#18916) (#19191)
|
||||
* Fix compare link in active feeds for new branch (#19149) (#19185)
|
||||
* Redirect .wiki/* ui link to /wiki (#18831) (#19184)
|
||||
* Ensure deploy keys with write access can push (#19010) (#19182)
|
||||
* Ensure that setting.LocalURL always has a trailing slash (#19171) (#19177)
|
||||
* Cleanup protected branches when deleting users & teams (#19158) (#19174)
|
||||
* Use IterateBufferSize whilst querying repositories during adoption check (#19140) (#19160)
|
||||
* Fix NPE /repos/issues/search when not signed in (#19154) (#19155)
|
||||
* Use custom favicon when viewing static files if it exists (#19130) (#19152)
|
||||
* Fix the editor height in review box (#19003) (#19147)
|
||||
* Ensure isSSH is set whenever DISABLE_HTTP_GIT is set (#19028) (#19146)
|
||||
* Fix wrong scopes caused by empty scope input (#19029) (#19145)
|
||||
* Make migrations SKIP_TLS_VERIFY apply to git too (#19132) (#19141)
|
||||
* Handle email address not exist (#19089) (#19121)
|
||||
* MISC
|
||||
* Update json-iterator to allow compilation with go1.18 (#18644) (#19100)
|
||||
* Update golang.org/x/crypto (#19097) (#19098)
|
||||
|
||||
## [1.16.4](https://github.com/go-gitea/gitea/releases/tag/v1.16.4) - 2022-03-14
|
||||
|
||||
* SECURITY
|
||||
* Restrict email address validation (#17688) (#19085)
|
||||
* Fix lfs bug (#19072) (#19080)
|
||||
* ENHANCEMENTS
|
||||
* Improve SyncMirrors logging (#19045) (#19050)
|
||||
* BUGFIXES
|
||||
* Refactor mirror code & fix `StartToMirror` (#18904) (#19075)
|
||||
* Update the webauthn_credential_id_sequence in Postgres (#19048) (#19060)
|
||||
* Prevent 500 when there is an error during new auth source post (#19041) (#19059)
|
||||
* If rendering has failed due to a net.OpError stop rendering (attempt 2) (#19049) (#19056)
|
||||
* Fix flag validation (#19046) (#19051)
|
||||
* Add pam account authorization check (#19040) (#19047)
|
||||
* Ignore missing comment for user notifications (#18954) (#19043)
|
||||
* Set `rel="nofollow noindex"` on new issue links (#19023) (#19042)
|
||||
* Upgrading binding package (#19034) (#19035)
|
||||
* Don't show context cancelled errors in attribute reader (#19006) (#19027)
|
||||
* Fix update hint bug (#18996) (#19002)
|
||||
* MISC
|
||||
* Fix potential assignee query for repo (#18994) (#18999)
|
||||
|
||||
## [1.16.3](https://github.com/go-gitea/gitea/releases/tag/v1.16.3) - 2022-03-02
|
||||
|
||||
* SECURITY
|
||||
* Git backend ignore replace objects (#18979) (#18980)
|
||||
* ENHANCEMENTS
|
||||
* Adjust error for already locked db and prevent level db lock on malformed connstr (#18923) (#18938)
|
||||
* BUGFIXES
|
||||
* Set max text height to prevent overflow (#18862) (#18977)
|
||||
* Fix newAttachmentPaths deletion for DeleteRepository() (#18973) (#18974)
|
||||
* Accounts with WebAuthn only (no TOTP) now exist ... fix code to handle that case (#18897) (#18964)
|
||||
* Send 404 on `/{org}.gpg` (#18959) (#18962)
|
||||
* Fix admin user list pagination (#18957) (#18960)
|
||||
* Fix lfs management setting (#18947) (#18946)
|
||||
* Fix login with email panic when email is not exist (#18942)
|
||||
* Update go-org to v1.6.1 (#18932) (#18933)
|
||||
* Fix `<strong>` html in translation (#18929) (#18931)
|
||||
* Fix page and missing return on unadopted repos API (#18848) (#18927)
|
||||
* Allow adminstrator teams members to see other teams (#18918) (#18919)
|
||||
* Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
|
||||
* Correctly link URLs to users/repos with dashes, dots or underscores (… (#18908)
|
||||
* Fix redirect when using lowercase repo name (#18775) (#18902)
|
||||
* Fix migration v210 (#18893) (#18892)
|
||||
* Fix team management UI (#18887) (18886)
|
||||
* BeforeSourcePath should point to base commit (#18880) (#18799)
|
||||
* TRANSLATION
|
||||
* Backport locales from master (#18944)
|
||||
* MISC
|
||||
* Don't update email for organisation (#18905) (#18906)
|
||||
|
||||
## [1.16.2](https://github.com/go-gitea/gitea/releases/tag/v1.16.2) - 2022-02-24
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Show fullname on issue edits and gpg/ssh signing info (#18828)
|
||||
* Immediately Hammer if second kill is sent (#18823) (#18826)
|
||||
* Allow mermaid render error to wrap (#18791)
|
||||
* BUGFIXES
|
||||
* Fix ldap user sync missed email in email_address table (#18786) (#18876)
|
||||
* Update assignees check to include any writing team and change org sidebar (#18680) (#18873)
|
||||
* Don't report signal: killed errors in serviceRPC (#18850) (#18865)
|
||||
* Fix bug where certain LDAP settings were reverted (#18859)
|
||||
* Update go-org to 1.6.0 (#18824) (#18839)
|
||||
* Fix login with email for ldap users (#18800) (#18836)
|
||||
* Fix bug for get user by email (#18834)
|
||||
* Fix panic in EscapeReader (#18820) (#18821)
|
||||
* Fix ldap loginname (#18789) (#18804)
|
||||
* Remove redundant call to UpdateRepoStats during migration (#18591) (#18794)
|
||||
* In disk_channel queues synchronously push to disk on shutdown (#18415) (#18788)
|
||||
* Fix template bug of LFS lock (#18784) (#18787)
|
||||
* Attempt to fix the webauthn migration again - part 3 (#18770) (#18771)
|
||||
* Send mail to issue/pr assignee/reviewer also when OnMention is set (#18707) (#18765)
|
||||
* Fix a broken link in commits_list_small.tmpl (#18763) (#18764)
|
||||
* Increase the size of the webauthn_credential credential_id field (#18739) (#18756)
|
||||
* Prevent dangling GetAttribute calls (#18754) (#18755)
|
||||
* Fix isempty detection of git repository (#18746) (#18750)
|
||||
* Fix source code line highlighting on external tracker (#18729) (#18740)
|
||||
* Prevent double encoding of branch names in delete branch (#18714) (#18738)
|
||||
* Always set PullRequestWorkInProgressPrefixes in PrepareViewPullInfo (#18713) (#18737)
|
||||
* Fix forked repositories missed tags (#18719) (#18735)
|
||||
* Fix release typo (#18728) (#18731)
|
||||
* Separate the details links of commit-statuses in headers (#18661) (#18730)
|
||||
* Update object repo with the migrated repository (#18684) (#18726)
|
||||
* Fix bug for version update hint (#18701) (#18705)
|
||||
* Fix issue with docker-rootless shimming script (#18690) (#18699)
|
||||
* Let `MinUnitAccessMode` return correct perm (#18675) (#18689)
|
||||
* Prevent security failure due to bad APP_ID (#18678) (#18682)
|
||||
* Restart zero worker if there is still work to do (#18658) (#18672)
|
||||
* If rendering has failed due to a net.OpError stop rendering (#18642) (#18645)
|
||||
* TESTING
|
||||
* Ensure git tag tests and others create test repos in tmpdir (#18447) (#18767)
|
||||
* BUILD
|
||||
* Reduce CI go module downloads, add make targets (#18708, #18475, #18443) (#18741)
|
||||
* MISC
|
||||
* Put buttons back in org dashboard (#18817) (#18825)
|
||||
* Various Mermaid improvements (#18776) (#18780)
|
||||
* C preprocessor colors improvement (#18671) (#18696)
|
||||
* Fix the missing i18n key for update checker (#18646) (#18665)
|
||||
|
||||
## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
|
||||
|
||||
* SECURITY
|
||||
* Update JS dependencies, fix lint (#18389) (#18540)
|
||||
* ENHANCEMENTS
|
||||
* Add dropdown icon to label set template dropdown (#18564) (#18571)
|
||||
* BUGFIXES
|
||||
* Comments on migrated issues/prs must link to the comment ID (#18630) (#18637)
|
||||
* Stop logging an error when notes are not found (#18626) (#18635)
|
||||
* Ensure that blob-excerpt links work for wiki (#18587) (#18624)
|
||||
* Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
|
||||
* Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
|
||||
* Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
|
||||
* Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
|
||||
* Collaborator trust model should trust collaborators (#18539) (#18557)
|
||||
* Detect conflicts with 3way merge (#18536) (#18537)
|
||||
* In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
|
||||
* Add `GetUserTeams` (#18499) (#18531)
|
||||
* Fix review excerpt (#18502) (#18530)
|
||||
* Fix for AvatarURL database type (#18487) (#18529)
|
||||
* Use `ImagedProvider` for gplus oauth2 provider (#18504) (#18505)
|
||||
* Fix OAuth Source Edit Page (#18495) (#18503)
|
||||
* Use "read" value for General Access (#18496) (#18500)
|
||||
* Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
|
||||
* BUILD
|
||||
* Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
|
||||
* DOCS
|
||||
* Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
|
||||
|
||||
## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
|
||||
|
||||
* BREAKING
|
||||
* Remove golang vendored directory (#18277)
|
||||
* Paginate releases page & set default page size to 10 (#16857)
|
||||
* Use shadowing script for docker (#17846)
|
||||
* Only allow webhook to send requests to allowed hosts (#17482)
|
||||
* SECURITY
|
||||
* Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
|
||||
* Only view milestones from current repo (#18414) (#18417)
|
||||
* Sanitize user-input on file name (#17666)
|
||||
* Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
|
||||
* FEATURES
|
||||
@ -228,6 +490,16 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Add left padding for chunk header of split diff view (#13397)
|
||||
* Allow U2F 2FA without TOTP (#11573)
|
||||
* BUGFIXES
|
||||
* GitLab reviews may not have the updated_at field set (#18450) (#18461)
|
||||
* Fix detection of no commits when the default branch is not master (#18422) (#18423)
|
||||
* Fix broken oauth2 authentication source edit page (#18412) (#18419)
|
||||
* Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
|
||||
* Fix restore without topic failure (#18387) (#18400)
|
||||
* Fix commit's time (#18375) (#18392)
|
||||
* Fix partial cloning a repo (#18373) (#18377)
|
||||
* Stop trimming preceding and suffixing spaces from editor filenames (#18334)
|
||||
* Prevent showing webauthn error for every time visiting `/user/settings/security` (#18386)
|
||||
* Fix mime-type detection for HTTP server (#18370) (#18371)
|
||||
* Stop trimming preceding and suffixing spaces from editor filenames (#18334)
|
||||
* Restore propagation of ErrDependenciesLeft (#18325)
|
||||
* Fix PR comments UI (#18323)
|
||||
@ -295,10 +567,22 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* BUILD
|
||||
* Add lockfile-check (#18285)
|
||||
* Don't store assets modified time into generated files (#18193)
|
||||
* Use shadowing script for docker (#17846)
|
||||
* MISC
|
||||
* Update JS dependencies (#17611)
|
||||
|
||||
## [1.15.11](https://github.com/go-gitea/gitea/releases/tag/v1.15.11) - 2022-01-29
|
||||
|
||||
* SECURITY
|
||||
* Only view milestones from current repo (#18414) (#18418)
|
||||
* BUGFIXES
|
||||
* Fix broken when no commits and default branch is not master (#18422) (#18424)
|
||||
* Fix commit's time (#18375) (#18409)
|
||||
* Fix restore without topic failure (#18387) (#18401)
|
||||
* Fix mermaid import in 1.15 (it uses ESModule now) (#18382)
|
||||
* Update to go/text 0.3.7 (#18336)
|
||||
* MISC
|
||||
* Upgrade EasyMDE to 2.16.1 (#18278) (#18279)
|
||||
|
||||
## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
|
||||
|
||||
* BUGFIXES
|
||||
|
@ -1,7 +1,5 @@
|
||||
|
||||
###################################
|
||||
#Build stage - temporarily using techknowlogick image until we upgrade to latest official alpine/go image
|
||||
FROM techknowlogick/go:1.17-alpine3.13 AS build-env
|
||||
#Build stage
|
||||
FROM golang:1.18-alpine3.15 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
@ -35,7 +33,6 @@ RUN apk --no-cache add \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gettext \
|
||||
git \
|
||||
linux-pam \
|
||||
openssh \
|
||||
s6 \
|
||||
@ -43,6 +40,8 @@ RUN apk --no-cache add \
|
||||
su-exec \
|
||||
gnupg
|
||||
|
||||
RUN apk add git --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/main
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
git && \
|
||||
|
@ -1,7 +1,5 @@
|
||||
|
||||
###################################
|
||||
#Build stage - temporarily using techknowlogick image until we upgrade to latest official alpine/go image
|
||||
FROM techknowlogick/go:1.17-alpine3.13 AS build-env
|
||||
#Build stage
|
||||
FROM golang:1.18-alpine3.15 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY ${GOPROXY:-direct}
|
||||
@ -34,10 +32,11 @@ RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
gettext \
|
||||
git \
|
||||
curl \
|
||||
gnupg
|
||||
|
||||
RUN apk add git --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/main
|
||||
|
||||
RUN addgroup \
|
||||
-S -g 1000 \
|
||||
git && \
|
||||
|
127
Makefile
127
Makefile
@ -24,10 +24,17 @@ SHASUM ?= shasum -a 256
|
||||
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||
COMMA := ,
|
||||
|
||||
XGO_VERSION := go-1.17.x
|
||||
MIN_GO_VERSION := 001016000
|
||||
MIN_NODE_VERSION := 012017000
|
||||
MIN_GOLANGCI_LINT_VERSION := 001043000
|
||||
XGO_VERSION := go-1.18.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.29.0
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.4.0
|
||||
ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.0
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.3.0
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.44.2
|
||||
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
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
|
||||
DOCKER_IMAGE ?= gitea/gitea
|
||||
DOCKER_TAG ?= latest
|
||||
@ -125,8 +132,6 @@ ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
|
||||
GO_SOURCES += $(BINDATA_DEST)
|
||||
endif
|
||||
|
||||
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger
|
||||
SWAGGER := $(GO) run github.com/go-swagger/go-swagger/cmd/swagger
|
||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g
|
||||
@ -166,6 +171,9 @@ help:
|
||||
@echo " - watch-backend watch backend files and continuously rebuild"
|
||||
@echo " - clean delete backend and integration files"
|
||||
@echo " - clean-all delete backend, frontend and integration files"
|
||||
@echo " - deps install dependencies"
|
||||
@echo " - deps-frontend install frontend dependencies"
|
||||
@echo " - deps-backend install backend dependencies"
|
||||
@echo " - lint lint everything"
|
||||
@echo " - lint-frontend lint frontend files"
|
||||
@echo " - lint-backend lint backend files"
|
||||
@ -193,10 +201,15 @@ help:
|
||||
|
||||
.PHONY: go-check
|
||||
go-check:
|
||||
$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
|
||||
$(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
||||
$(eval MIN_GO_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_GO_VERSION_STR)' | tr '.' ' ')))
|
||||
$(eval GO_VERSION_STR := $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+'))
|
||||
$(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(GO_VERSION_STR)' | tr '.' ' ')))
|
||||
@if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \
|
||||
echo "Gitea requires Go 1.16 or greater to build. You can get it at https://golang.org/dl/"; \
|
||||
echo "Gitea requires Go $(MIN_GO_VERSION_STR) or greater to build, but $(GO_VERSION) was found. You can get an updated version at https://go.dev/dl/"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "WARNING: Please ensure Go $(GO_VERSION_STR) is still maintained to avoid possible security problems. You can check it at https://go.dev/dl/"; \
|
||||
fi
|
||||
|
||||
.PHONY: git-check
|
||||
@ -208,11 +221,12 @@ git-check:
|
||||
|
||||
.PHONY: node-check
|
||||
node-check:
|
||||
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
|
||||
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
|
||||
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
|
||||
$(eval MIN_NODE_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_NODE_VERSION) | grep -o ...)))
|
||||
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
|
||||
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
|
||||
echo "Gitea requires Node.js $(MIN_NODE_VER_FMT) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
|
||||
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@ -231,8 +245,8 @@ clean:
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@echo "Running gitea-fmt(with gofmt)..."
|
||||
@$(GO) run build/code-batch-process.go gitea-fmt -s -w '{file-list}'
|
||||
@echo "Running gitea-fmt (with gofumpt)..."
|
||||
@MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@ -251,7 +265,7 @@ endif
|
||||
|
||||
.PHONY: generate-swagger
|
||||
generate-swagger:
|
||||
$(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
|
||||
$(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
|
||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
|
||||
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
|
||||
|
||||
@ -267,21 +281,18 @@ swagger-check: generate-swagger
|
||||
.PHONY: swagger-validate
|
||||
swagger-validate:
|
||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
|
||||
$(SWAGGER) validate './$(SWAGGER_SPEC)'
|
||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
||||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
|
||||
|
||||
.PHONY: errcheck
|
||||
errcheck:
|
||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install github.com/kisielk/errcheck@8ddee489636a8311a376fc92e27a6a13c6658344; \
|
||||
fi
|
||||
@echo "Running errcheck..."
|
||||
@errcheck $(GO_PACKAGES)
|
||||
$(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check:
|
||||
# get all go files and run gitea-fmt (with gofmt) on them
|
||||
@diff=$$($(GO) run build/code-batch-process.go gitea-fmt -s -d '{file-list}'); \
|
||||
@diff=$$(MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fmt' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
@ -320,10 +331,7 @@ watch-frontend: node-check node_modules
|
||||
|
||||
.PHONY: watch-backend
|
||||
watch-backend: go-check
|
||||
@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install github.com/cosmtrek/air@bedc18201271882c2be66d216d0e1a275b526ec4; \
|
||||
fi
|
||||
air -c .air.toml
|
||||
$(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||
|
||||
.PHONY: test
|
||||
test: test-frontend test-backend
|
||||
@ -396,6 +404,11 @@ test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
|
||||
|
||||
.PHONY: test-sqlite-migration\#%
|
||||
test-sqlite-migration\#%: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
|
||||
|
||||
|
||||
generate-ini-mysql:
|
||||
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
||||
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
||||
@ -591,12 +604,9 @@ $(DIST_DIRS):
|
||||
|
||||
.PHONY: release-windows
|
||||
release-windows: | $(DIST_DIRS)
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install src.techknowlogick.com/xgo@latest; \
|
||||
fi
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||
ifeq (,$(findstring gogit,$(TAGS)))
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||
endif
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
@ -604,20 +614,14 @@ endif
|
||||
|
||||
.PHONY: release-linux
|
||||
release-linux: | $(DIST_DIRS)
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install src.techknowlogick.com/xgo@latest; \
|
||||
fi
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-darwin
|
||||
release-darwin: | $(DIST_DIRS)
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install src.techknowlogick.com/xgo@latest; \
|
||||
fi
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
@ -632,10 +636,7 @@ release-check: | $(DIST_DIRS)
|
||||
|
||||
.PHONY: release-compress
|
||||
release-compress: | $(DIST_DIRS)
|
||||
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) install github.com/ulikunitz/xz/cmd/gxz@v0.5.10; \
|
||||
fi
|
||||
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
|
||||
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done;
|
||||
|
||||
.PHONY: release-sources
|
||||
release-sources: | $(DIST_DIRS)
|
||||
@ -656,6 +657,25 @@ docs:
|
||||
fi
|
||||
cd docs; make trans-copy clean build-offline;
|
||||
|
||||
.PHONY: deps
|
||||
deps: deps-frontend deps-backend
|
||||
|
||||
.PHONY: deps-frontend
|
||||
deps-frontend: node_modules
|
||||
|
||||
.PHONY: deps-backend
|
||||
deps-backend:
|
||||
$(GO) mod download
|
||||
$(GO) install $(AIR_PACKAGE)
|
||||
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE)
|
||||
$(GO) install $(ERRCHECK_PACKAGE)
|
||||
$(GO) install $(GOFUMPT_PACKAGE)
|
||||
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||
$(GO) install $(GXZ_PAGAGE)
|
||||
$(GO) install $(MISSPELL_PACKAGE)
|
||||
$(GO) install $(SWAGGER_PACKAGE)
|
||||
$(GO) install $(XGO_PACKAGE)
|
||||
|
||||
node_modules: package-lock.json
|
||||
npm install --no-save
|
||||
@touch node_modules
|
||||
@ -748,22 +768,19 @@ pr\#%: clean-all
|
||||
$(GO) run contrib/pr/checkout.go $*
|
||||
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: golangci-lint-check
|
||||
golangci-lint run --timeout 10m
|
||||
golangci-lint:
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
|
||||
|
||||
.PHONY: golangci-lint-check
|
||||
golangci-lint-check:
|
||||
$(eval GOLANGCI_LINT_VERSION := $(shell printf "%03d%03d%03d" $(shell golangci-lint --version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
|
||||
$(eval MIN_GOLANGCI_LINT_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_GOLANGCI_LINT_VERSION) | grep -o ...)))
|
||||
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
echo "Downloading golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
|
||||
export BINARY="golangci-lint"; \
|
||||
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
|
||||
elif [ "$(GOLANGCI_LINT_VERSION)" -lt "$(MIN_GOLANGCI_LINT_VERSION)" ]; then \
|
||||
echo "Downloading newer version of golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
|
||||
export BINARY="golangci-lint"; \
|
||||
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
|
||||
fi
|
||||
# workaround step for the lint-backend-windows CI task because 'go run' can not
|
||||
# have distinct GOOS/GOARCH for its build and run steps
|
||||
.PHONY: golangci-lint-windows
|
||||
golangci-lint-windows:
|
||||
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||
golangci-lint run
|
||||
|
||||
.PHONY: editorconfig-checker
|
||||
editorconfig-checker:
|
||||
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
|
@ -73,7 +73,7 @@ or if SQLite support is required:
|
||||
|
||||
The `build` target is split into two sub-targets:
|
||||
|
||||
- `make backend` which requires [Go 1.16](https://golang.org/dl/) or greater.
|
||||
- `make backend` which requires [Go 1.17](https://go.dev/dl/) or greater.
|
||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
|
||||
|
||||
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
|
||||
|
@ -40,7 +40,7 @@ func passThroughCmd(cmd string, args []string) error {
|
||||
}
|
||||
c := exec.Cmd{
|
||||
Path: foundCmd,
|
||||
Args: args,
|
||||
Args: append([]string{cmd}, args...),
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
@ -270,9 +270,10 @@ func main() {
|
||||
if containsString(subArgs, "-w") {
|
||||
cmdErrors = append(cmdErrors, giteaFormatGoImports(files))
|
||||
}
|
||||
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", substArgs))
|
||||
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-l"), containsString(subArgs, "-w")))
|
||||
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", "1.17"}, substArgs...)))
|
||||
case "misspell":
|
||||
cmdErrors = append(cmdErrors, passThroughCmd("misspell", substArgs))
|
||||
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("MISSPELL_PACKAGE")}, substArgs...)))
|
||||
default:
|
||||
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
|
||||
}
|
||||
|
20
cmd/admin.go
20
cmd/admin.go
@ -25,6 +25,7 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||
@ -113,6 +114,10 @@ var (
|
||||
Name: "access-token",
|
||||
Usage: "Generate access token for the user",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "restricted",
|
||||
Usage: "Make a restricted user account",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -537,17 +542,26 @@ func runCreateUser(c *cli.Context) error {
|
||||
changePassword = c.Bool("must-change-password")
|
||||
}
|
||||
|
||||
restricted := util.OptionalBoolNone
|
||||
|
||||
if c.IsSet("restricted") {
|
||||
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
||||
}
|
||||
|
||||
u := &user_model.User{
|
||||
Name: username,
|
||||
Email: c.String("email"),
|
||||
Passwd: password,
|
||||
IsActive: true,
|
||||
IsAdmin: c.Bool("admin"),
|
||||
MustChangePassword: changePassword,
|
||||
Theme: setting.UI.DefaultTheme,
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(u); err != nil {
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsRestricted: restricted,
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
|
||||
return fmt.Errorf("CreateUser: %v", err)
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ func argsSet(c *cli.Context, args ...string) error {
|
||||
return errors.New(a + " is not set")
|
||||
}
|
||||
|
||||
if util.IsEmptyString(a) {
|
||||
if util.IsEmptyString(c.String(a)) {
|
||||
return errors.New(a + " is required")
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func (o outputType) String() string {
|
||||
}
|
||||
|
||||
var outputTypeEnum = &outputType{
|
||||
Enum: []string{"zip", "rar", "tar", "sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4"},
|
||||
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4"},
|
||||
Default: "zip",
|
||||
}
|
||||
|
||||
@ -160,7 +160,12 @@ func runDump(ctx *cli.Context) error {
|
||||
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
|
||||
}
|
||||
} else {
|
||||
fileName = strings.TrimSuffix(fileName, path.Ext(fileName))
|
||||
for _, suffix := range outputTypeEnum.Enum {
|
||||
if strings.HasSuffix(fileName, "."+suffix) {
|
||||
fileName = strings.TrimSuffix(fileName, "."+suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
fileName += "." + outType
|
||||
}
|
||||
setting.LoadFromExisting()
|
||||
|
@ -185,7 +185,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
reponame := os.Getenv(models.EnvRepoName)
|
||||
userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(models.EnvPRID), 10, 64)
|
||||
isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
|
||||
deployKeyID, _ := strconv.ParseInt(os.Getenv(models.EnvDeployKeyID), 10, 64)
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
UserID: userID,
|
||||
@ -194,7 +194,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||
GitPushOptions: pushOptions(),
|
||||
PullRequestID: prID,
|
||||
IsDeployKey: isDeployKey,
|
||||
DeployKeyID: deployKeyID,
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
15
cmd/serv.go
15
cmd/serv.go
@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/pprof"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/lfs"
|
||||
|
||||
@ -243,11 +244,11 @@ func runServ(c *cli.Context) error {
|
||||
os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10))
|
||||
os.Setenv(models.EnvRepoID, strconv.FormatInt(results.RepoID, 10))
|
||||
os.Setenv(models.EnvPRID, fmt.Sprintf("%d", 0))
|
||||
os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey))
|
||||
os.Setenv(models.EnvDeployKeyID, fmt.Sprintf("%d", results.DeployKeyID))
|
||||
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
|
||||
os.Setenv(models.EnvAppURL, setting.AppURL)
|
||||
|
||||
//LFS token authentication
|
||||
// LFS token authentication
|
||||
if verb == lfsAuthenticateVerb {
|
||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||
|
||||
@ -297,6 +298,16 @@ func runServ(c *cli.Context) error {
|
||||
gitcmd = exec.CommandContext(ctx, verb, repoPath)
|
||||
}
|
||||
|
||||
// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
|
||||
// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
|
||||
if _, err := os.Stat(setting.RepoRootPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fail("Incorrect configuration.",
|
||||
"Directory `[repository]` `ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.")
|
||||
}
|
||||
}
|
||||
|
||||
process.SetSysProcAttribute(gitcmd)
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
|
@ -33,10 +33,8 @@ for i in "$@"; do
|
||||
done
|
||||
|
||||
if [ -z "$APP_INI_SET" ]; then
|
||||
CONF_ARG="-c \"$APP_INI\""
|
||||
CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
|
||||
fi
|
||||
|
||||
# Provide FHS compliant defaults
|
||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
|
||||
|
||||
|
||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"
|
||||
|
@ -880,7 +880,7 @@ PATH =
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; List of prefixes used in Pull Request title to mark them as Work In Progress
|
||||
;; List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner)
|
||||
;WORK_IN_PROGRESS_PREFIXES = WIP:,[WIP]
|
||||
;;
|
||||
;; List of keywords used in Pull Request comments to automatically close a related issue
|
||||
@ -1075,7 +1075,7 @@ PATH =
|
||||
;SEARCH_REPO_DESCRIPTION = true
|
||||
;;
|
||||
;; Whether to enable a Service Worker to cache frontend assets
|
||||
;USE_SERVICE_WORKER = true
|
||||
;USE_SERVICE_WORKER = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1,19 +1,18 @@
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-rootless
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}-rootless
|
||||
{{/each}}
|
||||
- "latest-rootless"
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-amd64-rootless
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64-rootless
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-arm64-rootless
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64-rootless
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
|
@ -1,20 +1,19 @@
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
- "latest"
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-amd64
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-arm64
|
||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
variant: v8
|
||||
|
@ -32,11 +32,9 @@ for i in "$@"; do
|
||||
done
|
||||
|
||||
if [ -z "$APP_INI_SET" ]; then
|
||||
CONF_ARG="-c \"$APP_INI\""
|
||||
CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
|
||||
fi
|
||||
|
||||
|
||||
# Provide docker defaults
|
||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
|
||||
|
||||
|
||||
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"
|
||||
|
@ -18,9 +18,9 @@ params:
|
||||
description: Git with a cup of tea
|
||||
author: The Gitea Authors
|
||||
website: https://docs.gitea.io
|
||||
version: 1.15.10
|
||||
version: 1.16.4
|
||||
minGoVersion: 1.16
|
||||
goVersion: 1.17
|
||||
goVersion: 1.18
|
||||
minNodeVersion: 12.17
|
||||
|
||||
outputs:
|
||||
|
@ -87,7 +87,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||
### Repository - Pull Request (`repository.pull-request`)
|
||||
|
||||
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request
|
||||
title to mark them as Work In Progress
|
||||
title to mark them as Work In Progress. These are matched in a case-insensitive manner.
|
||||
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: List of
|
||||
keywords used in Pull Request comments to automatically close a related issue
|
||||
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen
|
||||
@ -189,7 +189,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
add it to this config.
|
||||
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
|
||||
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
|
||||
- `USE_SERVICE_WORKER`: **true**: Whether to enable a Service Worker to cache frontend assets.
|
||||
- `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets.
|
||||
|
||||
### UI - Admin (`ui.admin`)
|
||||
|
||||
|
@ -42,7 +42,7 @@ To maintain understandable code and avoid circular dependencies it is important
|
||||
- `modules/setting`: Store all system configurations read from ini files and has been referenced by everywhere. But they should be used as function parameters when possible.
|
||||
- `modules/git`: Package to interactive with `Git` command line or Gogit package.
|
||||
- `public`: Compiled frontend files (javascript, images, css, etc.)
|
||||
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
|
||||
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) must not depend on routers.
|
||||
- `routers/api` Contains routers for `/api/v1` aims to handle RESTful API requests.
|
||||
- `routers/install` Could only respond when system is in INSTALL mode (INSTALL_LOCK=false).
|
||||
- `routers/private` will only be invoked by internal sub commands, especially `serv` and `hooks`.
|
||||
@ -106,10 +106,20 @@ i.e. `servcies/user`, `models/repository`.
|
||||
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
|
||||
i.e. `import user_service "code.gitea.io/gitea/services/user"`
|
||||
|
||||
### Important Gotchas
|
||||
|
||||
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
|
||||
- This will cause all rows in the table to be updated with the non-zero values of the exemplar - including IDs.
|
||||
- You should usually write `x.ID(id).Update(exemplar)`.
|
||||
- If during a migration you are inserting into a table using `x.Insert(exemplar)` where the ID is preset:
|
||||
- You will need to ``SET IDENTITY_INSERT `table` ON`` for the MSSQL variant (the migration will fail otherwise)
|
||||
- However, you will also need to update the id sequence for postgres - the migration will silently pass here but later insertions will fail:
|
||||
``SELECT setval('table_name_id_seq', COALESCE((SELECT MAX(id)+1 FROM `table_name`), 1), false)``
|
||||
|
||||
### Future Tasks
|
||||
|
||||
Currently, we are creating some refactors to do the following things:
|
||||
|
||||
- Correct that codes which doesn't follow the rules.
|
||||
- There are too many files in `models`, so we are moving some of them into a sub package `models/xxx`.
|
||||
- Some `modules` sub packages should be moved to `services` because they depends on `models`.
|
||||
- Some `modules` sub packages should be moved to `services` because they depend on `models`.
|
||||
|
@ -185,8 +185,6 @@ Before committing, make sure the linters pass:
|
||||
make lint-frontend
|
||||
```
|
||||
|
||||
Note: When working on frontend code, set `USE_SERVICE_WORKER` to `false` in `app.ini` to prevent undesirable caching of frontend assets.
|
||||
|
||||
### Building and adding SVGs
|
||||
|
||||
SVG icons are built using the `make svg` target which compiles the icon sources defined in `build/generate-svg.js` into the output directory `public/img/svg`. Custom icons can be added in the `web_src/svg` directory.
|
||||
|
@ -32,7 +32,7 @@ image as a service. Since there is no database available, one can be initialized
|
||||
Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`.
|
||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/`
|
||||
If you don't give the volume correct permissions, the container may not start.
|
||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag.
|
||||
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
|
||||
|
||||
```yaml
|
||||
version: "2"
|
||||
|
@ -34,7 +34,7 @@ image as a service. Since there is no database available, one can be initialized
|
||||
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
|
||||
Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
|
||||
If you don't give the volume correct permissions, the container may not start.
|
||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag.
|
||||
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev`)
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
@ -43,7 +43,7 @@ Vous devriez avoir une instance fonctionnelle de Gitea. Pour accèder à l'inter
|
||||
|
||||
## Named Volumes
|
||||
|
||||
Ce guide aboutira à une installation avec les données Gita et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité.
|
||||
Ce guide aboutira à une installation avec les données Gitea et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité.
|
||||
|
||||
### The Database
|
||||
|
||||
|
160
go.mod
160
go.mod
@ -3,41 +3,30 @@ module code.gitea.io/gitea
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.78.0 // indirect
|
||||
code.gitea.io/gitea-vet v0.2.1
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
|
||||
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1
|
||||
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
|
||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210705153151-cc34b1f6908b // indirect
|
||||
github.com/PuerkitoBio/goquery v1.7.0
|
||||
github.com/alecthomas/chroma v0.9.4
|
||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/blevesearch/bleve/v2 v2.3.0
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||
github.com/caddyserver/certmagic v0.15.2
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/blevesearch/bleve/v2 v2.3.1
|
||||
github.com/caddyserver/certmagic v0.15.4
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
|
||||
github.com/couchbase/gomemcached v0.1.2 // indirect
|
||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.10.0
|
||||
github.com/denisenkom/go-mssqldb v0.12.0
|
||||
github.com/djherbis/buffer v1.2.0
|
||||
github.com/djherbis/nio/v3 v3.0.1
|
||||
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
|
||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
|
||||
github.com/emirpasic/gods v1.12.0
|
||||
github.com/ethantkoenig/rupture v1.0.0
|
||||
github.com/gliderlabs/ssh v0.3.3
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.4
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.0
|
||||
github.com/go-enry/go-enry/v2 v2.7.1
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
@ -48,105 +37,130 @@ require (
|
||||
github.com/go-swagger/go-swagger v0.27.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||
github.com/google/go-github/v39 v39.2.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-version v1.3.1
|
||||
github.com/hashicorp/go-version v1.4.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/huandu/xstrings v1.3.2
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||
github.com/json-iterator/go v1.1.11
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||
github.com/klauspost/compress v1.13.1
|
||||
github.com/klauspost/compress v1.13.6
|
||||
github.com/klauspost/cpuid/v2 v2.0.9
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/lib/pq v1.10.2
|
||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
github.com/markbates/goth v1.68.0
|
||||
github.com/mattn/go-isatty v0.0.13
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.8
|
||||
github.com/mholt/archiver/v3 v3.5.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.16
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.12
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
|
||||
github.com/markbates/goth v1.69.0
|
||||
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/minio/minio-go/v7 v7.0.23
|
||||
github.com/msteinert/pam v1.0.0
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/niklasfasching/go-org v1.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/niklasfasching/go-org v1.6.2
|
||||
github.com/oliamb/cutter v0.2.2
|
||||
github.com/olivere/elastic/v7 v7.0.25
|
||||
github.com/pelletier/go-toml v1.9.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
||||
github.com/olivere/elastic/v7 v7.0.31
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/quasoft/websspi v1.0.0
|
||||
github.com/rs/xid v1.3.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/quasoft/websspi v1.1.2
|
||||
github.com/sergi/go-diff v1.2.0
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/unknwon/com v1.0.1
|
||||
github.com/unknwon/i18n v0.0.0-20210321134014-0ebbf2df1c44
|
||||
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
|
||||
github.com/unrolled/render v1.4.0
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/xanzy/go-gitlab v0.50.1
|
||||
github.com/xanzy/go-gitlab v0.58.0
|
||||
github.com/yohcop/openid-go v1.0.0
|
||||
github.com/yuin/goldmark v1.4.0
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
|
||||
github.com/yuin/goldmark-meta v1.0.0
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
github.com/yuin/goldmark v1.4.11
|
||||
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
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.19.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
|
||||
golang.org/x/tools v0.1.0
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
golang.org/x/tools v0.1.9
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/ini.v1 v1.66.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
mvdan.cc/xurls/v2 v2.2.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.9
|
||||
xorm.io/builder v0.3.10
|
||||
xorm.io/xorm v1.2.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||
github.com/cloudflare/cfssl v1.6.1 // indirect
|
||||
github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
|
||||
github.com/couchbase/gomemcached v0.1.2 // indirect
|
||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
||||
github.com/go-openapi/analysis v0.21.2 // indirect
|
||||
github.com/go-openapi/errors v0.20.2 // indirect
|
||||
github.com/go-openapi/runtime v0.21.1 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.1 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mholt/acmez v1.0.2 // indirect
|
||||
github.com/miekg/dns v1.1.46 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/rs/xid v1.3.0 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||
github.com/spf13/afero v1.8.0 // indirect
|
||||
github.com/spf13/cobra v1.3.0 // indirect
|
||||
github.com/spf13/viper v1.10.1 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.mongodb.org/mongo-driver v1.8.2 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||
|
||||
replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce
|
||||
|
||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||
|
||||
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4
|
||||
|
||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
|
||||
|
||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
@ -262,23 +263,26 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
|
||||
return func(t *testing.T) {
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
|
||||
owner, repo, index, ctx.Token)
|
||||
req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
|
||||
MergeMessageField: "doAPIMergePullRequest Merge",
|
||||
Do: string(repo_model.MergeStyleMerge),
|
||||
})
|
||||
|
||||
resp := ctx.Session.MakeRequest(t, req, NoExpectedStatus)
|
||||
var req *http.Request
|
||||
var resp *httptest.ResponseRecorder
|
||||
|
||||
if resp.Code == http.StatusMethodNotAllowed {
|
||||
err := api.APIError{}
|
||||
DecodeJSON(t, resp, &err)
|
||||
assert.EqualValues(t, "Please try again later", err.Message)
|
||||
queue.GetManager().FlushAll(context.Background(), 5*time.Second)
|
||||
for i := 0; i < 6; i++ {
|
||||
req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
|
||||
MergeMessageField: "doAPIMergePullRequest Merge",
|
||||
Do: string(repo_model.MergeStyleMerge),
|
||||
})
|
||||
|
||||
resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
|
||||
|
||||
if resp.Code != http.StatusMethodNotAllowed {
|
||||
break
|
||||
}
|
||||
err := api.APIError{}
|
||||
DecodeJSON(t, resp, &err)
|
||||
assert.EqualValues(t, "Please try again later", err.Message)
|
||||
queue.GetManager().FlushAll(context.Background(), 5*time.Second)
|
||||
<-time.After(1 * time.Second)
|
||||
}
|
||||
|
||||
expected := ctx.ExpectedCode
|
||||
|
@ -47,7 +47,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
results, err := private.ServCommand(ctx, 1, "user2", "repo1", perm.AccessModeWrite, "git-upload-pack", "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, results.IsWiki)
|
||||
assert.False(t, results.IsDeployKey)
|
||||
assert.Zero(t, results.DeployKeyID)
|
||||
assert.Equal(t, int64(1), results.KeyID)
|
||||
assert.Equal(t, "user2@localhost", results.KeyName)
|
||||
assert.Equal(t, "user2", results.UserName)
|
||||
@ -70,7 +70,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
results, err = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, results.IsWiki)
|
||||
assert.False(t, results.IsDeployKey)
|
||||
assert.Zero(t, results.DeployKeyID)
|
||||
assert.Equal(t, int64(1), results.KeyID)
|
||||
assert.Equal(t, "user2@localhost", results.KeyName)
|
||||
assert.Equal(t, "user2", results.UserName)
|
||||
@ -92,7 +92,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, results.IsWiki)
|
||||
assert.True(t, results.IsDeployKey)
|
||||
assert.NotZero(t, results.DeployKeyID)
|
||||
assert.Equal(t, deployKey.KeyID, results.KeyID)
|
||||
assert.Equal(t, "test-deploy", results.KeyName)
|
||||
assert.Equal(t, "user15", results.UserName)
|
||||
@ -129,7 +129,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, results.IsWiki)
|
||||
assert.True(t, results.IsDeployKey)
|
||||
assert.NotZero(t, results.DeployKeyID)
|
||||
assert.Equal(t, deployKey.KeyID, results.KeyID)
|
||||
assert.Equal(t, "test-deploy", results.KeyName)
|
||||
assert.Equal(t, "user15", results.UserName)
|
||||
@ -142,7 +142,7 @@ func TestAPIPrivateServ(t *testing.T) {
|
||||
results, err = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeWrite, "git-upload-pack", "")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, results.IsWiki)
|
||||
assert.True(t, results.IsDeployKey)
|
||||
assert.NotZero(t, results.DeployKeyID)
|
||||
assert.Equal(t, deployKey.KeyID, results.KeyID)
|
||||
assert.Equal(t, "test-deploy", results.KeyName)
|
||||
assert.Equal(t, "user15", results.UserName)
|
||||
|
@ -445,7 +445,6 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||
expectedStatus int
|
||||
}{
|
||||
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
|
||||
|
||||
// Transfer to a user with teams in another org should fail
|
||||
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
|
||||
// Transfer to a user with non-existent team IDs should fail
|
||||
|
@ -69,6 +69,12 @@ func TestAPIAddEmail(t *testing.T) {
|
||||
Primary: false,
|
||||
},
|
||||
}, emails)
|
||||
|
||||
opts = api.CreateEmailOption{
|
||||
Emails: []string{"notAEmail"},
|
||||
}
|
||||
req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails?token="+token, &opts)
|
||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
func TestAPIDeleteEmail(t *testing.T) {
|
||||
|
@ -123,6 +123,17 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{
|
||||
Filter: "blob:none",
|
||||
}))
|
||||
exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
}
|
||||
}
|
||||
|
||||
func doGitCloneFail(u *url.URL) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "doGitCloneFail")
|
||||
|
@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
|
||||
|
||||
t.Run("Clone", doGitClone(dstPath, u))
|
||||
|
||||
dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
|
||||
assert.NoError(t, err)
|
||||
defer util.RemoveAll(dstPath2)
|
||||
|
||||
t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
|
||||
|
||||
little, big := standardCommitAndPushTest(t, dstPath)
|
||||
littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
|
||||
rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
|
||||
|
@ -112,6 +112,13 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
}
|
||||
|
||||
os.Unsetenv("GIT_AUTHOR_NAME")
|
||||
os.Unsetenv("GIT_AUTHOR_EMAIL")
|
||||
os.Unsetenv("GIT_AUTHOR_DATE")
|
||||
os.Unsetenv("GIT_COMMITTER_NAME")
|
||||
os.Unsetenv("GIT_COMMITTER_EMAIL")
|
||||
os.Unsetenv("GIT_COMMITTER_DATE")
|
||||
|
||||
err := unittest.InitFixtures(
|
||||
unittest.FixturesOptions{
|
||||
Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
|
||||
|
@ -83,7 +83,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mssql/data/sessions
|
||||
|
||||
[log]
|
||||
MODE = test,file
|
||||
ROOT_PATH = mssql-log
|
||||
ROOT_PATH = {{REPO_TEST_DIR}}mssql-log
|
||||
ROUTER = ,
|
||||
XORM = file
|
||||
ENABLE_SSH_LOG = true
|
||||
|
@ -101,7 +101,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions
|
||||
|
||||
[log]
|
||||
MODE = test,file
|
||||
ROOT_PATH = mysql-log
|
||||
ROOT_PATH = {{REPO_TEST_DIR}}mysql-log
|
||||
ROUTER = ,
|
||||
XORM = file
|
||||
ENABLE_SSH_LOG = true
|
||||
|
@ -80,7 +80,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql8/data/sessions
|
||||
|
||||
[log]
|
||||
MODE = test,file
|
||||
ROOT_PATH = mysql8-log
|
||||
ROOT_PATH = {{REPO_TEST_DIR}}mysql8-log
|
||||
ROUTER = ,
|
||||
XORM = file
|
||||
ENABLE_SSH_LOG = true
|
||||
|
@ -84,7 +84,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-pgsql/data/sessions
|
||||
|
||||
[log]
|
||||
MODE = test,file
|
||||
ROOT_PATH = pgsql-log
|
||||
ROOT_PATH = {{REPO_TEST_DIR}}pgsql-log
|
||||
ROUTER = ,
|
||||
XORM = file
|
||||
ENABLE_SSH_LOG = true
|
||||
|
@ -25,6 +25,8 @@ import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unknwon/i18n"
|
||||
@ -65,7 +67,7 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str
|
||||
|
||||
func TestPullMerge(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
hookTasks, err := webhook.HookTasks(1, 1) //Retrieve previous hook number
|
||||
hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number
|
||||
assert.NoError(t, err)
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
@ -87,7 +89,7 @@ func TestPullMerge(t *testing.T) {
|
||||
|
||||
func TestPullRebase(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
hookTasks, err := webhook.HookTasks(1, 1) //Retrieve previous hook number
|
||||
hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number
|
||||
assert.NoError(t, err)
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
@ -109,7 +111,7 @@ func TestPullRebase(t *testing.T) {
|
||||
|
||||
func TestPullRebaseMerge(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
hookTasks, err := webhook.HookTasks(1, 1) //Retrieve previous hook number
|
||||
hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number
|
||||
assert.NoError(t, err)
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
@ -131,7 +133,7 @@ func TestPullRebaseMerge(t *testing.T) {
|
||||
|
||||
func TestPullSquash(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
hookTasks, err := webhook.HookTasks(1, 1) //Retrieve previous hook number
|
||||
hookTasks, err := webhook.HookTasks(1, 1) // Retrieve previous hook number
|
||||
assert.NoError(t, err)
|
||||
hookTasksLenBefore := len(hookTasks)
|
||||
|
||||
@ -335,3 +337,74 @@ func TestCantMergeUnrelated(t *testing.T) {
|
||||
gitRepo.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func TestConflictChecking(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
|
||||
// Create new clean repo to test conflict checking.
|
||||
baseRepo, err := repo_service.CreateRepository(user, user, models.CreateRepoOptions{
|
||||
Name: "conflict-checking",
|
||||
Description: "Tempo repo",
|
||||
AutoInit: true,
|
||||
Readme: "Default",
|
||||
DefaultBranch: "main",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, baseRepo)
|
||||
|
||||
// create a commit on new branch.
|
||||
_, err = files_service.CreateOrUpdateRepoFile(baseRepo, user, &files_service.UpdateRepoFileOptions{
|
||||
TreePath: "important_file",
|
||||
Message: "Add a important file",
|
||||
Content: "Just a non-important file",
|
||||
IsNewFile: true,
|
||||
OldBranch: "main",
|
||||
NewBranch: "important-secrets",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create a commit on main branch.
|
||||
_, err = files_service.CreateOrUpdateRepoFile(baseRepo, user, &files_service.UpdateRepoFileOptions{
|
||||
TreePath: "important_file",
|
||||
Message: "Add a important file",
|
||||
Content: "Not the same content :P",
|
||||
IsNewFile: true,
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create Pull to merge the important-secrets branch into main branch.
|
||||
pullIssue := &models.Issue{
|
||||
RepoID: baseRepo.ID,
|
||||
Title: "PR with conflict!",
|
||||
PosterID: user.ID,
|
||||
Poster: user,
|
||||
IsPull: true,
|
||||
}
|
||||
|
||||
pullRequest := &models.PullRequest{
|
||||
HeadRepoID: baseRepo.ID,
|
||||
BaseRepoID: baseRepo.ID,
|
||||
HeadBranch: "important-secrets",
|
||||
BaseBranch: "main",
|
||||
HeadRepo: baseRepo,
|
||||
BaseRepo: baseRepo,
|
||||
Type: models.PullRequestGitea,
|
||||
}
|
||||
err = pull.NewPullRequest(baseRepo, pullIssue, nil, nil, pullRequest, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{Title: "PR with conflict!"}).(*models.Issue)
|
||||
conflictingPR, err := models.GetPullRequestByIssueID(issue.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Ensure conflictedFiles is populated.
|
||||
assert.Equal(t, 1, len(conflictingPR.ConflictedFiles))
|
||||
// Check if status is correct.
|
||||
assert.Equal(t, models.PullRequestStatusConflict, conflictingPR.Status)
|
||||
// Ensure that mergeable returns false
|
||||
assert.False(t, conflictingPR.Mergeable())
|
||||
})
|
||||
}
|
||||
|
@ -51,8 +51,6 @@ func TestSignin(t *testing.T) {
|
||||
{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||
{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||
{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||
// test for duplicate email
|
||||
{username: "user2@example.com", password: "password", message: i18n.Tr("en", "form.email_been_used")},
|
||||
}
|
||||
|
||||
for _, s := range samples {
|
||||
|
@ -79,7 +79,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-sqlite/data/sessions
|
||||
|
||||
[log]
|
||||
MODE = test,file
|
||||
ROOT_PATH = sqlite-log
|
||||
ROOT_PATH = {{REPO_TEST_DIR}}sqlite-log
|
||||
ROUTER = ,
|
||||
XORM = file
|
||||
ENABLE_SSH_LOG = true
|
||||
|
@ -337,7 +337,7 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
||||
|
||||
actions := make([]*Action, 0, setting.UI.FeedPagingNum)
|
||||
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("`action`.created_unix").Where(cond).Join("INNER", "repository", "`repository`.id = `action`.repo_id").Find(&actions); err != nil {
|
||||
return nil, fmt.Errorf("Find: %v", err)
|
||||
}
|
||||
|
||||
@ -401,7 +401,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
||||
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
||||
}
|
||||
if !opts.IncludePrivate {
|
||||
cond = cond.And(builder.Eq{"is_private": false})
|
||||
cond = cond.And(builder.Eq{"`action`.is_private": false})
|
||||
}
|
||||
if !opts.IncludeDeleted {
|
||||
cond = cond.And(builder.Eq{"is_deleted": false})
|
||||
@ -414,8 +414,8 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
||||
} else {
|
||||
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
||||
|
||||
cond = cond.And(builder.Gte{"created_unix": dateLow.Unix()})
|
||||
cond = cond.And(builder.Lte{"created_unix": dateHigh.Unix()})
|
||||
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
|
||||
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,3 +129,20 @@ func TestNotifyWatchers(t *testing.T) {
|
||||
OpType: action.OpType,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFeedsCorrupted(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
|
||||
unittest.AssertExistsAndLoadBean(t, &Action{
|
||||
ID: 8,
|
||||
RepoID: 1700,
|
||||
})
|
||||
|
||||
actions, err := GetFeeds(GetFeedsOptions{
|
||||
RequestedUser: user,
|
||||
Actor: user,
|
||||
IncludePrivate: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, actions, 0)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ const (
|
||||
)
|
||||
|
||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error)) []*SignCommit {
|
||||
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
|
||||
newCommits := make([]*SignCommit, 0, len(oldCommits))
|
||||
keyMap := map[string]bool{}
|
||||
|
||||
@ -81,7 +81,7 @@ func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustMod
|
||||
Verification: ParseCommitWithSignature(c.Commit),
|
||||
}
|
||||
|
||||
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isCodeReader, &keyMap)
|
||||
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
||||
|
||||
newCommits = append(newCommits, signCommit)
|
||||
}
|
||||
@ -455,7 +455,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use
|
||||
|
||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
|
||||
// There are several trust models in Gitea
|
||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
|
||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
|
||||
if !verification.Verified {
|
||||
return
|
||||
}
|
||||
@ -500,11 +500,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
|
||||
var has bool
|
||||
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
|
||||
if !has {
|
||||
isMember, err = isCodeReader(verification.SigningUser)
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
(*keyMap)[verification.SigningKey.KeyID] = isMember
|
||||
}
|
||||
} else {
|
||||
isMember, err = isCodeReader(verification.SigningUser)
|
||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||
}
|
||||
|
||||
if !isMember {
|
||||
|
@ -58,7 +58,7 @@ func (key *DeployKey) GetContent() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly checks if the key can only be used for read operations
|
||||
// IsReadOnly checks if the key can only be used for read operations, used by template
|
||||
func (key *DeployKey) IsReadOnly() bool {
|
||||
return key.Mode == perm.AccessModeRead
|
||||
}
|
||||
@ -203,12 +203,6 @@ func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateDeployKey updates deploy key information.
|
||||
func UpdateDeployKey(key *DeployKey) error {
|
||||
_, err := db.GetEngine(db.DefaultContext).ID(key.ID).AllCols().Update(key)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListDeployKeysOptions are options for ListDeployKeys
|
||||
type ListDeployKeysOptions struct {
|
||||
db.ListOptions
|
||||
|
@ -43,7 +43,7 @@ type WebAuthnCredential struct {
|
||||
Name string
|
||||
LowerName string `xorm:"unique(s)"`
|
||||
UserID int64 `xorm:"INDEX unique(s)"`
|
||||
CredentialID string `xorm:"INDEX"`
|
||||
CredentialID string `xorm:"INDEX VARCHAR(410)"`
|
||||
PublicKey []byte
|
||||
AttestationType string
|
||||
AAGUID []byte
|
||||
|
@ -18,7 +18,7 @@ func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []
|
||||
user_model.ValidateCommitsWithEmails(commits),
|
||||
repo.GetTrustModel(),
|
||||
func(user *user_model.User) (bool, error) {
|
||||
return IsUserRepoAdmin(repo, user)
|
||||
return IsOwnerMemberCollaborator(repo, user.ID)
|
||||
},
|
||||
),
|
||||
repo,
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -94,7 +95,8 @@ func CountOrphanedIssues() (int64, error) {
|
||||
return db.GetEngine(db.DefaultContext).Table("issue").
|
||||
Join("LEFT", "repository", "issue.repo_id=repository.id").
|
||||
Where(builder.IsNull{"repository.id"}).
|
||||
Count("id")
|
||||
Select("COUNT(`issue`.`id`)").
|
||||
Count()
|
||||
}
|
||||
|
||||
// DeleteOrphanedIssues delete issues without a repo
|
||||
@ -139,8 +141,9 @@ func DeleteOrphanedIssues() error {
|
||||
func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
|
||||
return db.GetEngine(db.DefaultContext).Table("`"+subject+"`").
|
||||
Join("LEFT", "`"+refobject+"`", joinCond).
|
||||
Where(builder.IsNull{"`" + refobject + "`.id"}).
|
||||
Count("id")
|
||||
Where(builder.IsNull{"`" + refobject + "`.`id`"}).
|
||||
Select("COUNT(`" + subject + "`.`id`)").
|
||||
Count()
|
||||
}
|
||||
|
||||
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
|
||||
@ -240,10 +243,29 @@ func FixIssueLabelWithOutsideLabels() (int64, error) {
|
||||
WHERE
|
||||
(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
|
||||
) AS il_too )`)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// CountActionCreatedUnixString count actions where created_unix is an empty string
|
||||
func CountActionCreatedUnixString() (int64, error) {
|
||||
if setting.Database.UseSQLite3 {
|
||||
return db.GetEngine(db.DefaultContext).Where(`created_unix = ""`).Count(new(Action))
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
|
||||
func FixActionCreatedUnixString() (int64, error) {
|
||||
if setting.Database.UseSQLite3 {
|
||||
res, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -33,3 +34,46 @@ func TestDeleteOrphanedObjects(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, countBefore, countAfter)
|
||||
}
|
||||
|
||||
func TestConsistencyUpdateAction(t *testing.T) {
|
||||
if !setting.Database.UseSQLite3 {
|
||||
t.Skip("Test is only for SQLite database.")
|
||||
}
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
id := 8
|
||||
unittest.AssertExistsAndLoadBean(t, &Action{
|
||||
ID: int64(id),
|
||||
})
|
||||
_, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = "" WHERE id = ?`, id)
|
||||
assert.NoError(t, err)
|
||||
actions := make([]*Action, 0, 1)
|
||||
//
|
||||
// XORM returns an error when created_unix is a string
|
||||
//
|
||||
err = db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "type string to a int64: invalid syntax")
|
||||
}
|
||||
//
|
||||
// Get rid of incorrectly set created_unix
|
||||
//
|
||||
count, err := CountActionCreatedUnixString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count)
|
||||
count, err = FixActionCreatedUnixString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count)
|
||||
|
||||
count, err = CountActionCreatedUnixString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
count, err = FixActionCreatedUnixString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
|
||||
//
|
||||
// XORM must be happy now
|
||||
//
|
||||
assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
|
||||
unittest.CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
|
@ -332,7 +332,6 @@ type ErrInvalidCloneAddr struct {
|
||||
IsProtocolInvalid bool
|
||||
IsPermissionDenied bool
|
||||
LocalPath bool
|
||||
NotResolvedIP bool
|
||||
}
|
||||
|
||||
// IsErrInvalidCloneAddr checks if an error is a ErrInvalidCloneAddr.
|
||||
@ -342,9 +341,6 @@ func IsErrInvalidCloneAddr(err error) bool {
|
||||
}
|
||||
|
||||
func (err *ErrInvalidCloneAddr) Error() string {
|
||||
if err.NotResolvedIP {
|
||||
return fmt.Sprintf("migration/cloning from '%s' is not allowed: unknown hostname", err.Host)
|
||||
}
|
||||
if err.IsInvalidPath {
|
||||
return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided path is invalid", err.Host)
|
||||
}
|
||||
|
@ -56,3 +56,11 @@
|
||||
repo_id: 8
|
||||
is_private: false
|
||||
created_unix: 1603011540 # grouped with id:7
|
||||
|
||||
- id: 8
|
||||
user_id: 1
|
||||
op_type: 12 # close issue
|
||||
act_user_id: 1
|
||||
repo_id: 1700 # dangling intentional
|
||||
is_private: false
|
||||
created_unix: 1603011541
|
||||
|
@ -23,8 +23,8 @@ const (
|
||||
EnvPusherName = "GITEA_PUSHER_NAME"
|
||||
EnvPusherEmail = "GITEA_PUSHER_EMAIL"
|
||||
EnvPusherID = "GITEA_PUSHER_ID"
|
||||
EnvKeyID = "GITEA_KEY_ID"
|
||||
EnvIsDeployKey = "GITEA_IS_DEPLOY_KEY"
|
||||
EnvKeyID = "GITEA_KEY_ID" // public key ID
|
||||
EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"
|
||||
EnvPRID = "GITEA_PR_ID"
|
||||
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
||||
EnvAppURL = "GITEA_ROOT_URL"
|
||||
|
@ -1165,7 +1165,8 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) {
|
||||
// IssuesOptions represents options of an issue.
|
||||
type IssuesOptions struct {
|
||||
db.ListOptions
|
||||
RepoIDs []int64 // include all repos if empty
|
||||
RepoID int64 // overwrites RepoCond if not 0
|
||||
RepoCond builder.Cond
|
||||
AssigneeID int64
|
||||
PosterID int64
|
||||
MentionedID int64
|
||||
@ -1238,7 +1239,7 @@ func sortIssuesSession(sess *xorm.Session, sortType string, priorityRepoID int64
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
|
||||
func (opts *IssuesOptions) setupSessionWithLimit(sess *xorm.Session) {
|
||||
if opts.Page >= 0 && opts.PageSize > 0 {
|
||||
var start int
|
||||
if opts.Page == 0 {
|
||||
@ -1248,20 +1249,23 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
|
||||
}
|
||||
sess.Limit(opts.PageSize, start)
|
||||
}
|
||||
opts.setupSessionNoLimit(sess)
|
||||
}
|
||||
|
||||
func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
|
||||
if len(opts.IssueIDs) > 0 {
|
||||
sess.In("issue.id", opts.IssueIDs)
|
||||
}
|
||||
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
applyReposCondition(sess, opts.RepoIDs)
|
||||
if opts.RepoID != 0 {
|
||||
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoID}
|
||||
}
|
||||
if opts.RepoCond != nil {
|
||||
sess.And(opts.RepoCond)
|
||||
}
|
||||
|
||||
switch opts.IsClosed {
|
||||
case util.OptionalBoolTrue:
|
||||
sess.And("issue.is_closed=?", true)
|
||||
case util.OptionalBoolFalse:
|
||||
sess.And("issue.is_closed=?", false)
|
||||
if !opts.IsClosed.IsNone() {
|
||||
sess.And("issue.is_closed=?", opts.IsClosed.IsTrue())
|
||||
}
|
||||
|
||||
if opts.AssigneeID > 0 {
|
||||
@ -1342,9 +1346,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
|
||||
}
|
||||
|
||||
if opts.User != nil {
|
||||
sess.And(
|
||||
issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()),
|
||||
)
|
||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1380,10 +1382,6 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *Organizati
|
||||
return cond
|
||||
}
|
||||
|
||||
func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session {
|
||||
return sess.In("issue.repo_id", repoIDs)
|
||||
}
|
||||
|
||||
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session {
|
||||
return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||
And("issue_assignees.assignee_id = ?", assigneeID)
|
||||
@ -1413,8 +1411,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
|
||||
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
|
||||
opts.setupSession(sess)
|
||||
opts.setupSessionNoLimit(sess)
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
@ -1424,7 +1421,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
|
||||
Select("issue.repo_id AS repo_id, COUNT(*) AS count").
|
||||
Table("issue").
|
||||
Find(&countsSlice); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to CountIssuesByRepo: %w", err)
|
||||
}
|
||||
|
||||
countMap := make(map[int64]int64, len(countsSlice))
|
||||
@ -1440,15 +1437,14 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
|
||||
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
|
||||
opts.setupSession(sess)
|
||||
opts.setupSessionNoLimit(sess)
|
||||
|
||||
accessCond := accessibleRepositoryCondition(user)
|
||||
if err := sess.Where(accessCond).
|
||||
Distinct("issue.repo_id").
|
||||
Table("issue").
|
||||
Find(&repoIDs); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to GetRepoIDsForIssuesOptions: %w", err)
|
||||
}
|
||||
|
||||
return repoIDs, nil
|
||||
@ -1459,17 +1455,16 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
|
||||
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
opts.setupSession(sess)
|
||||
opts.setupSessionWithLimit(sess)
|
||||
sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID)
|
||||
|
||||
issues := make([]*Issue, 0, opts.ListOptions.PageSize)
|
||||
if err := sess.Find(&issues); err != nil {
|
||||
return nil, fmt.Errorf("Find: %v", err)
|
||||
return nil, fmt.Errorf("unable to query Issues: %w", err)
|
||||
}
|
||||
sess.Close()
|
||||
|
||||
if err := IssueList(issues).LoadAttributes(); err != nil {
|
||||
return nil, fmt.Errorf("LoadAttributes: %v", err)
|
||||
return nil, fmt.Errorf("unable to LoadAttributes for Issues: %w", err)
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
@ -1480,18 +1475,18 @@ func CountIssues(opts *IssuesOptions) (int64, error) {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
|
||||
countsSlice := make([]*struct {
|
||||
RepoID int64
|
||||
Count int64
|
||||
Count int64
|
||||
}, 0, 1)
|
||||
|
||||
sess := e.Select("COUNT(issue.id) AS count").Table("issue")
|
||||
sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
|
||||
opts.setupSession(sess)
|
||||
opts.setupSessionNoLimit(sess)
|
||||
|
||||
if err := sess.Find(&countsSlice); err != nil {
|
||||
return 0, fmt.Errorf("Find: %v", err)
|
||||
return 0, fmt.Errorf("unable to CountIssues: %w", err)
|
||||
}
|
||||
if len(countsSlice) < 1 {
|
||||
return 0, fmt.Errorf("there is less than one result sql record")
|
||||
if len(countsSlice) != 1 {
|
||||
return 0, fmt.Errorf("unable to get one row result when CountIssues, row count=%d", len(countsSlice))
|
||||
}
|
||||
return countsSlice[0].Count, nil
|
||||
}
|
||||
@ -1551,6 +1546,7 @@ const (
|
||||
FilterModeCreate
|
||||
FilterModeMention
|
||||
FilterModeReviewRequested
|
||||
FilterModeYourRepositories
|
||||
)
|
||||
|
||||
func parseCountResult(results []map[string][]byte) int64 {
|
||||
@ -1695,6 +1691,7 @@ type UserIssueStatsOptions struct {
|
||||
IssueIDs []int64
|
||||
IsArchived util.OptionalBool
|
||||
LabelIDs []int64
|
||||
RepoCond builder.Cond
|
||||
Org *Organization
|
||||
Team *Team
|
||||
}
|
||||
@ -1712,6 +1709,9 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
if len(opts.IssueIDs) > 0 {
|
||||
cond = cond.And(builder.In("issue.id", opts.IssueIDs))
|
||||
}
|
||||
if opts.RepoCond != nil {
|
||||
cond = cond.And(opts.RepoCond)
|
||||
}
|
||||
|
||||
if opts.UserID > 0 {
|
||||
cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.UserID, opts.Org, opts.Team, opts.IsPull))
|
||||
@ -1733,7 +1733,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
}
|
||||
|
||||
switch opts.FilterMode {
|
||||
case FilterModeAll:
|
||||
case FilterModeAll, FilterModeYourRepositories:
|
||||
stats.OpenCount, err = sess(cond).
|
||||
And("issue.is_closed = ?", false).
|
||||
Count(new(Issue))
|
||||
|
@ -155,31 +155,26 @@ func toggleUserAssignee(e db.Engine, issue *Issue, assigneeID int64) (removed bo
|
||||
}
|
||||
|
||||
// Check if the submitted user is already assigned, if yes delete him otherwise add him
|
||||
var i int
|
||||
for i = 0; i < len(issue.Assignees); i++ {
|
||||
found := false
|
||||
i := 0
|
||||
for ; i < len(issue.Assignees); i++ {
|
||||
if issue.Assignees[i].ID == assigneeID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assigneeIn := IssueAssignees{AssigneeID: assigneeID, IssueID: issue.ID}
|
||||
|
||||
toBeDeleted := i < len(issue.Assignees)
|
||||
if toBeDeleted {
|
||||
issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i:]...)
|
||||
if found {
|
||||
issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i+1:]...)
|
||||
_, err = e.Delete(assigneeIn)
|
||||
if err != nil {
|
||||
return toBeDeleted, err
|
||||
}
|
||||
} else {
|
||||
issue.Assignees = append(issue.Assignees, assignee)
|
||||
_, err = e.Insert(assigneeIn)
|
||||
if err != nil {
|
||||
return toBeDeleted, err
|
||||
}
|
||||
}
|
||||
|
||||
return toBeDeleted, nil
|
||||
return found, err
|
||||
}
|
||||
|
||||
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs
|
||||
|
@ -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"
|
||||
)
|
||||
@ -101,12 +102,10 @@ func (label *Label) CalOpenIssues() {
|
||||
|
||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
|
||||
func (label *Label) CalOpenOrgIssues(repoID, labelID int64) {
|
||||
repoIDs := []int64{repoID}
|
||||
labelIDs := []int64{labelID}
|
||||
|
||||
counts, _ := CountIssuesByRepo(&IssuesOptions{
|
||||
RepoIDs: repoIDs,
|
||||
LabelIDs: labelIDs,
|
||||
RepoID: repoID,
|
||||
LabelIDs: []int64{labelID},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
})
|
||||
|
||||
for _, count := range counts {
|
||||
|
@ -116,6 +116,11 @@ func getMilestoneByRepoID(e db.Engine, repoID, id int64) (*Milestone, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// HasMilestoneByRepoID returns if the milestone exists in the repository.
|
||||
func HasMilestoneByRepoID(repoID, id int64) (bool, error) {
|
||||
return db.GetEngine(db.DefaultContext).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone))
|
||||
}
|
||||
|
||||
// GetMilestoneByRepoID returns the milestone in a repository.
|
||||
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
|
||||
return getMilestoneByRepoID(db.GetEngine(db.DefaultContext), repoID, id)
|
||||
@ -134,22 +139,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
|
||||
return &mile, nil
|
||||
}
|
||||
|
||||
// GetMilestoneByID returns the milestone via id .
|
||||
func GetMilestoneByID(id int64) (*Milestone, error) {
|
||||
return getMilestoneByID(db.GetEngine(db.DefaultContext), id)
|
||||
}
|
||||
|
||||
func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) {
|
||||
var m Milestone
|
||||
has, err := e.ID(id).Get(&m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// UpdateMilestone updates information of given milestone.
|
||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
|
||||
ctx, committer, err := db.TxContext()
|
||||
@ -267,6 +256,17 @@ func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) err
|
||||
}
|
||||
|
||||
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *Issue, oldMilestoneID int64) error {
|
||||
// Only check if milestone exists if we don't remove it.
|
||||
if issue.MilestoneID > 0 {
|
||||
has, err := HasMilestoneByRepoID(issue.RepoID, issue.MilestoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HasMilestoneByRepoID: %v", err)
|
||||
}
|
||||
if !has {
|
||||
return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist")
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateIssueCols(ctx, issue, "milestone_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestIssue_ReplaceLabels(t *testing.T) {
|
||||
@ -153,7 +154,7 @@ func TestIssues(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IssuesOptions{
|
||||
RepoIDs: []int64{1, 3},
|
||||
RepoCond: builder.In("repo_id", 1, 3),
|
||||
SortType: "oldest",
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
@ -340,7 +341,7 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IssuesOptions{
|
||||
RepoIDs: []int64{1, 2},
|
||||
RepoCond: builder.In("repo_id", 1, 2),
|
||||
},
|
||||
[]int64{1, 2},
|
||||
},
|
||||
|
@ -137,6 +137,7 @@ func QueryIssueContentHistoryEditedCountMap(dbCtx context.Context, issueID int64
|
||||
type IssueContentListItem struct {
|
||||
UserID int64
|
||||
UserName string
|
||||
UserFullName string
|
||||
UserAvatarLink string
|
||||
|
||||
HistoryID int64
|
||||
@ -148,7 +149,7 @@ type IssueContentListItem struct {
|
||||
// FetchIssueContentHistoryList fetch list
|
||||
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
|
||||
res := make([]*IssueContentListItem, 0)
|
||||
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name,"+
|
||||
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name, u.full_name as user_full_name,"+
|
||||
"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
|
||||
Table([]string{"issue_content_history", "h"}).
|
||||
Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
|
||||
|
@ -43,8 +43,9 @@ func TestContentHistory(t *testing.T) {
|
||||
when the refactor of models are done, this test will be possible to be run then with a real `User` model.
|
||||
*/
|
||||
type User struct {
|
||||
ID int64
|
||||
Name string
|
||||
ID int64
|
||||
Name string
|
||||
FullName string
|
||||
}
|
||||
_ = dbEngine.Sync2(&User{})
|
||||
|
||||
|
@ -193,12 +193,13 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
|
||||
// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
|
||||
// even if error occurs, it won't hurt users and won't make things worse
|
||||
for i := range metas {
|
||||
p := lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size}
|
||||
_, err = sess.Insert(&LFSMetaObject{
|
||||
Pointer: lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size},
|
||||
Pointer: p,
|
||||
RepositoryID: repoID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("failed to insert LFS meta object into database, err=%v", err)
|
||||
log.Warn("failed to insert LFS meta object %-v for repo_id: %d into database, err=%v", p, repoID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,10 +52,6 @@ func InsertIssues(issues ...*Issue) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = UpdateRepoStats(ctx, issues[0].RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
@ -147,11 +143,6 @@ func InsertPullRequests(prs ...*PullRequest) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = UpdateRepoStats(ctx, prs[0].Issue.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,9 @@ func TestMigrate_InsertMilestones(t *testing.T) {
|
||||
unittest.CheckConsistencyFor(t, &Milestone{})
|
||||
}
|
||||
|
||||
func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
|
||||
func assertCreateIssues(t *testing.T, isPull bool) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
|
||||
label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
|
||||
@ -63,38 +64,14 @@ func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
|
||||
|
||||
i := unittest.AssertExistsAndLoadBean(t, &Issue{Title: title}).(*Issue)
|
||||
unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
|
||||
|
||||
labelModified := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
|
||||
assert.EqualValues(t, label.NumIssues+1, labelModified.NumIssues)
|
||||
assert.EqualValues(t, label.NumClosedIssues+1, labelModified.NumClosedIssues)
|
||||
|
||||
milestoneModified := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: milestone.ID}).(*Milestone)
|
||||
assert.EqualValues(t, milestone.NumIssues+1, milestoneModified.NumIssues)
|
||||
assert.EqualValues(t, milestone.NumClosedIssues+1, milestoneModified.NumClosedIssues)
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
|
||||
|
||||
assertCreateIssues(t, reponame, false)
|
||||
|
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
|
||||
assert.EqualValues(t, repo.NumIssues+1, repoModified.NumIssues)
|
||||
assert.EqualValues(t, repo.NumClosedIssues+1, repoModified.NumClosedIssues)
|
||||
assertCreateIssues(t, false)
|
||||
}
|
||||
|
||||
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
|
||||
|
||||
assertCreateIssues(t, reponame, true)
|
||||
|
||||
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
|
||||
assert.EqualValues(t, repo.NumPulls+1, repoModified.NumPulls)
|
||||
assert.EqualValues(t, repo.NumClosedPulls+1, repoModified.NumClosedPulls)
|
||||
assertCreateIssues(t, true)
|
||||
}
|
||||
|
||||
func TestMigrate_InsertIssueComments(t *testing.T) {
|
||||
|
@ -0,0 +1,12 @@
|
||||
-
|
||||
id: 1
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 2
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 3
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||
-
|
||||
id: 4
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
@ -0,0 +1,21 @@
|
||||
-
|
||||
id: 1
|
||||
name: "u2fkey-correctly-migrated"
|
||||
user_id: 1
|
||||
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||
counter: 0
|
||||
- id: 2
|
||||
name: "u2fkey-incorrectly-migrated"
|
||||
user_id: 1
|
||||
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||
counter: 0
|
||||
- id: 3
|
||||
name: "u2fkey-deleted"
|
||||
user_id: 1
|
||||
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||
counter: 0
|
||||
- id: 4
|
||||
name: "u2fkey-wrong-user-id"
|
||||
user_id: 2
|
||||
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||
counter: 0
|
@ -0,0 +1,30 @@
|
||||
-
|
||||
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: "u2fkey-incorrectly-migrated"
|
||||
name: "u2fkey-incorrectly-migrated"
|
||||
user_id: 1
|
||||
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
|
||||
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
|
||||
attestation_type: 'fido-u2f'
|
||||
sign_count: 1
|
||||
clone_warning: false
|
||||
-
|
||||
id: 4
|
||||
lower_name: "u2fkey-wrong-user-id"
|
||||
name: "u2fkey-wrong-user-id"
|
||||
user_id: 1
|
||||
credential_id: "THIS SHOULD CHANGE"
|
||||
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
|
||||
attestation_type: 'fido-u2f'
|
||||
sign_count: 1
|
||||
clone_warning: false
|
@ -61,7 +61,6 @@ type Version struct {
|
||||
// update minDBVersion accordingly
|
||||
var migrations = []Migration{
|
||||
// Gitea 1.5.0 ends at v69
|
||||
|
||||
// v70 -> v71
|
||||
NewMigration("add issue_dependencies", addIssueDependencies),
|
||||
// v71 -> v72
|
||||
@ -367,9 +366,13 @@ var migrations = []Migration{
|
||||
// v206 -> v207
|
||||
NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
|
||||
// v207 -> v208
|
||||
NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
|
||||
NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", addWebAuthnCred),
|
||||
// v208 -> v209
|
||||
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
|
||||
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", useBase32HexForCredIDInWebAuthnCredential),
|
||||
// v209 -> v210
|
||||
NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410),
|
||||
// v210 -> v211
|
||||
NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
@ -450,9 +453,12 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
|
||||
|
||||
// Downgrading Gitea's database version not supported
|
||||
if int(v-minDBVersion) > len(migrations) {
|
||||
msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n",
|
||||
v, minDBVersion+len(migrations), minDBVersion+len(migrations))
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gitea, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations))
|
||||
msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)."
|
||||
if !setting.IsProd {
|
||||
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stderr, msg)
|
||||
log.Fatal(msg)
|
||||
return nil
|
||||
}
|
||||
|
@ -5,87 +5,11 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/tstranex/u2f"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addWebAuthnCred(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"`
|
||||
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
|
||||
}
|
||||
|
||||
// Now migrate the old u2f registrations to the new format
|
||||
type u2fRegistration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
Raw []byte
|
||||
Counter uint32 `xorm:"BIGINT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
var start int
|
||||
regs := make([]*u2fRegistration, 0, 50)
|
||||
for {
|
||||
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reg := range regs {
|
||||
parsed := new(u2f.Registration)
|
||||
err = parsed.UnmarshalBinary(reg.Raw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c := &webauthnCredential{
|
||||
ID: reg.ID,
|
||||
Name: reg.Name,
|
||||
LowerName: strings.ToLower(reg.Name),
|
||||
UserID: reg.UserID,
|
||||
CredentialID: base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
|
||||
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
|
||||
AttestationType: "fido-u2f",
|
||||
AAGUID: []byte{},
|
||||
SignCount: reg.Counter,
|
||||
}
|
||||
|
||||
_, err := x.Insert(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(regs) < 50 {
|
||||
break
|
||||
}
|
||||
start += 50
|
||||
regs = regs[:0]
|
||||
}
|
||||
// NO-OP Don't migrate here - let v210 do this.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,47 +5,10 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
|
||||
|
||||
// Create webauthnCredential table
|
||||
type webauthnCredential struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CredentialID string `xorm:"INDEX"`
|
||||
}
|
||||
if err := x.Sync2(&webauthnCredential{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var start int
|
||||
regs := make([]*webauthnCredential, 0, 50)
|
||||
for {
|
||||
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, reg := range regs {
|
||||
credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
|
||||
reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
|
||||
|
||||
_, err := x.Update(reg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(regs) < 50 {
|
||||
break
|
||||
}
|
||||
start += 50
|
||||
regs = regs[:0]
|
||||
}
|
||||
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
|
17
models/migrations/v209.go
Normal file
17
models/migrations/v209.go
Normal file
@ -0,0 +1,17 @@
|
||||
// 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 (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func increaseCredentialIDTo410(x *xorm.Engine) error {
|
||||
// no-op
|
||||
// V208 is badly broken
|
||||
// So now we have to no-op again.
|
||||
|
||||
return nil
|
||||
}
|
184
models/migrations/v210.go
Normal file
184
models/migrations/v210.go
Normal file
@ -0,0 +1,184 @@
|
||||
// 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 (
|
||||
"crypto/elliptic"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/tstranex/u2f"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// v208 migration was completely broken
|
||||
func remigrateU2FCredentials(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)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||
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
|
||||
}
|
||||
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.ORACLE:
|
||||
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.MSSQL:
|
||||
// This column has an index on it. I could write all of the code to attempt to change the index OR
|
||||
// I could just use recreate table.
|
||||
sess := x.NewSession()
|
||||
if err := sess.Begin(); err != nil {
|
||||
_ = sess.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := recreateTable(sess, new(webauthnCredential)); err != nil {
|
||||
_ = sess.Close()
|
||||
return err
|
||||
}
|
||||
if err := sess.Commit(); err != nil {
|
||||
_ = sess.Close()
|
||||
return err
|
||||
}
|
||||
if err := sess.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.POSTGRES:
|
||||
_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
|
||||
// nor is there any need to re-migrate
|
||||
}
|
||||
|
||||
exist, err := x.IsTableExist("u2f_registration")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now migrate the old u2f registrations to the new format
|
||||
type u2fRegistration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
Raw []byte
|
||||
Counter uint32 `xorm:"BIGINT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
var start int
|
||||
regs := make([]*u2fRegistration, 0, 50)
|
||||
for {
|
||||
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
||||
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)
|
||||
}
|
||||
if x.Dialect().URI().DBType == schemas.MSSQL {
|
||||
if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil {
|
||||
return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err)
|
||||
}
|
||||
}
|
||||
for _, reg := range regs {
|
||||
parsed := new(u2f.Registration)
|
||||
err = parsed.UnmarshalBinary(reg.Raw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
remigrated := &webauthnCredential{
|
||||
ID: reg.ID,
|
||||
Name: reg.Name,
|
||||
LowerName: strings.ToLower(reg.Name),
|
||||
UserID: reg.UserID,
|
||||
CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle),
|
||||
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
|
||||
AttestationType: "fido-u2f",
|
||||
AAGUID: []byte{},
|
||||
SignCount: reg.Counter,
|
||||
UpdatedUnix: reg.UpdatedUnix,
|
||||
CreatedUnix: reg.CreatedUnix,
|
||||
}
|
||||
|
||||
has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||
}
|
||||
if !has {
|
||||
has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
|
||||
}
|
||||
if !has {
|
||||
_, err = sess.Insert(remigrated)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(regs) < 50 {
|
||||
break
|
||||
}
|
||||
start += 50
|
||||
regs = regs[:0]
|
||||
}
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
75
models/migrations/v210_test.go
Normal file
75
models/migrations/v210_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2021 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 (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func Test_remigrateU2FCredentials(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)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||
PublicKey []byte
|
||||
AttestationType string
|
||||
SignCount uint32 `xorm:"BIGINT"`
|
||||
CloneWarning bool
|
||||
}
|
||||
|
||||
// Now migrate the old u2f registrations to the new format
|
||||
type U2fRegistration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
Raw []byte
|
||||
Counter uint32 `xorm:"BIGINT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
type ExpectedWebauthnCredential struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.SQLITE {
|
||||
return
|
||||
}
|
||||
|
||||
// Run the migration
|
||||
if err := remigrateU2FCredentials(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 := []ExpectedWebauthnCredential{}
|
||||
if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.EqualValues(t, expected, got)
|
||||
}
|
@ -498,14 +498,15 @@ func (n *Notification) APIURL() string {
|
||||
type NotificationList []*Notification
|
||||
|
||||
// LoadAttributes load Repo Issue User and Comment if not loaded
|
||||
func (nl NotificationList) LoadAttributes() (err error) {
|
||||
func (nl NotificationList) LoadAttributes() error {
|
||||
var err error
|
||||
for i := 0; i < len(nl); i++ {
|
||||
err = nl[i].LoadAttributes()
|
||||
if err != nil && !IsErrCommentNotExist(err) {
|
||||
return
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -49,22 +50,67 @@ func init() {
|
||||
db.RegisterModel(new(TeamUnit))
|
||||
}
|
||||
|
||||
// SearchTeamOptions holds the search options
|
||||
type SearchTeamOptions struct {
|
||||
// SearchOrgTeamOptions holds the search options
|
||||
type SearchOrgTeamOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64
|
||||
Keyword string
|
||||
OrgID int64
|
||||
IncludeDesc bool
|
||||
}
|
||||
|
||||
// GetUserTeamOptions holds the search options.
|
||||
type GetUserTeamOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// SearchMembersOptions holds the search options
|
||||
type SearchMembersOptions struct {
|
||||
db.ListOptions
|
||||
}
|
||||
|
||||
// SearchTeam search for teams. Caller is responsible to check permissions.
|
||||
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
|
||||
// GetUserTeams search for org teams. Caller is responsible to check permissions.
|
||||
func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
|
||||
if opts.Page <= 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
// Default limit
|
||||
opts.PageSize = 10
|
||||
}
|
||||
|
||||
sess := db.GetEngine(db.DefaultContext)
|
||||
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
|
||||
And("team_user.uid=?", opts.UserID)
|
||||
|
||||
count, err := sess.
|
||||
Count(new(Team))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts.PageSize == -1 {
|
||||
opts.PageSize = int(count)
|
||||
} else {
|
||||
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
|
||||
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
|
||||
And("team_user.uid=?", opts.UserID)
|
||||
|
||||
teams := make([]*Team, 0, opts.PageSize)
|
||||
if err = sess.
|
||||
OrderBy("lower_name").
|
||||
Find(&teams); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return teams, count, nil
|
||||
}
|
||||
|
||||
// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
|
||||
func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
|
||||
if opts.Page <= 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
@ -196,7 +242,7 @@ func (t *Team) getRepositories(e db.Engine) error {
|
||||
}
|
||||
|
||||
// GetRepositories returns paginated repositories in team of organization.
|
||||
func (t *Team) GetRepositories(opts *SearchTeamOptions) error {
|
||||
func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
|
||||
if opts.Page == 0 {
|
||||
return t.getRepositories(db.GetEngine(db.DefaultContext))
|
||||
}
|
||||
@ -716,7 +762,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
|
||||
// DeleteTeam deletes given team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func DeleteTeam(t *Team) error {
|
||||
if err := t.GetRepositories(&SearchTeamOptions{}); err != nil {
|
||||
if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -731,8 +777,45 @@ func DeleteTeam(t *Team) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.removeAllRepositories(ctx); err != nil {
|
||||
return err
|
||||
// update branch protections
|
||||
{
|
||||
protections := make([]*ProtectedBranch, 0, 10)
|
||||
err := sess.In("repo_id",
|
||||
builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
|
||||
Find(&protections)
|
||||
if err != nil {
|
||||
return fmt.Errorf("findProtectedBranches: %v", err)
|
||||
}
|
||||
for _, p := range protections {
|
||||
var matched1, matched2, matched3 bool
|
||||
if len(p.WhitelistTeamIDs) != 0 {
|
||||
p.WhitelistTeamIDs, matched1 = util.RemoveIDFromList(
|
||||
p.WhitelistTeamIDs, t.ID)
|
||||
}
|
||||
if len(p.ApprovalsWhitelistTeamIDs) != 0 {
|
||||
p.ApprovalsWhitelistTeamIDs, matched2 = util.RemoveIDFromList(
|
||||
p.ApprovalsWhitelistTeamIDs, t.ID)
|
||||
}
|
||||
if len(p.MergeWhitelistTeamIDs) != 0 {
|
||||
p.MergeWhitelistTeamIDs, matched3 = util.RemoveIDFromList(
|
||||
p.MergeWhitelistTeamIDs, t.ID)
|
||||
}
|
||||
if matched1 || matched2 || matched3 {
|
||||
if _, err = sess.ID(p.ID).Cols(
|
||||
"whitelist_team_i_ds",
|
||||
"merge_whitelist_team_i_ds",
|
||||
"approvals_whitelist_team_i_ds",
|
||||
).Update(p); err != nil {
|
||||
return fmt.Errorf("updateProtectedBranches: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !t.IncludesAllRepositories {
|
||||
if err := t.removeAllRepositories(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Delete team-user.
|
||||
@ -857,11 +940,6 @@ func AddTeamMember(team *Team, userID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get team and its repositories.
|
||||
if err := team.GetRepositories(&SearchTeamOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -883,17 +961,51 @@ func AddTeamMember(team *Team, userID int64) error {
|
||||
team.NumMembers++
|
||||
|
||||
// Give access to team repositories.
|
||||
for _, repo := range team.Repos {
|
||||
if err := recalculateUserAccess(ctx, repo, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if setting.Service.AutoWatchNewRepos {
|
||||
if err = repo_model.WatchRepoCtx(ctx, userID, repo.ID, true); err != nil {
|
||||
return err
|
||||
// update exist access if mode become bigger
|
||||
subQuery := builder.Select("repo_id").From("team_repo").
|
||||
Where(builder.Eq{"team_id": team.ID})
|
||||
|
||||
if _, err := sess.Where("user_id=?", userID).
|
||||
In("repo_id", subQuery).
|
||||
And("mode < ?", team.AccessMode).
|
||||
SetExpr("mode", team.AccessMode).
|
||||
Update(new(Access)); err != nil {
|
||||
return fmt.Errorf("update user accesses: %v", err)
|
||||
}
|
||||
|
||||
// for not exist access
|
||||
var repoIDs []int64
|
||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
|
||||
if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
|
||||
return fmt.Errorf("select id accesses: %v", err)
|
||||
}
|
||||
|
||||
accesses := make([]*Access, 0, 100)
|
||||
for i, repoID := range repoIDs {
|
||||
accesses = append(accesses, &Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
|
||||
if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
|
||||
if err = db.Insert(ctx, accesses); err != nil {
|
||||
return fmt.Errorf("insert new user accesses: %v", err)
|
||||
}
|
||||
accesses = accesses[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// watch could be failed, so run it in a goroutine
|
||||
if setting.Service.AutoWatchNewRepos {
|
||||
// Get team and its repositories.
|
||||
if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
|
||||
log.Error("getRepositories failed: %v", err)
|
||||
}
|
||||
go func(repos []*repo_model.Repository) {
|
||||
for _, repo := range repos {
|
||||
if err = repo_model.WatchRepoCtx(db.DefaultContext, userID, repo.ID, true); err != nil {
|
||||
log.Error("watch repo failed: %v", err)
|
||||
}
|
||||
}
|
||||
}(team.Repos)
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
|
||||
|
||||
test := func(teamID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
|
||||
assert.NoError(t, team.GetRepositories(&SearchTeamOptions{}))
|
||||
assert.NoError(t, team.GetRepositories(&SearchOrgTeamOptions{}))
|
||||
assert.Len(t, team.Repos, team.NumRepos)
|
||||
for _, repo := range team.Repos {
|
||||
unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
|
||||
@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
|
||||
func TestGetUserTeams(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(userID int64) {
|
||||
teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID})
|
||||
teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
|
||||
assert.NoError(t, err)
|
||||
for _, team := range teams {
|
||||
unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
|
||||
|
@ -150,6 +150,17 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
||||
e := db.GetEngine(ctx)
|
||||
oldProjectID := issue.projectID(e)
|
||||
|
||||
// Only check if we add a new project and not remove it.
|
||||
if newProjectID > 0 {
|
||||
newProject, err := GetProjectByID(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 := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -222,22 +222,19 @@ func (pr *PullRequest) loadProtectedBranch(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
// GetDefaultMergeMessage returns default message used when merging pull request
|
||||
func (pr *PullRequest) GetDefaultMergeMessage() string {
|
||||
func (pr *PullRequest) GetDefaultMergeMessage() (string, error) {
|
||||
if pr.HeadRepo == nil {
|
||||
var err error
|
||||
pr.HeadRepo, err = repo_model.GetRepositoryByID(pr.HeadRepoID)
|
||||
if err != nil {
|
||||
log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
|
||||
return ""
|
||||
return "", fmt.Errorf("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
|
||||
}
|
||||
}
|
||||
if err := pr.LoadIssue(); err != nil {
|
||||
log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
return ""
|
||||
return "", fmt.Errorf("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
}
|
||||
if err := pr.LoadBaseRepo(); err != nil {
|
||||
log.Error("LoadBaseRepo: %v", err)
|
||||
return ""
|
||||
return "", fmt.Errorf("LoadBaseRepo: %v", err)
|
||||
}
|
||||
|
||||
issueReference := "#"
|
||||
@ -246,10 +243,10 @@ func (pr *PullRequest) GetDefaultMergeMessage() string {
|
||||
}
|
||||
|
||||
if pr.BaseRepoID == pr.HeadRepoID {
|
||||
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch)
|
||||
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
|
||||
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), nil
|
||||
}
|
||||
|
||||
// ReviewCount represents a count of Reviews
|
||||
@ -335,19 +332,17 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error {
|
||||
}
|
||||
|
||||
// GetDefaultSquashMessage returns default message used when squash and merging pull request
|
||||
func (pr *PullRequest) GetDefaultSquashMessage() string {
|
||||
func (pr *PullRequest) GetDefaultSquashMessage() (string, error) {
|
||||
if err := pr.LoadIssue(); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return ""
|
||||
return "", fmt.Errorf("LoadIssue: %v", err)
|
||||
}
|
||||
if err := pr.LoadBaseRepo(); err != nil {
|
||||
log.Error("LoadBaseRepo: %v", err)
|
||||
return ""
|
||||
return "", fmt.Errorf("LoadBaseRepo: %v", err)
|
||||
}
|
||||
if pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker) {
|
||||
return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index)
|
||||
return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index), nil
|
||||
}
|
||||
return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index)
|
||||
return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index), nil
|
||||
}
|
||||
|
||||
// GetGitRefName returns git ref for hidden pull request branch
|
||||
@ -627,7 +622,7 @@ func (pr *PullRequest) IsWorkInProgress() bool {
|
||||
// HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
|
||||
func HasWorkInProgressPrefix(title string) bool {
|
||||
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
|
||||
if strings.HasPrefix(strings.ToUpper(title), prefix) {
|
||||
if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -648,7 +643,7 @@ func (pr *PullRequest) GetWorkInProgressPrefix() string {
|
||||
}
|
||||
|
||||
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
|
||||
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), prefix) {
|
||||
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), strings.ToUpper(prefix)) {
|
||||
return pr.Issue.Title[0:len(prefix)]
|
||||
}
|
||||
}
|
||||
@ -702,3 +697,14 @@ func (pr *PullRequest) GetHeadBranchHTMLURL() string {
|
||||
}
|
||||
return pr.HeadRepo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch)
|
||||
}
|
||||
|
||||
// Mergeable returns if the pullrequest is mergeable.
|
||||
func (pr *PullRequest) Mergeable() bool {
|
||||
// If a pull request isn't mergable if it's:
|
||||
// - Being conflict checked.
|
||||
// - Has a conflict.
|
||||
// - Received a error while being conflict checked.
|
||||
// - Is a work-in-progress pull request.
|
||||
return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict &&
|
||||
pr.Status != PullRequestStatusError && !pr.IsWorkInProgress()
|
||||
}
|
||||
|
@ -261,11 +261,15 @@ func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
|
||||
|
||||
assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage())
|
||||
msg, err := pr.GetDefaultMergeMessage()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", msg)
|
||||
|
||||
pr.BaseRepoID = 1
|
||||
pr.HeadRepoID = 2
|
||||
assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
|
||||
msg, err = pr.GetDefaultMergeMessage()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", msg)
|
||||
}
|
||||
|
||||
func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
|
||||
@ -283,9 +287,13 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest)
|
||||
|
||||
assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage())
|
||||
msg, err := pr.GetDefaultMergeMessage()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", msg)
|
||||
|
||||
pr.BaseRepoID = 1
|
||||
pr.HeadRepoID = 2
|
||||
assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
|
||||
msg, err = pr.GetDefaultMergeMessage()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", msg)
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitT
|
||||
}
|
||||
|
||||
func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
|
||||
if user.IsAdmin {
|
||||
if user != nil && user.IsAdmin {
|
||||
return true
|
||||
}
|
||||
perm, err := getUserRepoPermission(ctx, repo, user)
|
||||
@ -150,27 +150,56 @@ func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*us
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err = e.
|
||||
userIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("access").
|
||||
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
|
||||
Find(&accesses); err != nil {
|
||||
Select("user_id").
|
||||
Find(&userIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uidMap := map[int64]bool{}
|
||||
i := 0
|
||||
for _, uid := range userIDs {
|
||||
if uidMap[uid] {
|
||||
continue
|
||||
}
|
||||
uidMap[uid] = true
|
||||
userIDs[i] = uid
|
||||
i++
|
||||
}
|
||||
userIDs = userIDs[:i]
|
||||
userIDs = append(userIDs, additionalUserIDs...)
|
||||
|
||||
for _, uid := range additionalUserIDs {
|
||||
if uidMap[uid] {
|
||||
continue
|
||||
}
|
||||
userIDs[i] = uid
|
||||
i++
|
||||
}
|
||||
userIDs = userIDs[:i]
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(accesses)+1)
|
||||
if len(accesses) > 0 {
|
||||
userIDs := make([]int64, len(accesses))
|
||||
for i := 0; i < len(accesses); i++ {
|
||||
userIDs[i] = accesses[i].UserID
|
||||
}
|
||||
|
||||
users := make([]*user_model.User, 0, len(userIDs)+1)
|
||||
if len(userIDs) > 0 {
|
||||
if err = e.In("id", userIDs).Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !repo.Owner.IsOrganization() {
|
||||
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
|
||||
users = append(users, repo.Owner)
|
||||
}
|
||||
|
||||
@ -948,7 +977,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
|
||||
|
||||
// Remove attachment with no issue_id and release_id.
|
||||
for i := range newAttachmentPaths {
|
||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
|
||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachmentPaths[i])
|
||||
}
|
||||
|
||||
if len(repo.Avatar) > 0 {
|
||||
|
@ -6,6 +6,7 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@ -115,6 +116,13 @@ func UpdateMirror(m *Mirror) error {
|
||||
return updateMirror(db.GetEngine(db.DefaultContext), m)
|
||||
}
|
||||
|
||||
// TouchMirror updates the mirror updatedUnix
|
||||
func TouchMirror(ctx context.Context, m *Mirror) error {
|
||||
m.UpdatedUnix = timeutil.TimeStampNow()
|
||||
_, err := db.GetEngine(ctx).ID(m.ID).Cols("updated_unix").Update(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteMirrorByRepoID deletes a mirror by repoID
|
||||
func DeleteMirrorByRepoID(repoID int64) error {
|
||||
_, err := db.GetEngine(db.DefaultContext).Delete(&Mirror{RepoID: repoID})
|
||||
|
@ -233,14 +233,28 @@ func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Typ
|
||||
builder.Select("repo_id").From("team_repo").Where(
|
||||
builder.Eq{
|
||||
"team_id": teamID,
|
||||
}.And(
|
||||
}.And(builder.Or(
|
||||
// Check if the user is member of the team.
|
||||
builder.In(
|
||||
"team_id", builder.Select("team_id").From("team_user").Where(
|
||||
builder.Eq{
|
||||
"uid": userID,
|
||||
},
|
||||
),
|
||||
)).And(
|
||||
),
|
||||
// Check if the user is in the owner team of the organisation.
|
||||
builder.Exists(builder.Select("team_id").From("team_user").
|
||||
Where(builder.Eq{
|
||||
"org_id": orgID,
|
||||
"team_id": builder.Select("id").From("team").Where(
|
||||
builder.Eq{
|
||||
"org_id": orgID,
|
||||
"lower_name": strings.ToLower(ownerTeamName),
|
||||
}),
|
||||
"uid": userID,
|
||||
}),
|
||||
),
|
||||
)).And(
|
||||
builder.In(
|
||||
"team_id", builder.Select("team_id").From("team_unit").Where(
|
||||
builder.Eq{
|
||||
|
@ -167,3 +167,21 @@ func TestLinkedRepository(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoAssignees(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
|
||||
users, err := GetRepoAssignees(repo2)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 1)
|
||||
assert.Equal(t, users[0].ID, int64(2))
|
||||
|
||||
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository)
|
||||
users, err = GetRepoAssignees(repo21)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 3)
|
||||
assert.Equal(t, users[0].ID, int64(15))
|
||||
assert.Equal(t, users[1].ID, int64(18))
|
||||
assert.Equal(t, users[2].ID, int64(16))
|
||||
}
|
||||
|
@ -328,7 +328,12 @@ func AllUnitKeyNames() []string {
|
||||
// MinUnitAccessMode returns the minial permission of the permission map
|
||||
func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
|
||||
res := perm.AccessModeNone
|
||||
for _, mode := range unitsMap {
|
||||
for t, mode := range unitsMap {
|
||||
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
|
||||
if t == TypeExternalTracker || t == TypeExternalWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
// get the minial permission great than AccessModeNone except all are AccessModeNone
|
||||
if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
|
||||
res = mode
|
||||
|
@ -175,8 +175,10 @@ func init() {
|
||||
|
||||
checkForActionConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
action := reflectionWrap(bean)
|
||||
repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")})
|
||||
assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "action: %+v", action)
|
||||
if action.int("RepoID") != 1700 { // dangling intentional
|
||||
repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")})
|
||||
assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "action: %+v", action)
|
||||
}
|
||||
}
|
||||
|
||||
consistencyCheckMap["user"] = checkForUserConsistency
|
||||
|
@ -13,11 +13,13 @@ import (
|
||||
_ "image/jpeg" // Needed for jpeg support
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -82,6 +84,11 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
}
|
||||
// ***** END: Follow *****
|
||||
|
||||
if _, err := db.GetEngine(ctx).In("grant_id", builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": u.ID})).
|
||||
Delete(&auth_model.OAuth2AuthorizationCode{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = deleteBeans(e,
|
||||
&AccessToken{UID: u.ID},
|
||||
&Collaboration{UserID: u.ID},
|
||||
@ -99,6 +106,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
&Collaboration{UserID: u.ID},
|
||||
&Stopwatch{UserID: u.ID},
|
||||
&user_model.Setting{UserID: u.ID},
|
||||
&auth_model.OAuth2Application{UID: u.ID},
|
||||
&auth_model.OAuth2Grant{UserID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
@ -130,6 +139,50 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ***** START: Branch Protections *****
|
||||
{
|
||||
const batchSize = 50
|
||||
for start := 0; ; start += batchSize {
|
||||
protections := make([]*ProtectedBranch, 0, batchSize)
|
||||
// @perf: We can't filter on DB side by u.ID, as those IDs are serialized as JSON strings.
|
||||
// We could filter down with `WHERE repo_id IN (reposWithPushPermission(u))`,
|
||||
// though that query will be quite complex and tricky to maintain (compare `getRepoAssignees()`).
|
||||
// Also, as we didn't update branch protections when removing entries from `access` table,
|
||||
// it's safer to iterate all protected branches.
|
||||
if err = e.Limit(batchSize, start).Find(&protections); err != nil {
|
||||
return fmt.Errorf("findProtectedBranches: %v", err)
|
||||
}
|
||||
if len(protections) == 0 {
|
||||
break
|
||||
}
|
||||
for _, p := range protections {
|
||||
var matched1, matched2, matched3 bool
|
||||
if len(p.WhitelistUserIDs) != 0 {
|
||||
p.WhitelistUserIDs, matched1 = util.RemoveIDFromList(
|
||||
p.WhitelistUserIDs, u.ID)
|
||||
}
|
||||
if len(p.ApprovalsWhitelistUserIDs) != 0 {
|
||||
p.ApprovalsWhitelistUserIDs, matched2 = util.RemoveIDFromList(
|
||||
p.ApprovalsWhitelistUserIDs, u.ID)
|
||||
}
|
||||
if len(p.MergeWhitelistUserIDs) != 0 {
|
||||
p.MergeWhitelistUserIDs, matched3 = util.RemoveIDFromList(
|
||||
p.MergeWhitelistUserIDs, u.ID)
|
||||
}
|
||||
if matched1 || matched2 || matched3 {
|
||||
if _, err = e.ID(p.ID).Cols(
|
||||
"whitelist_user_i_ds",
|
||||
"merge_whitelist_user_i_ds",
|
||||
"approvals_whitelist_user_i_ds",
|
||||
).Update(p); err != nil {
|
||||
return fmt.Errorf("updateProtectedBranches: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ***** END: Branch Protections *****
|
||||
|
||||
// ***** START: PublicKey *****
|
||||
if _, err = e.Delete(&asymkey_model.PublicKey{OwnerID: u.ID}); err != nil {
|
||||
return fmt.Errorf("deletePublicKeys: %v", err)
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -21,10 +22,23 @@ import (
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmailNotActivated e-mail address has not been activated error
|
||||
ErrEmailNotActivated = errors.New("E-mail address has not been activated")
|
||||
)
|
||||
// ErrEmailNotActivated e-mail address has not been activated error
|
||||
var ErrEmailNotActivated = errors.New("e-mail address has not been activated")
|
||||
|
||||
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
|
||||
type ErrEmailCharIsNotSupported struct {
|
||||
Email string
|
||||
}
|
||||
|
||||
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
|
||||
func IsErrEmailCharIsNotSupported(err error) bool {
|
||||
_, ok := err.(ErrEmailCharIsNotSupported)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrEmailCharIsNotSupported) Error() string {
|
||||
return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
|
||||
type ErrEmailInvalid struct {
|
||||
@ -108,12 +122,24 @@ func (email *EmailAddress) BeforeInsert() {
|
||||
}
|
||||
}
|
||||
|
||||
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
// ValidateEmail check if email is a allowed address
|
||||
func ValidateEmail(email string) error {
|
||||
if len(email) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !emailRegexp.MatchString(email) {
|
||||
return ErrEmailCharIsNotSupported{email}
|
||||
}
|
||||
|
||||
if !(email[0] >= 'a' && email[0] <= 'z') &&
|
||||
!(email[0] >= 'A' && email[0] <= 'Z') &&
|
||||
!(email[0] >= '0' && email[0] <= '9') {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
if _, err := mail.ParseAddress(email); err != nil {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
@ -252,3 +252,58 @@ func TestListEmails(t *testing.T) {
|
||||
assert.Len(t, emails, 5)
|
||||
assert.Greater(t, count, int64(len(emails)))
|
||||
}
|
||||
|
||||
func TestEmailAddressValidate(t *testing.T) {
|
||||
kases := map[string]error{
|
||||
"abc@gmail.com": nil,
|
||||
"132@hotmail.com": nil,
|
||||
"1-3-2@test.org": nil,
|
||||
"1.3.2@test.org": nil,
|
||||
"a_123@test.org.cn": nil,
|
||||
`first.last@iana.org`: nil,
|
||||
`first!last@iana.org`: nil,
|
||||
`first#last@iana.org`: nil,
|
||||
`first$last@iana.org`: nil,
|
||||
`first%last@iana.org`: nil,
|
||||
`first&last@iana.org`: nil,
|
||||
`first'last@iana.org`: nil,
|
||||
`first*last@iana.org`: nil,
|
||||
`first+last@iana.org`: nil,
|
||||
`first/last@iana.org`: nil,
|
||||
`first=last@iana.org`: nil,
|
||||
`first?last@iana.org`: nil,
|
||||
`first^last@iana.org`: nil,
|
||||
"first`last@iana.org": nil,
|
||||
`first{last@iana.org`: nil,
|
||||
`first|last@iana.org`: nil,
|
||||
`first}last@iana.org`: nil,
|
||||
`first~last@iana.org`: nil,
|
||||
`first;last@iana.org`: ErrEmailCharIsNotSupported{`first;last@iana.org`},
|
||||
".233@qq.com": ErrEmailInvalid{".233@qq.com"},
|
||||
"!233@qq.com": ErrEmailInvalid{"!233@qq.com"},
|
||||
"#233@qq.com": ErrEmailInvalid{"#233@qq.com"},
|
||||
"$233@qq.com": ErrEmailInvalid{"$233@qq.com"},
|
||||
"%233@qq.com": ErrEmailInvalid{"%233@qq.com"},
|
||||
"&233@qq.com": ErrEmailInvalid{"&233@qq.com"},
|
||||
"'233@qq.com": ErrEmailInvalid{"'233@qq.com"},
|
||||
"*233@qq.com": ErrEmailInvalid{"*233@qq.com"},
|
||||
"+233@qq.com": ErrEmailInvalid{"+233@qq.com"},
|
||||
"/233@qq.com": ErrEmailInvalid{"/233@qq.com"},
|
||||
"=233@qq.com": ErrEmailInvalid{"=233@qq.com"},
|
||||
"?233@qq.com": ErrEmailInvalid{"?233@qq.com"},
|
||||
"^233@qq.com": ErrEmailInvalid{"^233@qq.com"},
|
||||
"`233@qq.com": ErrEmailInvalid{"`233@qq.com"},
|
||||
"{233@qq.com": ErrEmailInvalid{"{233@qq.com"},
|
||||
"|233@qq.com": ErrEmailInvalid{"|233@qq.com"},
|
||||
"}233@qq.com": ErrEmailInvalid{"}233@qq.com"},
|
||||
"~233@qq.com": ErrEmailInvalid{"~233@qq.com"},
|
||||
";233@qq.com": ErrEmailCharIsNotSupported{";233@qq.com"},
|
||||
"Foo <foo@bar.com>": ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
|
||||
string([]byte{0xE2, 0x84, 0xAA}): ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
|
||||
}
|
||||
for kase, err := range kases {
|
||||
t.Run(kase, func(t *testing.T) {
|
||||
assert.EqualValues(t, err, ValidateEmail(kase))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ type ExternalLoginUser struct {
|
||||
LastName string
|
||||
NickName string
|
||||
Description string
|
||||
AvatarURL string
|
||||
AvatarURL string `xorm:"TEXT"`
|
||||
Location string
|
||||
AccessToken string `xorm:"TEXT"`
|
||||
AccessTokenSecret string `xorm:"TEXT"`
|
||||
|
@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
|
||||
for _, user := range users {
|
||||
results[user.ID] = false // Set default to false
|
||||
}
|
||||
tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
|
||||
if err == nil {
|
||||
|
||||
if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
|
||||
for _, token := range tokenMaps {
|
||||
results[token.UID] = true
|
||||
}
|
||||
}
|
||||
|
||||
if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
|
||||
for _, id := range ids {
|
||||
results[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
|
||||
|
||||
userIDs := users.GetUserIDs()
|
||||
tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
|
||||
err := e.
|
||||
In("uid", userIDs).
|
||||
Find(&tokenMaps)
|
||||
if err != nil {
|
||||
if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
|
||||
return nil, fmt.Errorf("find two factor: %v", err)
|
||||
}
|
||||
return tokenMaps, nil
|
||||
}
|
||||
|
||||
func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
|
||||
if len(users) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ids := make([]int64, 0, len(users))
|
||||
if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
|
||||
return nil, fmt.Errorf("find two factor: %v", err)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// GetUsersByIDs returns all resolved users from a list of Ids.
|
||||
func GetUsersByIDs(ids []int64) (UserList, error) {
|
||||
ous := make([]*User, 0, len(ids))
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
// SearchUserOptions contains the options for searching
|
||||
type SearchUserOptions struct {
|
||||
db.ListOptions
|
||||
|
||||
Keyword string
|
||||
Type UserType
|
||||
UID int64
|
||||
@ -33,6 +34,8 @@ type SearchUserOptions struct {
|
||||
IsRestricted util.OptionalBool
|
||||
IsTwoFactorEnabled util.OptionalBool
|
||||
IsProhibitLogin util.OptionalBool
|
||||
|
||||
ExtraParamStrings map[string]string
|
||||
}
|
||||
|
||||
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
|
||||
|
@ -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.
|
||||
@ -622,7 +630,14 @@ func IsUsableUsername(name string) error {
|
||||
|
||||
// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
|
||||
type CreateUserOverwriteOptions struct {
|
||||
Visibility structs.VisibleType
|
||||
KeepEmailPrivate util.OptionalBool
|
||||
Visibility *structs.VisibleType
|
||||
AllowCreateOrganization util.OptionalBool
|
||||
EmailNotificationsPreference *string
|
||||
MaxRepoCreation *int
|
||||
Theme *string
|
||||
IsRestricted util.OptionalBool
|
||||
IsActive util.OptionalBool
|
||||
}
|
||||
|
||||
// CreateUser creates record of a new user.
|
||||
@ -638,10 +653,45 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
|
||||
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
||||
u.MaxRepoCreation = -1
|
||||
u.Theme = setting.UI.DefaultTheme
|
||||
u.IsRestricted = setting.Service.DefaultUserIsRestricted
|
||||
u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm)
|
||||
|
||||
// overwrite defaults if set
|
||||
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
|
||||
u.Visibility = overwriteDefault[0].Visibility
|
||||
overwrite := overwriteDefault[0]
|
||||
if !overwrite.KeepEmailPrivate.IsNone() {
|
||||
u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue()
|
||||
}
|
||||
if overwrite.Visibility != nil {
|
||||
u.Visibility = *overwrite.Visibility
|
||||
}
|
||||
if !overwrite.AllowCreateOrganization.IsNone() {
|
||||
u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue()
|
||||
}
|
||||
if overwrite.EmailNotificationsPreference != nil {
|
||||
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
|
||||
}
|
||||
if overwrite.MaxRepoCreation != nil {
|
||||
u.MaxRepoCreation = *overwrite.MaxRepoCreation
|
||||
}
|
||||
if overwrite.Theme != nil {
|
||||
u.Theme = *overwrite.Theme
|
||||
}
|
||||
if !overwrite.IsRestricted.IsNone() {
|
||||
u.IsRestricted = overwrite.IsRestricted.IsTrue()
|
||||
}
|
||||
if !overwrite.IsActive.IsNone() {
|
||||
u.IsActive = overwrite.IsActive.IsTrue()
|
||||
}
|
||||
}
|
||||
|
||||
// validate data
|
||||
if err := validateUser(u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ValidateEmail(u.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext()
|
||||
@ -652,11 +702,6 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
|
||||
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
// validate data
|
||||
if err := validateUser(u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isExist, err := isUserExist(sess, 0, u.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -827,8 +872,9 @@ func validateUser(u *User) error {
|
||||
return ValidateEmail(u.Email)
|
||||
}
|
||||
|
||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
|
||||
if err := validateUser(u); err != nil {
|
||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
|
||||
err := validateUser(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -860,15 +906,35 @@ func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !u.IsOrganization() { // check if primary email in email_address table
|
||||
primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !primaryEmailExist {
|
||||
if _, err = e.Insert(&EmailAddress{
|
||||
Email: u.Email,
|
||||
UID: u.ID,
|
||||
IsActivated: true,
|
||||
IsPrimary: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := e.ID(u.ID).AllCols().Update(u)
|
||||
if len(cols) == 0 {
|
||||
_, err = e.ID(u.ID).AllCols().Update(u)
|
||||
} else {
|
||||
_, err = e.ID(u.ID).Cols(cols...).Update(u)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateUser updates user's information.
|
||||
func UpdateUser(u *User, emailChanged bool) error {
|
||||
return updateUser(db.DefaultContext, u, emailChanged)
|
||||
func UpdateUser(u *User, emailChanged bool, cols ...string) error {
|
||||
return updateUser(db.DefaultContext, u, emailChanged, cols...)
|
||||
}
|
||||
|
||||
// UpdateUserCols update user according special columns
|
||||
@ -1104,19 +1170,9 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
|
||||
}
|
||||
|
||||
email = strings.ToLower(email)
|
||||
// First try to find the user by primary email
|
||||
user := &User{Email: email}
|
||||
has, err := db.GetEngine(ctx).Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Otherwise, check in alternative list for activated email addresses
|
||||
emailAddress := &EmailAddress{Email: email, IsActivated: true}
|
||||
has, err = db.GetEngine(ctx).Get(emailAddress)
|
||||
emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
|
||||
has, err := db.GetEngine(ctx).Get(emailAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1183,3 +1239,36 @@ 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})))
|
||||
}
|
||||
|
@ -232,7 +232,21 @@ func TestCreateUserInvalidEmail(t *testing.T) {
|
||||
|
||||
err := CreateUser(user)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrEmailInvalid(err))
|
||||
assert.True(t, IsErrEmailCharIsNotSupported(err))
|
||||
}
|
||||
|
||||
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
|
||||
// add new user with user2's email
|
||||
user.Name = "testuser"
|
||||
user.LowerName = strings.ToLower(user.Name)
|
||||
user.ID = 0
|
||||
err := CreateUser(user)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrEmailAlreadyUsed(err))
|
||||
}
|
||||
|
||||
func TestGetUserIDsByNames(t *testing.T) {
|
||||
|
@ -497,14 +497,19 @@ func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) {
|
||||
}
|
||||
|
||||
// GetSystemWebhooks returns all admin system webhooks.
|
||||
func GetSystemWebhooks() ([]*Webhook, error) {
|
||||
return getSystemWebhooks(db.GetEngine(db.DefaultContext))
|
||||
func GetSystemWebhooks(isActive util.OptionalBool) ([]*Webhook, error) {
|
||||
return getSystemWebhooks(db.GetEngine(db.DefaultContext), isActive)
|
||||
}
|
||||
|
||||
func getSystemWebhooks(e db.Engine) ([]*Webhook, error) {
|
||||
func getSystemWebhooks(e db.Engine, isActive util.OptionalBool) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
if isActive.IsNone() {
|
||||
return webhooks, e.
|
||||
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
|
||||
Find(&webhooks)
|
||||
}
|
||||
return webhooks, e.
|
||||
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
|
||||
Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,10 @@ func Auth(serviceName, userName, passwd string) (string, error) {
|
||||
if err = t.Authenticate(0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = t.AcctMgmt(0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// PAM login names might suffer transformations in the PAM stack.
|
||||
// We should take whatever the PAM stack returns for it.
|
||||
|
@ -131,7 +131,26 @@ func RemoveBOMIfPresent(content []byte) []byte {
|
||||
|
||||
// DetectEncoding detect the encoding of content
|
||||
func DetectEncoding(content []byte) (string, error) {
|
||||
if utf8.Valid(content) {
|
||||
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
|
||||
|
||||
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
|
||||
// instead we walk backwards from the end to trim off a the incomplete character
|
||||
toValidate := content
|
||||
end := len(toValidate) - 1
|
||||
|
||||
if end < 0 {
|
||||
// no-op
|
||||
} else if toValidate[end]>>5 == 0b110 {
|
||||
// Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
|
||||
toValidate = toValidate[:end]
|
||||
} else if end > 0 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>4 == 0b1110 {
|
||||
// Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
|
||||
toValidate = toValidate[:end-1]
|
||||
} else if end > 1 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>6 == 0b10 && toValidate[end-2]>>3 == 0b11110 {
|
||||
// Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
|
||||
toValidate = toValidate[:end-2]
|
||||
}
|
||||
if utf8.Valid(toValidate) {
|
||||
log.Debug("Detected encoding: utf-8 (fast)")
|
||||
return "UTF-8", nil
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
package charset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -272,3 +274,145 @@ func stringMustEndWith(t *testing.T, expected, value string) {
|
||||
func bytesMustStartWith(t *testing.T, expected, value []byte) {
|
||||
assert.Equal(t, expected, value[:len(expected)])
|
||||
}
|
||||
|
||||
func TestToUTF8WithFallbackReader(t *testing.T) {
|
||||
resetDefaultCharsetsOrder()
|
||||
|
||||
for testLen := 0; testLen < 2048; testLen++ {
|
||||
pattern := " test { () }\n"
|
||||
input := ""
|
||||
for len(input) < testLen {
|
||||
input += pattern
|
||||
}
|
||||
input = input[:testLen]
|
||||
input += "// Выключаем"
|
||||
rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)))
|
||||
r, _ := io.ReadAll(rd)
|
||||
assert.EqualValuesf(t, input, string(r), "testing string len=%d", testLen)
|
||||
}
|
||||
|
||||
truncatedOneByteExtension := failFastBytes
|
||||
encoding, _ := DetectEncoding(truncatedOneByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
truncatedTwoByteExtension := failFastBytes
|
||||
truncatedTwoByteExtension[len(failFastBytes)-1] = 0x9b
|
||||
truncatedTwoByteExtension[len(failFastBytes)-2] = 0xe2
|
||||
|
||||
encoding, _ = DetectEncoding(truncatedTwoByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
|
||||
truncatedThreeByteExtension := failFastBytes
|
||||
truncatedThreeByteExtension[len(failFastBytes)-1] = 0x92
|
||||
truncatedThreeByteExtension[len(failFastBytes)-2] = 0x9f
|
||||
truncatedThreeByteExtension[len(failFastBytes)-3] = 0xf0
|
||||
|
||||
encoding, _ = DetectEncoding(truncatedThreeByteExtension)
|
||||
assert.Equal(t, "UTF-8", encoding)
|
||||
}
|
||||
|
||||
var failFastBytes = []byte{
|
||||
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x74, 0x6f,
|
||||
0x6f, 0x6c, 0x73, 0x2e, 0x61, 0x6e, 0x74, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x64, 0x65, 0x66, 0x73, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x73, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67,
|
||||
0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f,
|
||||
0x74, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x6c, 0x65, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x2e, 0x42,
|
||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x0a, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x64, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d,
|
||||
0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65,
|
||||
0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
|
||||
0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x2d, 0x64, 0x6f, 0x63, 0x73, 0x22, 0x29,
|
||||
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x64, 0x62,
|
||||
0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x66,
|
||||
0x73, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x22, 0x29, 0x29, 0x0a, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22,
|
||||
0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74,
|
||||
0x65, 0x72, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x68, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
|
||||
0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x29, 0x0a,
|
||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
|
||||
0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x77, 0x65, 0x62, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x6f, 0x70,
|
||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x20,
|
||||
0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f,
|
||||
0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63,
|
||||
0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2d, 0x61, 0x6c, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
|
||||
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
|
||||
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
||||
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a,
|
||||
0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x63, 0x68, 0x2e, 0x71,
|
||||
0x6f, 0x73, 0x2e, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x2d, 0x63,
|
||||
0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x69, 0x6f, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65,
|
||||
0x74, 0x65, 0x72, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x72, 0x79, 0x2d, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x6f, 0x74,
|
||||
0x6c, 0x69, 0x6e, 0x28, 0x22, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x22, 0x29, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
||||
0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
|
||||
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
|
||||
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
|
||||
0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a,
|
||||
0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d,
|
||||
0x74, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a,
|
||||
0x61, 0x72, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x28, 0x4a, 0x61, 0x72, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
0x20, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e,
|
||||
0x73, 0x65, 0x74, 0x28, 0x22, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
0x76, 0x61, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68,
|
||||
0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67,
|
||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
|
||||
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
||||
0x73, 0x28, 0x22, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x2d, 0x50, 0x61, 0x74, 0x68, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62,
|
||||
0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
|
||||
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x3d,
|
||||
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x2b, 0x22, 0x2e, 0x74, 0x6f, 0x52, 0x65, 0x67, 0x65, 0x78, 0x28, 0x29,
|
||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
||||
0x65, 0x20, 0x66, 0x75, 0x6e, 0x20, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x3a, 0x20, 0x53, 0x74,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70,
|
||||
0x61, 0x74, 0x68, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x28, 0x22, 0x20, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x55, 0x52, 0x49, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x55,
|
||||
0x52, 0x4c, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x72, 0x65, 0x70, 0x6c,
|
||||
0x61, 0x63, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x28, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x2c, 0x20, 0x22, 0x2f,
|
||||
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x73,
|
||||
0x6b, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x3c, 0x42, 0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x3e, 0x28, 0x22, 0x62,
|
||||
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4f,
|
||||
0x73, 0x2e, 0x69, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x28, 0x4f, 0x73, 0x2e, 0x46, 0x41, 0x4d, 0x49, 0x4c, 0x59,
|
||||
0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x28, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x28, 0x22, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x22, 0x29, 0x2e, 0x6d, 0x61, 0x70, 0x20, 0x7b, 0x20, 0x69, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20,
|
||||
0x7d, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x61, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
|
||||
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xd0,
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ func EscapeControlBytes(text []byte) (EscapeStatus, []byte) {
|
||||
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
readStart := 0
|
||||
runeCount := 0
|
||||
var n int
|
||||
var writePos int
|
||||
|
||||
@ -74,10 +75,13 @@ readingloop:
|
||||
for err == nil {
|
||||
n, err = text.Read(buf[readStart:])
|
||||
bs := buf[:n+readStart]
|
||||
n = len(bs)
|
||||
i := 0
|
||||
|
||||
for i < len(bs) {
|
||||
r, size := utf8.DecodeRune(bs[i:])
|
||||
runeCount++
|
||||
|
||||
// Now handle the codepoints
|
||||
switch {
|
||||
case r == utf8.RuneError:
|
||||
@ -112,6 +116,8 @@ readingloop:
|
||||
lineHasRTLScript = false
|
||||
lineHasLTRScript = false
|
||||
|
||||
case runeCount == 1 && r == 0xFEFF: // UTF BOM
|
||||
// the first BOM is safe
|
||||
case r == '\r' || r == '\t' || r == ' ':
|
||||
// These are acceptable control characters and space characters
|
||||
case unicode.IsSpace(r):
|
||||
@ -143,7 +149,8 @@ readingloop:
|
||||
return
|
||||
}
|
||||
writePos = i + size
|
||||
case unicode.Is(unicode.C, r):
|
||||
// 65279 == BOM rune.
|
||||
case unicode.Is(unicode.C, r) && r != rune(65279):
|
||||
escaped.Escaped = true
|
||||
escaped.HasControls = true
|
||||
if writePos < i {
|
||||
|
@ -129,6 +129,14 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
|
||||
"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
|
||||
status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
|
||||
},
|
||||
{
|
||||
// UTF-8/16/32 all use the same codepoint for BOM
|
||||
// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
|
||||
name: "UTF BOM",
|
||||
text: "\xef\xbb\xbftest",
|
||||
result: "\xef\xbb\xbftest",
|
||||
status: EscapeStatus{HasLTRScript: true},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEscapeControlString(t *testing.T) {
|
||||
@ -163,10 +171,18 @@ func TestEscapeControlReader(t *testing.T) {
|
||||
// lets add some control characters to the tests
|
||||
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
|
||||
copy(tests, escapeControlTests)
|
||||
|
||||
// if there is a BOM, we should keep the BOM
|
||||
addPrefix := func(prefix, s string) string {
|
||||
if strings.HasPrefix(s, "\xef\xbb\xbf") {
|
||||
return s[:3] + prefix + s[3:]
|
||||
}
|
||||
return prefix + s
|
||||
}
|
||||
for _, test := range escapeControlTests {
|
||||
test.name += " (+Control)"
|
||||
test.text = "\u001E" + test.text
|
||||
test.result = `<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">` + "\u001e" + `</span></span>` + test.result
|
||||
test.text = addPrefix("\u001E", test.text)
|
||||
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
|
||||
test.status.Escaped = true
|
||||
test.status.HasControls = true
|
||||
tests = append(tests, test)
|
||||
@ -174,8 +190,8 @@ func TestEscapeControlReader(t *testing.T) {
|
||||
|
||||
for _, test := range escapeControlTests {
|
||||
test.name += " (+Mark)"
|
||||
test.text = "\u0300" + test.text
|
||||
test.result = `<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">` + "\u0300" + `</span></span>` + test.result
|
||||
test.text = addPrefix("\u0300", test.text)
|
||||
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
|
||||
test.status.Escaped = true
|
||||
test.status.HasMarks = true
|
||||
tests = append(tests, test)
|
||||
@ -200,3 +216,12 @@ func TestEscapeControlReader(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeControlReader_panic(t *testing.T) {
|
||||
bs := make([]byte, 0, 20479)
|
||||
bs = append(bs, 'A')
|
||||
for i := 0; i < 6826; i++ {
|
||||
bs = append(bs, []byte("—")...)
|
||||
}
|
||||
_, _ = EscapeControlBytes(bs)
|
||||
}
|
||||
|
@ -288,6 +288,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
||||
},
|
||||
Org: &APIOrganization{},
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
|
||||
ctx.csrf = Csrfer(csrfOpts, ctx.Context)
|
||||
|
@ -9,9 +9,11 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@ -69,6 +71,16 @@ type Context struct {
|
||||
Org *Organization
|
||||
}
|
||||
|
||||
// Close frees all resources hold by Context
|
||||
func (ctx *Context) Close() error {
|
||||
var err error
|
||||
if ctx.Req != nil && ctx.Req.MultipartForm != nil {
|
||||
err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
}
|
||||
// TODO: close opened repo, and more
|
||||
return err
|
||||
}
|
||||
|
||||
// TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString.
|
||||
// This is useful if the locale message is intended to only produce HTML content.
|
||||
func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
|
||||
@ -179,6 +191,12 @@ func (ctx *Context) RedirectToFirst(location ...string) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
|
||||
// Therefore we should ignore these redirect locations to prevent open redirects
|
||||
if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := url.Parse(loc)
|
||||
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
|
||||
continue
|
||||
@ -264,6 +282,12 @@ func (ctx *Context) ServerError(logMsg string, logErr error) {
|
||||
func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
|
||||
if logErr != nil {
|
||||
log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
|
||||
if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
|
||||
// This is an error within the underlying connection
|
||||
// and further rendering will not work so just return
|
||||
return
|
||||
}
|
||||
|
||||
if !setting.IsProd {
|
||||
ctx.Data["ErrorMsg"] = logErr
|
||||
}
|
||||
@ -291,6 +315,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
|
||||
}
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||
log.Error("Write bytes failed: %v", err)
|
||||
}
|
||||
@ -628,6 +653,8 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
"RunModeIsProd": setting.IsProd,
|
||||
},
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
||||
ctx.PageData = map[string]interface{}{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user