Compare commits
121 Commits
main
...
suyono/pok
Author | SHA1 | Date |
---|---|---|
Suyono | 7e82ddd5f3 | |
John Olheiser | f6cb7860a2 | |
Yarden Shoham | 6068978c42 | |
Yarden Shoham | c320caed97 | |
silverwind | f1c826ed29 | |
zeripath | 3c531d3957 | |
John Olheiser | 1ae2525922 | |
John Olheiser | fd7ebaaa9c | |
John Olheiser | fa33271157 | |
techknowlogick | 4b3e456afa | |
John Olheiser | 63e5db5d7a | |
John Olheiser | e6e2c2f4a4 | |
Jason Song | e902b98cc2 | |
John Olheiser | 6992e72647 | |
KN4CK3R | 1bbf490926 | |
Yarden Shoham | 45bdeac730 | |
Haruo Kinoshita | a32700d0fd | |
John Olheiser | a9400ba7a3 | |
zeripath | 9a6d78eaa8 | |
zeripath | af8151cbb9 | |
zeripath | ee37edc465 | |
wxiaoguang | 29bbfcc118 | |
zeripath | f430050d24 | |
Jimmy Praet | 510c811574 | |
zeripath | f93522ddae | |
zeripath | 10c9f96a1e | |
Jonathan Tran | 7b60d47c3c | |
zeripath | 265d438a6e | |
zeripath | 93e907de41 | |
zeripath | f3034b1fd9 | |
zeripath | d0c74dd2d2 | |
zeripath | 2f91a12143 | |
zeripath | 3ad62127df | |
Lunny Xiao | 37e23c982f | |
zeripath | 421d87933b | |
Lunny Xiao | 426c0ad14c | |
John Olheiser | 41a06d2e82 | |
Yarden Shoham | 885082f7a7 | |
Lunny Xiao | 32999e2511 | |
Lunny Xiao | 16d7596635 | |
isla w | adc0bcaebb | |
Lunny Xiao | 0cca1e079b | |
John Olheiser | 55c6433fac | |
Kyle D | 5b8763476a | |
Jason Song | 09c667eb45 | |
Lunny Xiao | 791f290c26 | |
John Olheiser | 58e642c1d6 | |
Yarden Shoham | 72d1f9e63e | |
Lunny Xiao | 0697075547 | |
Lunny Xiao | f1e07d8c87 | |
Chongyi Zheng | 443fd27a90 | |
Gusted | 75f128ebf8 | |
Lunny Xiao | 53db977e7e | |
Lunny Xiao | 4fdd4fb2c4 | |
Lunny Xiao | 900e158064 | |
Jason Song | e9bc2c77c3 | |
Lunny Xiao | 9b4da56963 | |
zeripath | 5583eaa904 | |
Lauris BH | 2a5e7f8f92 | |
KN4CK3R | d2777444d9 | |
zeripath | 198342efe4 | |
KN4CK3R | f7258aa42b | |
John Olheiser | 9a0a4086e2 | |
Gusted | 145e11bc39 | |
zeripath | 72524adf3f | |
Christian Ullrich | 2d4083f03c | |
zeripath | 56bded9d8d | |
silverwind | e88218f4be | |
silverwind | 4297aced93 | |
zeripath | dd2343d01f | |
KN4CK3R | 9e49270676 | |
zeripath | 194b780cd7 | |
aceArt-GmbH | 1409b348c6 | |
Lunny Xiao | c36a1bc766 | |
Lunny Xiao | 079ef56824 | |
KN4CK3R | b54c064f89 | |
Jason Song | c0ca9c612b | |
zeripath | e39bb2d05a | |
Michael Kriese | ac54331549 | |
zeripath | 35fc9ad984 | |
zeripath | 6e4ba04843 | |
Lunny Xiao | 09794b4259 | |
zeripath | 757b49ec5e | |
6543 | 9819a47717 | |
zeripath | c7770fa502 | |
silverwind | da956b863b | |
zeripath | 888384a631 | |
silverwind | cddceb9dca | |
zeripath | b56d269cf8 | |
KN4CK3R | ff4e292b3f | |
Xinyu Zhou | 9ba4ef93ff | |
techknowlogick | 9bccc60cf5 | |
Xinyu Zhou | 16772ffde3 | |
Xinyu Zhou | c844c4ff88 | |
KN4CK3R | f4ec03a4e5 | |
KN4CK3R | b2369830bb | |
silverwind | ef08998bf6 | |
Jason Song | 7a004ad7eb | |
Gusted | af8b2250c4 | |
Jason Song | 8917af8701 | |
zeripath | 0d25292fbc | |
Jason Song | ac409fcfba | |
Gusted | df512f77b7 | |
silverwind | e4bf9cad1e | |
Jason Song | 169eeee101 | |
wxiaoguang | 3aacc9b4ac | |
wxiaoguang | 87d05d376d | |
Lunny Xiao | b9dcf991b9 | |
Xinyu Zhou | a2a42cd5de | |
Wayne Starr | 805a14cc91 | |
Xinyu Zhou | 69a54545a8 | |
Wayne Starr | e054f80fe0 | |
wxiaoguang | 89d52922d0 | |
zeripath | 3a0d000b94 | |
silverwind | fd4e7447e7 | |
Jason Song | 7a8e34b255 | |
Jason Song | e4a10f8c78 | |
silverwind | 6dba648e5d | |
KN4CK3R | 4d39fd8aae | |
wxiaoguang | 4869f9c3c8 | |
qwerty287 | 79275d9db4 |
|
@ -5,6 +5,6 @@ tmp_dir = ".air"
|
|||
cmd = "make backend"
|
||||
bin = "gitea"
|
||||
include_ext = ["go", "tmpl"]
|
||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
|
||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
|
||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"]
|
||||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||
|
|
|
@ -941,7 +941,8 @@ steps:
|
|||
image: plugins/hugo:latest
|
||||
pull: always
|
||||
commands:
|
||||
- apk add --no-cache make bash curl
|
||||
# https://github.com/drone-plugins/drone-hugo/issues/36
|
||||
- apk upgrade --no-cache libcurl && apk add --no-cache make bash curl
|
||||
- cd docs
|
||||
- make trans-copy clean build
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
golang 1.19
|
311
CHANGELOG.md
311
CHANGELOG.md
|
@ -4,6 +4,317 @@ 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.18.3](https://github.com/go-gitea/gitea/releases/tag/v1.18.3) - 2023-01-23
|
||||
|
||||
* SECURITY
|
||||
* Prevent multiple `To` recipients (#22566) (#22569)
|
||||
* BUGFIXES
|
||||
* Truncate commit summary on repo files table. (#22551) (#22552)
|
||||
* Mute all links in issue timeline (#22534)
|
||||
|
||||
## [1.18.2](https://github.com/go-gitea/gitea/releases/tag/v1.18.2) - 2023-01-19
|
||||
|
||||
* BUGFIXES
|
||||
* When updating by rebase we need to set the environment for head repo (#22535) (#22536)
|
||||
* Fix issue not auto-closing when it includes a reference to a branch (#22514) (#22521)
|
||||
* Fix invalid issue branch reference if not specified in template (#22513) (#22520)
|
||||
* Fix 500 error viewing pull request when fork has pull requests disabled (#22512) (#22515)
|
||||
* Reliable selection of admin user (#22509) (#22511)
|
||||
* Set disable_gravatar/enable_federated_avatar when offline mode is true (#22479) (#22496)
|
||||
* BUILD
|
||||
* cgo cross-compile for freebsd (#22397) (#22519)
|
||||
|
||||
## [1.18.1](https://github.com/go-gitea/gitea/releases/tag/v1.18.1) - 2023-01-17
|
||||
|
||||
* API
|
||||
* Add `sync_on_commit` option for push mirrors api (#22271) (#22292)
|
||||
* BUGFIXES
|
||||
* Update `github.com/zeripath/zapx/v15` (#22485)
|
||||
* Fix pull request API field `closed_at` always being `null` (#22482) (#22483)
|
||||
* Fix container blob mount (#22226) (#22476)
|
||||
* Fix error when calculating repository size (#22392) (#22474)
|
||||
* Fix Operator does not exist bug on explore page with ONLY_SHOW_RELEVANT_REPOS (#22454) (#22472)
|
||||
* Fix environments for KaTeX and error reporting (#22453) (#22473)
|
||||
* Remove the netgo tag for Windows build (#22467) (#22468)
|
||||
* Fix migration from GitBucket (#22477) (#22465)
|
||||
* Prevent panic on looking at api "git" endpoints for empty repos (#22457) (#22458)
|
||||
* Fix PR status layout on mobile (#21547) (#22441)
|
||||
* Fix wechatwork webhook sends empty content in PR review (#21762) (#22440)
|
||||
* Remove duplicate "Actions" label in mobile view (#21974) (#22439)
|
||||
* Fix leaving organization bug on user settings -> orgs (#21983) (#22438)
|
||||
* Fixed colour transparency regex matching in project board sorting (#22092) (#22437)
|
||||
* Correctly handle select on multiple channels in Queues (#22146) (#22428)
|
||||
* Prepend refs/heads/ to issue template refs (#20461) (#22427)
|
||||
* Restore function to "Show more" buttons (#22399) (#22426)
|
||||
* Continue GCing other repos on error in one repo (#22422) (#22425)
|
||||
* Allow HOST has no port (#22280) (#22409)
|
||||
* Fix omit avatar_url in discord payload when empty (#22393) (#22394)
|
||||
* Don't display stop watch top bar icon when disabled and hidden when click other place (#22374) (#22387)
|
||||
* Don't lookup mail server when using sendmail (#22300) (#22383)
|
||||
* Fix gravatar disable bug (#22337)
|
||||
* Fix update settings table on install (#22326) (#22327)
|
||||
* Fix sitemap (#22272) (#22320)
|
||||
* Fix code search title translation (#22285) (#22316)
|
||||
* Fix due date rendering the wrong date in issue (#22302) (#22306)
|
||||
* Fix get system setting bug when enabled redis cache (#22298)
|
||||
* Fix bug of DisableGravatar default value (#22297)
|
||||
* Fix key signature error page (#22229) (#22230)
|
||||
* TESTING
|
||||
* Remove test session cache to reduce possible concurrent problem (#22199) (#22429)
|
||||
* MISC
|
||||
* Restore previous official review when an official review is deleted (#22449) (#22460)
|
||||
* Log STDERR of external renderer when it fails (#22442) (#22444)
|
||||
|
||||
## [1.18.0](https://github.com/go-gitea/gitea/releases/tag/1.18.0) - 2022-12-22
|
||||
|
||||
* SECURITY
|
||||
* Remove ReverseProxy authentication from the API (#22219) (#22251)
|
||||
* Support Go Vulnerability Management (#21139)
|
||||
* Forbid HTML string tooltips (#20935)
|
||||
* BREAKING
|
||||
* Rework mailer settings (#18982)
|
||||
* Remove U2F support (#20141)
|
||||
* Refactor `i18n` to `locale` (#20153)
|
||||
* Enable contenthash in filename for dynamic assets (#20813)
|
||||
* FEATURES
|
||||
* Add color previews in markdown (#21474)
|
||||
* Allow package version sorting (#21453)
|
||||
* Add support for Chocolatey/NuGet v2 API (#21393)
|
||||
* Add API endpoint to get changed files of a PR (#21177)
|
||||
* Add filetree on left of diff view (#21012)
|
||||
* Support Issue forms and PR forms (#20987)
|
||||
* Add support for Vagrant packages (#20930)
|
||||
* Add support for `npm unpublish` (#20688)
|
||||
* Add badge capabilities to users (#20607)
|
||||
* Add issue filter for Author (#20578)
|
||||
* Add KaTeX rendering to Markdown. (#20571)
|
||||
* Add support for Pub packages (#20560)
|
||||
* Support localized README (#20508)
|
||||
* Add support mCaptcha as captcha provider (#20458)
|
||||
* Add team member invite by email (#20307)
|
||||
* Added email notification option to receive all own messages (#20179)
|
||||
* Switch Unicode Escaping to a VSCode-like system (#19990)
|
||||
* Add user/organization code search (#19977)
|
||||
* Only show relevant repositories on explore page (#19361)
|
||||
* User keypairs and HTTP signatures for ActivityPub federation using go-ap (#19133)
|
||||
* Add sitemap support (#18407)
|
||||
* Allow creation of OAuth2 applications for orgs (#18084)
|
||||
* Add system setting table with cache and also add cache supports for user setting (#18058)
|
||||
* Add pages to view watched repos and subscribed issues/PRs (#17156)
|
||||
* Support Proxy protocol (#12527)
|
||||
* Implement sync push mirror on commit (#19411)
|
||||
* API
|
||||
* Allow empty assignees on pull request edit (#22150) (#22214)
|
||||
* Make external issue tracker regexp configurable via API (#21338)
|
||||
* Add name field for org api (#21270)
|
||||
* Show teams with no members if user is admin (#21204)
|
||||
* Add latest commit's SHA to content response (#20398)
|
||||
* Add allow_rebase_update, default_delete_branch_after_merge to repository api response (#20079)
|
||||
* Add new endpoints for push mirrors management (#19841)
|
||||
* ENHANCEMENTS
|
||||
* Add setting to disable the git apply step in test patch (#22130) (#22170)
|
||||
* Multiple improvements for comment edit diff (#21990) (#22007)
|
||||
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21928)
|
||||
* Fix flex layout for repo list icons (#21896) (#21920)
|
||||
* Fix vertical align of committer avatar rendered by email address (#21884) (#21918)
|
||||
* Fix setting HTTP headers after write (#21833) (#21877)
|
||||
* Color and Style enhancements (#21784, #21799) (#21868)
|
||||
* Ignore line anchor links with leading zeroes (#21728) (#21776)
|
||||
* Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
|
||||
* Use CSS color-scheme instead of invert (#21616) (#21623)
|
||||
* Respect user's locale when rendering the date range in the repo activity page (#21410)
|
||||
* Change `commits-table` column width (#21564)
|
||||
* Refactor git command arguments and make all arguments to be safe to be used (#21535)
|
||||
* CSS color enhancements (#21534)
|
||||
* Add link to user profile in markdown mention only if user exists (#21533, #21554)
|
||||
* Add option to skip index dirs (#21501)
|
||||
* Diff file tree tweaks (#21446)
|
||||
* Localize all timestamps (#21440)
|
||||
* Add `code` highlighting in issue titles (#21432)
|
||||
* Use Name instead of DisplayName in LFS Lock (#21415)
|
||||
* Consolidate more CSS colors into variables (#21402)
|
||||
* Redirect to new repository owner (#21398)
|
||||
* Use ISO date format instead of hard-coded English date format for date range in repo activity page (#21396)
|
||||
* Use weighted algorithm for string matching when finding files in repo (#21370)
|
||||
* Show private data in feeds (#21369)
|
||||
* Refactor parseTreeEntries, speed up tree list (#21368)
|
||||
* Add GET and DELETE endpoints for Docker blob uploads (#21367)
|
||||
* Add nicer error handling on template compile errors (#21350)
|
||||
* Add `stat` to `ToCommit` function for speed (#21337)
|
||||
* Support instance-wide OAuth2 applications (#21335)
|
||||
* Record OAuth client type at registration (#21316)
|
||||
* Add new CSS variables --color-accent and --color-small-accent (#21305)
|
||||
* Improve error descriptions for unauthorized_client (#21292)
|
||||
* Case-insensitive "find files in repo" (#21269)
|
||||
* Consolidate more CSS rules, fix inline code on arc-green (#21260)
|
||||
* Log real ip of requests from ssh (#21216)
|
||||
* Save files in local storage as group readable (#21198)
|
||||
* Enable fluid page layout on medium size viewports (#21178)
|
||||
* File header tweaks (#21175)
|
||||
* Added missing headers on user packages page (#21172)
|
||||
* Display image digest for container packages (#21170)
|
||||
* Skip dirty check for team forms (#21154)
|
||||
* Keep path when creating a new branch (#21153)
|
||||
* Remove fomantic image module (#21145)
|
||||
* Make labels clickable in the comments section. (#21137)
|
||||
* Sort branches and tags by date descending (#21136)
|
||||
* Better repo API unit checks (#21130)
|
||||
* Improve commit status icons (#21124)
|
||||
* Limit length of repo description and repo url input fields (#21119)
|
||||
* Show .editorconfig errors in frontend (#21088)
|
||||
* Allow poster to choose reviewers (#21084)
|
||||
* Remove black labels and CSS cleanup (#21003)
|
||||
* Make e-mail sanity check more precise (#20991)
|
||||
* Use native inputs in whitespace dropdown (#20980)
|
||||
* Enhance package date display (#20928)
|
||||
* Display total blob size of a package version (#20927)
|
||||
* Show language name on hover (#20923)
|
||||
* Show instructions for all generic package files (#20917)
|
||||
* Refactor AssertExistsAndLoadBean to use generics (#20797)
|
||||
* Move the official website link at the footer of gitea (#20777)
|
||||
* Add support for full name in reverse proxy auth (#20776)
|
||||
* Remove useless JS operation for relative time tooltips (#20756)
|
||||
* Replace some icons with SVG (#20741)
|
||||
* Change commit status icons to SVG (#20736)
|
||||
* Improve single repo action for issue and pull requests (#20730)
|
||||
* Allow multiple files in generic packages (#20661)
|
||||
* Add option to create new issue from /issues page (#20650)
|
||||
* Background color of private list-items updated (#20630)
|
||||
* Added search input field to issue filter (#20623)
|
||||
* Increase default item listing size `ISSUE_PAGING_NUM` to 20 (#20547)
|
||||
* Modify milestone search keywords to be case insensitive again (#20513)
|
||||
* Show hint to link package to repo when viewing empty repo package list (#20504)
|
||||
* Add Tar ZSTD support (#20493)
|
||||
* Make code review checkboxes clickable (#20481)
|
||||
* Add "X-Gitea-Object-Type" header for GET `/raw/` & `/media/` API (#20438)
|
||||
* Display project in issue list (#20434)
|
||||
* Prepend commit message to template content when opening a new PR (#20429)
|
||||
* Replace fomantic popup module with tippy.js (#20428)
|
||||
* Allow to specify colors for text in markup (#20363)
|
||||
* Allow access to the Public Organization Member lists with minimal permissions (#20330)
|
||||
* Use default values when provided values are empty (#20318)
|
||||
* Vertical align navbar avatar at middle (#20302)
|
||||
* Delete cancel button in repo creation page (#21381)
|
||||
* Include login_name in adminCreateUser response (#20283)
|
||||
* fix: icon margin in user/settings/repos (#20281)
|
||||
* Remove blue text on migrate page (#20273)
|
||||
* Modify milestone search keywords to be case insensitive (#20266)
|
||||
* Move some files into models' sub packages (#20262)
|
||||
* Add tooltip to repo icons in explore page (#20241)
|
||||
* Remove deprecated licenses (#20222)
|
||||
* Webhook for Wiki changes (#20219)
|
||||
* Share HTML template renderers and create a watcher framework (#20218)
|
||||
* Allow enable LDAP source and disable user sync via CLI (#20206)
|
||||
* Adds a checkbox to select all issues/PRs (#20177)
|
||||
* Refactor `i18n` to `locale` (#20153)
|
||||
* Disable status checks in template if none found (#20088)
|
||||
* Allow manager logging to set SQL (#20064)
|
||||
* Add order by for assignee no sort issue (#20053)
|
||||
* Take a stab at porting existing components to Vue3 (#20044)
|
||||
* Add doctor command to write commit-graphs (#20007)
|
||||
* Add support for authentication based on reverse proxy email (#19949)
|
||||
* Enable spellcheck for EasyMDE, use contenteditable mode (#19776)
|
||||
* Allow specifying SECRET_KEY_URI, similar to INTERNAL_TOKEN_URI (#19663)
|
||||
* Rework mailer settings (#18982)
|
||||
* Add option to purge users (#18064)
|
||||
* Add author search input (#21246)
|
||||
* Make rss/atom identifier globally unique (#21550)
|
||||
* BUGFIXES
|
||||
* Auth interface return error when verify failure (#22119) (#22259)
|
||||
* Use complete SHA to create and query commit status (#22244) (#22257)
|
||||
* Update bleve and zapx to fix unaligned atomic (#22031) (#22218)
|
||||
* Prevent panic in doctor command when running default checks (#21791) (#21807)
|
||||
* Load GitRepo in API before deleting issue (#21720) (#21796)
|
||||
* Ignore line anchor links with leading zeroes (#21728) (#21776)
|
||||
* Set last login when activating account (#21731) (#21755)
|
||||
* Fix UI language switching bug (#21597) (#21749)
|
||||
* Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
|
||||
* Allow local package identifiers for PyPI packages (#21690) (#21727)
|
||||
* Deal with markdown template without metadata (#21639) (#21654)
|
||||
* Fix opaque background on mermaid diagrams (#21642) (#21652)
|
||||
* Fix repository adoption on Windows (#21646) (#21650)
|
||||
* Sync git hooks when config file path changed (#21619) (#21626)
|
||||
* Fix 500 on PR files API (#21602) (#21607)
|
||||
* Fix `Timestamp.IsZero` (#21593) (#21603)
|
||||
* Fix viewing user subscriptions (#21482)
|
||||
* Fix mermaid-related bugs (#21431)
|
||||
* Fix branch dropdown shifting on page load (#21428)
|
||||
* Fix default theme-auto selector when nologin (#21346)
|
||||
* Fix and improve incorrect error messages (#21342)
|
||||
* Fix formatted link for PR review notifications to matrix (#21319)
|
||||
* Center-aligning content of WebAuthN page (#21127)
|
||||
* Remove follow from commits by file (#20765)
|
||||
* Fix commit status popup (#20737)
|
||||
* Fix init mail render logic (#20704)
|
||||
* Use correct page size for link header pagination (#20546)
|
||||
* Preserve unix socket file (#20499)
|
||||
* Use tippy.js for context popup (#20393)
|
||||
* Add missing parameter for error in log message (#20144)
|
||||
* Do not allow organisation owners add themselves as collaborator (#20043)
|
||||
* Rework file highlight rendering and fix yaml copy-paste (#19967)
|
||||
* Improve code diff highlight, fix incorrect rendered diff result (#19958)
|
||||
* TESTING
|
||||
* Improve OAuth integration tests (#21390)
|
||||
* Add playwright tests (#20123)
|
||||
* BUILD
|
||||
* Switch to building with go1.19 (#20695)
|
||||
* Update JS dependencies, adjust eslint (#20659)
|
||||
* Add more linters to improve code readability (#19989)
|
||||
|
||||
## [1.17.4](https://github.com/go-gitea/gitea/releases/tag/1.17.4) - 2022-12-21
|
||||
|
||||
* SECURITY
|
||||
* Do not allow Ghost access to limited visible user/org (#21849) (#21875)
|
||||
* Fix package access for admins and inactive users (#21580) (#21592)
|
||||
* ENHANCEMENTS
|
||||
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21927)
|
||||
* Fix vertical align of committer avatar rendered by email address (#21884) (#21919)
|
||||
* Fix setting HTTP headers after write (#21833) (#21874)
|
||||
* Ignore line anchor links with leading zeroes (#21728) (#21777)
|
||||
* Enable Monaco automaticLayout (#21516)
|
||||
* BUGFIXES
|
||||
* Do not list active repositories as unadopted (#22034) (#22167)
|
||||
* Correctly handle moved files in apply patch (#22118) (#22136)
|
||||
* Fix condition for is_internal (#22095) (#22131)
|
||||
* Fix permission check on issue/pull lock (#22114)
|
||||
* Fix sorting admin user list by last login (#22081) (#22106)
|
||||
* Workaround for container registry push/pull errors (#21862) (#22069)
|
||||
* Fix issue/PR numbers (#22037) (#22045)
|
||||
* Handle empty author names (#21902) (#22028)
|
||||
* Fix ListBranches to handle empty case (#21921) (#22025)
|
||||
* Fix enabling partial clones on 1.17 (#21809)
|
||||
* Prevent panic in doctor command when running default checks (#21791) (#21808)
|
||||
* Upgrade golang.org/x/crypto (#21792) (#21794)
|
||||
* Init git module before database migration (#21764) (#21766)
|
||||
* Set last login when activating account (#21731) (#21754)
|
||||
* Add HEAD fix to gitea doctor (#21352) (#21751)
|
||||
* Fix UI language switching bug (#21597) (#21748)
|
||||
* Remove semver compatible flag and change pypi to an array of test cases (#21708) (#21729)
|
||||
* Allow local package identifiers for PyPI packages (#21690) (#21726)
|
||||
* Fix repository adoption on Windows (#21646) (#21651)
|
||||
* Sync git hooks when config file path changed (#21619) (#21625)
|
||||
* Added check for disabled Packages (#21540) (#21614)
|
||||
* Fix `Timestamp.IsZero` (#21593) (#21604)
|
||||
* Fix issues count bug (#21600)
|
||||
* Support binary deploy in npm packages (#21589)
|
||||
* Update milestone counters when issue is deleted (#21459) (#21586)
|
||||
* SessionUser protection against nil pointer dereference (#21581)
|
||||
* Case-insensitive NuGet symbol file GUID (#21409) (#21575)
|
||||
* Suppress `ExternalLoginUserNotExist` error (#21504) (#21572)
|
||||
* Prevent Authorization header for presigned LFS urls (#21531) (#21569)
|
||||
* Update binding to fix bugs (#21560)
|
||||
* Fix generating compare link (#21519) (#21530)
|
||||
* Ignore error when retrieving changed PR review files (#21487) (#21524)
|
||||
* Fix incorrect notification commit url (#21479) (#21483)
|
||||
* Display total commit count in hook message (#21400) (#21481)
|
||||
* Enforce grouped NuGet search results (#21442) (#21480)
|
||||
* Return 404 when user is not found on avatar (#21476) (#21477)
|
||||
* Normalize NuGet package version on upload (#22186) (#22201)
|
||||
* MISC
|
||||
* Check for zero time instant in TimeStamp.IsZero() (#22171) (#22173)
|
||||
* Fix warn in database structs sync (#22111)
|
||||
* Allow for resolution of NPM registry paths that match upstream (#21568) (#21723)
|
||||
|
||||
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
|
||||
|
||||
* SECURITY
|
||||
|
|
|
@ -31,6 +31,7 @@ EXPOSE 2222 3000
|
|||
RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
dumb-init \
|
||||
gettext \
|
||||
git \
|
||||
curl \
|
||||
|
@ -68,6 +69,6 @@ ENV HOME "/var/lib/gitea/git"
|
|||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||
WORKDIR /var/lib/gitea
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD []
|
||||
|
||||
|
|
15
Makefile
15
Makefile
|
@ -358,7 +358,7 @@ watch-frontend: node-check node_modules
|
|||
|
||||
.PHONY: watch-backend
|
||||
watch-backend: go-check
|
||||
$(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||
|
||||
.PHONY: test
|
||||
test: test-frontend test-backend
|
||||
|
@ -733,16 +733,16 @@ $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
|||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: release
|
||||
release: frontend generate release-windows release-linux release-darwin release-copy release-compress vendor release-sources release-docs release-check
|
||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-docs release-check
|
||||
|
||||
$(DIST_DIRS):
|
||||
mkdir -p $(DIST_DIRS)
|
||||
|
||||
.PHONY: release-windows
|
||||
release-windows: | $(DIST_DIRS)
|
||||
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) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||
ifeq (,$(findstring gogit,$(TAGS)))
|
||||
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 .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||
endif
|
||||
ifeq ($(CI),true)
|
||||
cp /build/* $(DIST)/binaries
|
||||
|
@ -762,6 +762,13 @@ ifeq ($(CI),true)
|
|||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-freebsd
|
||||
release-freebsd: | $(DIST_DIRS)
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
|
||||
ifeq ($(CI),true)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-copy
|
||||
release-copy: | $(DIST_DIRS)
|
||||
cd $(DIST); for file in `find . -type f -name "*"`; do cp $${file} ./release/; done;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
const (
|
||||
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||
maxUnicodeVersion = 12
|
||||
maxUnicodeVersion = 14
|
||||
)
|
||||
|
||||
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
||||
|
|
|
@ -413,9 +413,9 @@ var (
|
|||
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "addr",
|
||||
Name: "host",
|
||||
Value: "",
|
||||
Usage: "SMTP Addr",
|
||||
Usage: "SMTP Host",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port",
|
||||
|
@ -955,8 +955,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
|||
}
|
||||
conf.Auth = c.String("auth-type")
|
||||
}
|
||||
if c.IsSet("addr") {
|
||||
conf.Addr = c.String("addr")
|
||||
if c.IsSet("host") {
|
||||
conf.Host = c.String("host")
|
||||
}
|
||||
if c.IsSet("port") {
|
||||
conf.Port = c.Int("port")
|
||||
|
|
|
@ -996,6 +996,9 @@ ROUTER = console
|
|||
;;
|
||||
;; Add co-authored-by and co-committed-by trailers if committer does not match author
|
||||
;ADD_CO_COMMITTER_TRAILERS = true
|
||||
;;
|
||||
;; In addition to testing patches using the three-way merge method, re-test conflicting patches with git apply
|
||||
;TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = true
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1550,7 +1553,7 @@ ROUTER = console
|
|||
;; Prefix displayed before subject in mail
|
||||
;SUBJECT_PREFIX =
|
||||
;;
|
||||
;; Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy".
|
||||
;; Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy".
|
||||
;; - sendmail: use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems.
|
||||
;; - dummy: send email messages to the log as a testing phase.
|
||||
;; If your provider does not explicitly say which protocol it uses but does provide a port,
|
||||
|
|
|
@ -101,6 +101,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
|||
- `DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY`: **true**: In default merge messages only include approvers who are officially allowed to review.
|
||||
- `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: In default squash-merge messages include the commit message of all commits comprising the pull request.
|
||||
- `ADD_CO_COMMITTER_TRAILERS`: **true**: Add co-authored-by and co-committed-by trailers to merge commit messages if committer does not match author.
|
||||
- `TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY`: **true**: PR patches are tested using a three-way merge method to discover if there are conflicts. If this setting is set to **true**, conflicting patches will be retested using `git apply` - This was the previous behaviour in 1.18 (and earlier) but is somewhat inefficient. Please report if you find that this setting is required.
|
||||
|
||||
### Repository - Issue (`repository.issue`)
|
||||
|
||||
|
@ -672,7 +673,7 @@ and
|
|||
[Gitea 1.17 configuration document](https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md)
|
||||
|
||||
- `ENABLED`: **false**: Enable to use a mail service.
|
||||
- `PROTOCOL`: **\<empty\>**: Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._
|
||||
- `PROTOCOL`: **\<empty\>**: Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._
|
||||
- SMTP family, if your provider does not explicitly say which protocol it uses but does provide a port, you can set SMTP_PORT instead and this will be inferred.
|
||||
- **sendmail** Use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems.
|
||||
- **dummy** Send email messages to the log as a testing phase.
|
||||
|
@ -734,9 +735,9 @@ and
|
|||
|
||||
- `GRAVATAR_SOURCE`: **gravatar**: Can be `gravatar`, `duoshuo` or anything like
|
||||
`http://cn.gravatar.com/avatar/`.
|
||||
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only.
|
||||
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure.
|
||||
- `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see
|
||||
[http://www.libravatar.org](http://www.libravatar.org)).
|
||||
[http://www.libravatar.org](http://www.libravatar.org)). **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure.
|
||||
|
||||
- `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`.
|
||||
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.
|
||||
|
|
32
go.mod
32
go.mod
|
@ -15,8 +15,8 @@ require (
|
|||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/alecthomas/chroma/v2 v2.3.0
|
||||
github.com/blevesearch/bleve/v2 v2.3.4
|
||||
github.com/alecthomas/chroma/v2 v2.4.0
|
||||
github.com/blevesearch/bleve/v2 v2.3.5
|
||||
github.com/buildkite/terminal-to-html/v3 v3.7.0
|
||||
github.com/caddyserver/certmagic v0.17.2
|
||||
github.com/chi-middleware/proxy v1.1.1
|
||||
|
@ -94,11 +94,11 @@ require (
|
|||
github.com/yuin/goldmark-meta v1.1.0
|
||||
go.jolheiser.com/hcaptcha v0.0.4
|
||||
go.jolheiser.com/pwn v0.0.3
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/text v0.3.8
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/tools v0.1.12
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
@ -129,21 +129,21 @@ require (
|
|||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.3.3 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.3 // indirect
|
||||
github.com/blevesearch/geo v0.1.14 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.4 // indirect
|
||||
github.com/blevesearch/geo v0.1.15 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.2 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 // indirect
|
||||
github.com/blevesearch/segment v0.9.0 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
|
||||
github.com/blevesearch/vellum v1.0.8 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.5 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.5 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.5 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.5 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.5 // indirect
|
||||
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.6 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
|
@ -302,6 +302,8 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
|||
|
||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
|
||||
|
||||
replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2
|
||||
|
||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||
|
||||
exclude github.com/gofrs/uuid v4.0.0+incompatible
|
||||
|
|
69
go.sum
69
go.sum
|
@ -149,7 +149,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
|||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
|
||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/RoaringBitmap/roaring v1.2.1 h1:58/LJlg/81wfEHd5L9qsHduznOIhyv4qb1yWcSvVq9A=
|
||||
github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
|
@ -160,9 +159,10 @@ github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ
|
|||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
||||
github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
|
||||
github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ=
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||
|
@ -225,52 +225,47 @@ github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
|
|||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM=
|
||||
github.com/blevesearch/bleve/v2 v2.3.4 h1:SSb7/cwGzo85LWX1jchIsXM8ZiNNMX3shT5lROM63ew=
|
||||
github.com/blevesearch/bleve/v2 v2.3.4/go.mod h1:Ot0zYum8XQRfPcwhae8bZmNyYubynsoMjVvl1jPqL30=
|
||||
github.com/blevesearch/bleve/v2 v2.3.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8=
|
||||
github.com/blevesearch/bleve/v2 v2.3.5/go.mod h1:FneKGHMRrCLrp4X9+iy3wlBqgM2ALucg7bp8jUuAi/s=
|
||||
github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
|
||||
github.com/blevesearch/bleve_index_api v1.0.3 h1:DDSWaPXOZZJ2BB73ZTWjKxydAugjwywcqU+91AAqcAg=
|
||||
github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
|
||||
github.com/blevesearch/geo v0.1.13/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
|
||||
github.com/blevesearch/geo v0.1.14 h1:TTDpJN6l9ck/cUYbXSn4aCElNls0Whe44rcQKsB7EfU=
|
||||
github.com/blevesearch/geo v0.1.14/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
|
||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
|
||||
github.com/blevesearch/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU=
|
||||
github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4=
|
||||
github.com/blevesearch/geo v0.1.15/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.2 h1:TAte9VZLWda5WAVlZTTZ+GCzEHqGJb4iB2aiZSA6Iv8=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.2/go.mod h1:rvoQXZGq8drq7vXbNeyiRzdEOwZkjkiYGf1822i6CRA=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3/go.mod h1:eZrfp1y+lUh+DzFjUcTBUSnKGuunyFIpBIvqYVzJfvc=
|
||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
||||
github.com/blevesearch/vellum v1.0.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo=
|
||||
github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRvNBlNMrEVgY=
|
||||
github.com/blevesearch/vellum v1.0.8 h1:iMGh4lfxza4BnWO/UJTMPlI3HsK9YawjPv+TteVa9ck=
|
||||
github.com/blevesearch/vellum v1.0.8/go.mod h1:+cpRi/tqq49xUYSQN2P7A5zNSNrS+MscLeeaZ3J46UA=
|
||||
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM=
|
||||
github.com/blevesearch/zapx/v11 v11.3.5 h1:eBQWQ7huA+mzm0sAGnZDwgGGli7S45EO+N+ObFWssbI=
|
||||
github.com/blevesearch/zapx/v11 v11.3.5/go.mod h1:5UdIa/HRMdeRCiLQOyFESsnqBGiip7vQmYReA9toevU=
|
||||
github.com/blevesearch/zapx/v11 v11.3.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc=
|
||||
github.com/blevesearch/zapx/v11 v11.3.6/go.mod h1:B0CzJRj/pS7hJIroflRtFsa9mRHpMSucSgre0FVINns=
|
||||
github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A=
|
||||
github.com/blevesearch/zapx/v12 v12.3.5 h1:5pX2hU+R1aZihT7ac1dNWh1n4wqkIM9pZzWp0ANED9s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.5/go.mod h1:ANcthYRZQycpbRut/6ArF5gP5HxQyJqiFcuJCBju/ss=
|
||||
github.com/blevesearch/zapx/v12 v12.3.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4=
|
||||
github.com/blevesearch/zapx/v12 v12.3.6/go.mod h1:iYi7tIKpauwU5os5wTxJITixr5Km21Hl365otMwdaP0=
|
||||
github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ=
|
||||
github.com/blevesearch/zapx/v13 v13.3.5 h1:eJ3gbD+Nu8p36/O6lhfdvWQ4pxsGYSuTOBrLLPVWJ74=
|
||||
github.com/blevesearch/zapx/v13 v13.3.5/go.mod h1:FV+dRnScFgKnRDIp08RQL4JhVXt1x2HE3AOzqYa6fjo=
|
||||
github.com/blevesearch/zapx/v13 v13.3.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig=
|
||||
github.com/blevesearch/zapx/v13 v13.3.6/go.mod h1:X+FsTwCU8qOHtK0d/ArvbOH7qiIgViSQ1GQvcR6LSkI=
|
||||
github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g=
|
||||
github.com/blevesearch/zapx/v14 v14.3.5 h1:hEvVjZaagFCvOUJrlFQ6/Z6Jjy0opM3g7TMEo58TwP4=
|
||||
github.com/blevesearch/zapx/v14 v14.3.5/go.mod h1:954A/eKFb+pg/ncIYWLWCKY+mIjReM9FGTGIO2Wu1cU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE=
|
||||
github.com/blevesearch/zapx/v14 v14.3.6/go.mod h1:9X8W3XoikagU0rwcTqwZho7p9cC7m7zhPZO94S4wUvM=
|
||||
github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A=
|
||||
github.com/blevesearch/zapx/v15 v15.3.5 h1:NVD0qq8vRk66ImJn1KloXT5ckqPDUZT7VbVJs9jKlac=
|
||||
github.com/blevesearch/zapx/v15 v15.3.5/go.mod h1:QMUh2hXCaYIWFKPYGavq/Iga2zbHWZ9DZAa9uFbWyvg=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
|
@ -361,7 +356,6 @@ github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFl
|
|||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 h1:4KDlx3vjalrHD/EfsjCpV91HNX3JPaIqRtt83zZ7x+Y=
|
||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
|
@ -830,6 +824,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
|
@ -1487,6 +1482,8 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.m
|
|||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2 h1:IRB+69BV7fTT5ccw35ca7TCBe2b7dm5Q5y5tUMQmCvU=
|
||||
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
|
@ -1608,8 +1605,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0
|
|||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -1721,8 +1718,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1876,13 +1873,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1892,8 +1889,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -272,7 +272,7 @@ func (a *Action) GetRefLink() string {
|
|||
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
|
||||
case strings.HasPrefix(a.RefName, git.TagPrefix):
|
||||
return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix))
|
||||
case len(a.RefName) == 40 && git.IsValidSHAPattern(a.RefName):
|
||||
case len(a.RefName) == git.SHAFullLength && git.IsValidSHAPattern(a.RefName):
|
||||
return a.GetRepoLink() + "/src/commit/" + a.RefName
|
||||
default:
|
||||
// FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here.
|
||||
|
|
|
@ -68,8 +68,16 @@ func (key *GPGKey) PaddedKeyID() string {
|
|||
if len(key.KeyID) > 15 {
|
||||
return key.KeyID
|
||||
}
|
||||
return PaddedKeyID(key.KeyID)
|
||||
}
|
||||
|
||||
// PaddedKeyID show KeyID padded to 16 characters
|
||||
func PaddedKeyID(keyID string) string {
|
||||
if len(keyID) > 15 {
|
||||
return keyID
|
||||
}
|
||||
zeros := "0000000000000000"
|
||||
return zeros[0:16-len(key.KeyID)] + key.KeyID
|
||||
return zeros[0:16-len(keyID)] + keyID
|
||||
}
|
||||
|
||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||
|
|
|
@ -20,8 +20,12 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
const DefaultAvatarPixelSize = 28
|
||||
const (
|
||||
// DefaultAvatarClass is the default class of a rendered avatar
|
||||
DefaultAvatarClass = "ui avatar vm"
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
DefaultAvatarPixelSize = 28
|
||||
)
|
||||
|
||||
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
|
||||
type EmailHash struct {
|
||||
|
@ -150,10 +154,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
|||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
|
||||
enableFederatedAvatar := system_model.GetSettingBool(system_model.KeyPictureEnableFederatedAvatar)
|
||||
|
||||
var err error
|
||||
if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
|
||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||
emailHash := saveEmailHash(email)
|
||||
if final {
|
||||
// for final link, we can spend more time on slow external query
|
||||
|
@ -171,8 +175,8 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
|||
return urlStr
|
||||
}
|
||||
|
||||
disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
||||
if disableGravatar != nil && !disableGravatar.GetValueBool() {
|
||||
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||
if !disableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
avatarURLCopy := *system_model.GravatarSourceURL
|
||||
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// ResourceIndex represents a resource index which could be used as issue/release and others
|
||||
|
@ -24,11 +27,6 @@ var (
|
|||
ErrGetResourceIndexFailed = errors.New("get resource index failed")
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxDupIndexAttempts max retry times to create index
|
||||
MaxDupIndexAttempts = 3
|
||||
)
|
||||
|
||||
// SyncMaxResourceIndex sync the max index with the resource
|
||||
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
|
||||
e := GetEngine(ctx)
|
||||
|
@ -61,8 +59,25 @@ func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxInd
|
|||
return nil
|
||||
}
|
||||
|
||||
func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||
res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
|
||||
"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1 RETURNING max_index",
|
||||
tableName, tableName), groupID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return 0, ErrGetResourceIndexFailed
|
||||
}
|
||||
return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
|
||||
}
|
||||
|
||||
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
|
||||
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||
if setting.Database.UsePostgreSQL {
|
||||
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||
}
|
||||
|
||||
e := GetEngine(ctx)
|
||||
|
||||
// try to update the max_index to next value, and acquire the write-lock for the record
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 0
|
||||
size: 6708
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ package git
|
|||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -49,79 +51,67 @@ func init() {
|
|||
db.RegisterModel(new(CommitStatusIndex))
|
||||
}
|
||||
|
||||
// upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
|
||||
func upsertCommitStatusIndex(ctx context.Context, repoID int64, sha string) (err error) {
|
||||
// An atomic UPSERT operation (INSERT/UPDATE) is the only operation
|
||||
// that ensures that the key is actually locked.
|
||||
switch {
|
||||
case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
|
||||
_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
|
||||
"VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1",
|
||||
repoID, sha)
|
||||
case setting.Database.UseMySQL:
|
||||
_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
|
||||
"VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
|
||||
repoID, sha)
|
||||
case setting.Database.UseMSSQL:
|
||||
// https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
|
||||
_, err = db.Exec(ctx, "MERGE `commit_status_index` WITH (HOLDLOCK) as target "+
|
||||
"USING (SELECT ? AS repo_id, ? AS sha) AS src "+
|
||||
"ON src.repo_id = target.repo_id AND src.sha = target.sha "+
|
||||
"WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
|
||||
"WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) "+
|
||||
"VALUES (src.repo_id, src.sha, 1);",
|
||||
repoID, sha)
|
||||
default:
|
||||
return fmt.Errorf("database type not supported")
|
||||
func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
res, err := db.GetEngine(ctx).Query("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
|
||||
"VALUES (?,?,1) ON CONFLICT (repo_id, sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1 RETURNING max_index",
|
||||
repoID, sha)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return err
|
||||
if len(res) == 0 {
|
||||
return 0, db.ErrGetResourceIndexFailed
|
||||
}
|
||||
return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
|
||||
}
|
||||
|
||||
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||
func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
|
||||
for i := 0; i < db.MaxDupIndexAttempts; i++ {
|
||||
idx, err := getNextCommitStatusIndex(repoID, sha)
|
||||
if err == db.ErrResouceOutdated {
|
||||
continue
|
||||
}
|
||||
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||
if setting.Database.UsePostgreSQL {
|
||||
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
// try to update the max_index to next value, and acquire the write-lock for the record
|
||||
res, err := e.Exec("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?", repoID, sha)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if affected == 0 {
|
||||
// this slow path is only for the first time of creating a resource index
|
||||
_, errIns := e.Exec("INSERT INTO `commit_status_index` (repo_id, sha, max_index) VALUES (?, ?, 0)", repoID, sha)
|
||||
res, err = e.Exec("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?", repoID, sha)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
return 0, db.ErrGetResourceIndexFailed
|
||||
}
|
||||
|
||||
// getNextCommitStatusIndex return the next index
|
||||
func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
|
||||
ctx, commiter, err := db.TxContext()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer commiter.Close()
|
||||
|
||||
var preIdx int64
|
||||
_, err = db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
affected, err = res.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// if the update still can not update any records, the record must not exist and there must be some errors (insert error)
|
||||
if affected == 0 {
|
||||
if errIns == nil {
|
||||
return 0, errors.New("impossible error when GetNextCommitStatusIndex, insert and update both succeeded but no record is updated")
|
||||
}
|
||||
return 0, errIns
|
||||
}
|
||||
}
|
||||
|
||||
if err := upsertCommitStatusIndex(ctx, repoID, sha); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var curIdx int64
|
||||
has, err := db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?", repoID, sha, preIdx+1).Get(&curIdx)
|
||||
// now, the new index is in database (protected by the transaction and write-lock)
|
||||
var newIdx int64
|
||||
has, err := e.SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id=? AND sha=?", repoID, sha).Get(&newIdx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !has {
|
||||
return 0, db.ErrResouceOutdated
|
||||
return 0, errors.New("impossible error when GetNextCommitStatusIndex, upsert succeeded but no record can be selected")
|
||||
}
|
||||
if err := commiter.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return curIdx, nil
|
||||
return newIdx, nil
|
||||
}
|
||||
|
||||
func (status *CommitStatus) loadAttributes(ctx context.Context) (err error) {
|
||||
|
@ -291,10 +281,8 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
|||
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
|
||||
}
|
||||
|
||||
// Get the next Status Index
|
||||
idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate commit status index failed: %w", err)
|
||||
if _, err := git.NewIDFromString(opts.SHA); err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext()
|
||||
|
@ -303,6 +291,12 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
// Get the next Status Index
|
||||
idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate commit status index failed: %w", err)
|
||||
}
|
||||
|
||||
opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
|
||||
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
|
||||
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
|
||||
|
@ -316,7 +310,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
|||
|
||||
// Insert new CommitStatus
|
||||
if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil {
|
||||
return fmt.Errorf("Insert CommitStatus[%s, %s]: %w", repoPath, opts.SHA, err)
|
||||
return fmt.Errorf("insert CommitStatus[%s, %s]: %w", repoPath, opts.SHA, err)
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
|
|
|
@ -1010,12 +1010,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||
}
|
||||
}
|
||||
|
||||
if opts.IsPull {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
} else {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
}
|
||||
if err != nil {
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -742,17 +742,9 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
|
|||
if err != nil {
|
||||
return nil, err
|
||||
} else if official {
|
||||
// recalculate the latest official review for reviewer
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if review != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
|
||||
|
@ -770,6 +762,22 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
|
|||
return comment, committer.Commit()
|
||||
}
|
||||
|
||||
// Recalculate the latest official review for reviewer
|
||||
func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64) error {
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issueID, reviewerID)
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if review != nil {
|
||||
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTeamReviewRequest add a review request from one team
|
||||
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||
ctx, committer, err := db.TxContext()
|
||||
|
@ -988,6 +996,12 @@ func DeleteReview(r *Review) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if r.Official {
|
||||
if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
|
|
|
@ -201,3 +201,38 @@ func TestDismissReview(t *testing.T) {
|
|||
assert.False(t, requestReviewExample.Dismissed)
|
||||
assert.True(t, approveReviewExample.Dismissed)
|
||||
}
|
||||
|
||||
func TestDeleteReview(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
review1, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
|
||||
Content: "Official rejection",
|
||||
Type: issues_model.ReviewTypeReject,
|
||||
Official: false,
|
||||
Issue: issue,
|
||||
Reviewer: user,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
review2, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
|
||||
Content: "Official approval",
|
||||
Type: issues_model.ReviewTypeApprove,
|
||||
Official: true,
|
||||
Issue: issue,
|
||||
Reviewer: user,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, issues_model.DeleteReview(review2))
|
||||
|
||||
_, err = issues_model.GetReviewByID(db.DefaultContext, review2.ID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, issues_model.IsErrReviewNotExist(err), "IsErrReviewNotExist")
|
||||
|
||||
review1, err = issues_model.GetReviewByID(db.DefaultContext, review1.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, review1.Official)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package issues
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -47,33 +48,42 @@ func (t *TrackedTime) LoadAttributes() (err error) {
|
|||
}
|
||||
|
||||
func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
|
||||
// Load the issue
|
||||
if t.Issue == nil {
|
||||
t.Issue, err = GetIssueByID(ctx, t.IssueID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = t.Issue.LoadRepo(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if t.User == nil {
|
||||
t.User, err = user_model.GetUserByIDCtx(ctx, t.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadAttributes load Issue, User
|
||||
func (tl TrackedTimeList) LoadAttributes() (err error) {
|
||||
for _, t := range tl {
|
||||
if err = t.LoadAttributes(); err != nil {
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
// Now load the repo for the issue (which we may have just loaded)
|
||||
if t.Issue != nil {
|
||||
err = t.Issue.LoadRepo(ctx)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Load the user
|
||||
if t.User == nil {
|
||||
t.User, err = user_model.GetUserByIDCtx(ctx, t.UserID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
t.User = user_model.NewGhostUser()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAttributes load Issue, User
|
||||
func (tl TrackedTimeList) LoadAttributes() error {
|
||||
for _, t := range tl {
|
||||
if err := t.LoadAttributes(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
|
@ -513,6 +514,13 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
|
|||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if git.DefaultContext == nil {
|
||||
if err = git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for i, m := range migrations[v-minDBVersion:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
|
||||
|
|
|
@ -458,8 +458,9 @@ func CountOrgs(opts FindOrgOptions) (int64, error) {
|
|||
|
||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||
// Not SignedUser
|
||||
if user == nil {
|
||||
// If user is nil, it's an anonymous user/request.
|
||||
// The Ghost user is handled like an anonymous user.
|
||||
if user == nil || user.IsGhost() {
|
||||
return orgOrUser.Visibility == structs.VisibleTypePublic
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ type BlobSearchOptions struct {
|
|||
Digest string
|
||||
Tag string
|
||||
IsManifest bool
|
||||
Repository string
|
||||
}
|
||||
|
||||
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
||||
|
@ -54,6 +55,15 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
|
|||
|
||||
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||
}
|
||||
if opts.Repository != "" {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypePackage,
|
||||
"package_property.name": container_module.PropertyRepository,
|
||||
"package_property.value": opts.Repository,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
|||
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
|
|
|
@ -444,7 +444,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||
},
|
||||
// Repository.NumIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, false),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", false),
|
||||
repoStatsCorrectNumIssues,
|
||||
"repository count 'num_issues'",
|
||||
},
|
||||
|
@ -456,7 +456,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||
},
|
||||
// Repository.NumPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, true),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", true),
|
||||
repoStatsCorrectNumPulls,
|
||||
"repository count 'num_pulls'",
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
|
@ -498,8 +499,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
|||
// Only show a repo that either has a topic or description.
|
||||
subQueryCond := builder.NewCond()
|
||||
|
||||
// Topic checking. Topics is non-null.
|
||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||
// Topic checking. Topics are present.
|
||||
if setting.Database.UsePostgreSQL { // postgres stores the topics as json and not as text
|
||||
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
|
||||
} else {
|
||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||
}
|
||||
|
||||
// Description checking. Description not empty.
|
||||
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
|
||||
|
|
|
@ -185,7 +185,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
|
|||
return committer.Commit()
|
||||
}
|
||||
|
||||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
|
||||
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||
func UpdateRepoSize(ctx context.Context, repoID, size int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{
|
||||
Size: size,
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
setting_module "code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"strk.kbt.io/projects/go/libravatar"
|
||||
|
@ -35,6 +36,10 @@ func (s *Setting) TableName() string {
|
|||
}
|
||||
|
||||
func (s *Setting) GetValueBool() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
b, _ := strconv.ParseBool(s.SettingValue)
|
||||
return b
|
||||
}
|
||||
|
@ -75,8 +80,8 @@ func IsErrDataExpired(err error) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// GetSetting returns specific setting
|
||||
func GetSetting(key string) (*Setting, error) {
|
||||
// GetSettingNoCache returns specific setting without using the cache
|
||||
func GetSettingNoCache(key string) (*Setting, error) {
|
||||
v, err := GetSettings([]string{key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -84,7 +89,26 @@ func GetSetting(key string) (*Setting, error) {
|
|||
if len(v) == 0 {
|
||||
return nil, ErrSettingIsNotExist{key}
|
||||
}
|
||||
return v[key], nil
|
||||
return v[strings.ToLower(key)], nil
|
||||
}
|
||||
|
||||
// GetSetting returns the setting value via the key
|
||||
func GetSetting(key string) (string, error) {
|
||||
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
|
||||
res, err := GetSettingNoCache(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetSettingBool return bool value of setting,
|
||||
// none existing keys and errors are ignored and result in false
|
||||
func GetSettingBool(key string) bool {
|
||||
s, _ := GetSetting(key)
|
||||
v, _ := strconv.ParseBool(s)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetSettings returns specific settings
|
||||
|
@ -108,7 +132,7 @@ func GetSettings(keys []string) (map[string]*Setting, error) {
|
|||
type AllSettings map[string]*Setting
|
||||
|
||||
func (settings AllSettings) Get(key string) Setting {
|
||||
if v, ok := settings[key]; ok {
|
||||
if v, ok := settings[strings.ToLower(key)]; ok {
|
||||
return *v
|
||||
}
|
||||
return Setting{}
|
||||
|
@ -139,12 +163,13 @@ func GetAllSettings() (AllSettings, error) {
|
|||
|
||||
// DeleteSetting deletes a specific setting for a user
|
||||
func DeleteSetting(setting *Setting) error {
|
||||
cache.Remove(genSettingCacheKey(setting.SettingKey))
|
||||
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
|
||||
return err
|
||||
}
|
||||
|
||||
func SetSettingNoVersion(key, value string) error {
|
||||
s, err := GetSetting(key)
|
||||
s, err := GetSettingNoCache(key)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
return SetSetting(&Setting{
|
||||
SettingKey: key,
|
||||
|
@ -163,7 +188,14 @@ func SetSetting(setting *Setting) error {
|
|||
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setting.Version++
|
||||
|
||||
cc := cache.GetCache()
|
||||
if cc != nil {
|
||||
return cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -213,9 +245,9 @@ var (
|
|||
|
||||
func Init() error {
|
||||
var disableGravatar bool
|
||||
disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
|
||||
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
disableGravatar = setting.GetDefaultDisableGravatar()
|
||||
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
@ -224,9 +256,9 @@ func Init() error {
|
|||
}
|
||||
|
||||
var enableFederatedAvatar bool
|
||||
enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
|
||||
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
@ -234,20 +266,30 @@ func Init() error {
|
|||
enableFederatedAvatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
|
||||
if setting.OfflineMode {
|
||||
if setting_module.OfflineMode {
|
||||
disableGravatar = true
|
||||
enableFederatedAvatar = false
|
||||
}
|
||||
|
||||
if disableGravatar || !enableFederatedAvatar {
|
||||
var err error
|
||||
GravatarSourceURL, err = url.Parse(setting.GravatarSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting.GravatarSource, err)
|
||||
if !GetSettingBool(KeyPictureDisableGravatar) {
|
||||
if err := SetSettingNoVersion(KeyPictureDisableGravatar, "true"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
}
|
||||
}
|
||||
if GetSettingBool(KeyPictureEnableFederatedAvatar) {
|
||||
if err := SetSettingNoVersion(KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if enableFederatedAvatarSetting.GetValueBool() {
|
||||
if enableFederatedAvatar || !disableGravatar {
|
||||
var err error
|
||||
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||
}
|
||||
}
|
||||
|
||||
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() {
|
||||
LibravatarService = libravatar.New()
|
||||
if GravatarSourceURL.Scheme == "https" {
|
||||
LibravatarService.SetUseHTTPS(true)
|
||||
|
|
|
@ -9,3 +9,8 @@ const (
|
|||
KeyPictureDisableGravatar = "picture.disable_gravatar"
|
||||
KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar"
|
||||
)
|
||||
|
||||
// genSettingCacheKey returns the cache key for some configuration
|
||||
func genSettingCacheKey(key string) string {
|
||||
return "system.setting." + key
|
||||
}
|
||||
|
|
|
@ -34,10 +34,14 @@ func TestSettings(t *testing.T) {
|
|||
assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
|
||||
|
||||
// updated setting
|
||||
updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.Version}
|
||||
updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: settings[strings.ToLower(keyName)].Version}
|
||||
err = system.SetSetting(updatedSetting)
|
||||
assert.NoError(t, err)
|
||||
|
||||
value, err := system.GetSetting(keyName)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, updatedSetting.SettingValue, value)
|
||||
|
||||
// get all settings
|
||||
settings, err = system.GetAllSettings()
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -89,6 +89,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
|||
&user_model.UserBadge{UserID: u.ID},
|
||||
&pull_model.AutoMerge{DoerID: u.ID},
|
||||
&pull_model.ReviewState{UserID: u.ID},
|
||||
&user_model.Redirect{RedirectUserID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %w", err)
|
||||
}
|
||||
|
|
|
@ -68,11 +68,7 @@ func (u *User) AvatarLinkWithSize(size int) string {
|
|||
useLocalAvatar := false
|
||||
autoGenerateAvatar := false
|
||||
|
||||
var disableGravatar bool
|
||||
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
||||
if disableGravatarSetting != nil {
|
||||
disableGravatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -47,9 +48,25 @@ func IsErrUserSettingIsNotExist(err error) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// GetSetting returns specific setting
|
||||
func GetSetting(uid int64, key string) (*Setting, error) {
|
||||
v, err := GetUserSettings(uid, []string{key})
|
||||
// genSettingCacheKey returns the cache key for some configuration
|
||||
func genSettingCacheKey(userID int64, key string) string {
|
||||
return fmt.Sprintf("user_%d.setting.%s", userID, key)
|
||||
}
|
||||
|
||||
// GetSetting returns the setting value via the key
|
||||
func GetSetting(uid int64, key string) (string, error) {
|
||||
return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) {
|
||||
res, err := GetSettingNoCache(uid, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetSettingNoCache returns specific setting without using the cache
|
||||
func GetSettingNoCache(uid int64, key string) (*Setting, error) {
|
||||
v, err := GetSettings(uid, []string{key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -59,8 +76,8 @@ func GetSetting(uid int64, key string) (*Setting, error) {
|
|||
return v[key], nil
|
||||
}
|
||||
|
||||
// GetUserSettings returns specific settings from user
|
||||
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
|
||||
// GetSettings returns specific settings from user
|
||||
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
|
||||
settings := make([]*Setting, 0, len(keys))
|
||||
if err := db.GetEngine(db.DefaultContext).
|
||||
Where("user_id=?", uid).
|
||||
|
@ -105,6 +122,7 @@ func GetUserSetting(userID int64, key string, def ...string) (string, error) {
|
|||
if err := validateUserSettingKey(key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
setting := &Setting{UserID: userID, SettingKey: key}
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(setting)
|
||||
if err != nil {
|
||||
|
@ -124,7 +142,10 @@ func DeleteUserSetting(userID int64, key string) error {
|
|||
if err := validateUserSettingKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache.Remove(genSettingCacheKey(userID, key))
|
||||
_, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -133,7 +154,12 @@ func SetUserSetting(userID int64, key, value string) error {
|
|||
if err := validateUserSettingKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
return upsertUserSettingValue(userID, key, value)
|
||||
|
||||
_, err := cache.GetString(genSettingCacheKey(userID, key), func() (string, error) {
|
||||
return value, upsertUserSettingValue(userID, key, value)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func upsertUserSettingValue(userID int64, key, value string) error {
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestSettings(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// get specific setting
|
||||
settings, err := user_model.GetUserSettings(99, []string{keyName})
|
||||
settings, err := user_model.GetSettings(99, []string{keyName})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, settings, 1)
|
||||
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
|
||||
|
|
|
@ -1227,7 +1227,10 @@ func GetUserByOpenID(uri string) (*User, error) {
|
|||
// GetAdminUser returns the first administrator
|
||||
func GetAdminUser() (*User, error) {
|
||||
var admin User
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("is_admin=?", true).Get(&admin)
|
||||
has, err := db.GetEngine(db.DefaultContext).
|
||||
Where("is_admin=?", true).
|
||||
Asc("id"). // Reliably get the admin with the lowest ID.
|
||||
Get(&admin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
// GetKeyPair function returns a user's private and public keys
|
||||
func GetKeyPair(user *user_model.User) (pub, priv string, err error) {
|
||||
var settings map[string]*user_model.Setting
|
||||
settings, err = user_model.GetUserSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
|
||||
settings, err = user_model.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(settings) == 0 {
|
||||
|
|
|
@ -51,27 +51,26 @@ func GetString(key string, getFunc func() (string, error)) (string, error) {
|
|||
if conn == nil || setting.CacheService.TTL == 0 {
|
||||
return getFunc()
|
||||
}
|
||||
if !conn.IsExist(key) {
|
||||
var (
|
||||
value string
|
||||
err error
|
||||
)
|
||||
if value, err = getFunc(); err != nil {
|
||||
|
||||
cached := conn.Get(key)
|
||||
|
||||
if cached == nil {
|
||||
value, err := getFunc()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
}
|
||||
value := conn.Get(key)
|
||||
if v, ok := value.(string); ok {
|
||||
return v, nil
|
||||
|
||||
if value, ok := cached.(string); ok {
|
||||
return value, nil
|
||||
}
|
||||
if v, ok := value.(fmt.Stringer); ok {
|
||||
return v.String(), nil
|
||||
|
||||
if stringer, ok := cached.(fmt.Stringer); ok {
|
||||
return stringer.String(), nil
|
||||
}
|
||||
return fmt.Sprintf("%s", conn.Get(key)), nil
|
||||
|
||||
return fmt.Sprintf("%s", cached), nil
|
||||
}
|
||||
|
||||
// GetInt returns key value from cache with callback when no key exists in cache
|
||||
|
@ -79,30 +78,33 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
|
|||
if conn == nil || setting.CacheService.TTL == 0 {
|
||||
return getFunc()
|
||||
}
|
||||
if !conn.IsExist(key) {
|
||||
var (
|
||||
value int
|
||||
err error
|
||||
)
|
||||
if value, err = getFunc(); err != nil {
|
||||
|
||||
cached := conn.Get(key)
|
||||
|
||||
if cached == nil {
|
||||
value, err := getFunc()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
}
|
||||
switch value := conn.Get(key).(type) {
|
||||
|
||||
switch v := cached.(type) {
|
||||
case int:
|
||||
return value, nil
|
||||
return v, nil
|
||||
case string:
|
||||
v, err := strconv.Atoi(value)
|
||||
value, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v, nil
|
||||
return value, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
|
||||
value, err := getFunc()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,30 +113,34 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
|
|||
if conn == nil || setting.CacheService.TTL == 0 {
|
||||
return getFunc()
|
||||
}
|
||||
if !conn.IsExist(key) {
|
||||
var (
|
||||
value int64
|
||||
err error
|
||||
)
|
||||
if value, err = getFunc(); err != nil {
|
||||
|
||||
cached := conn.Get(key)
|
||||
|
||||
if cached == nil {
|
||||
value, err := getFunc()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
}
|
||||
switch value := conn.Get(key).(type) {
|
||||
|
||||
switch v := conn.Get(key).(type) {
|
||||
case int64:
|
||||
return value, nil
|
||||
return v, nil
|
||||
case string:
|
||||
v, err := strconv.ParseInt(value, 10, 64)
|
||||
value, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v, nil
|
||||
return value, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
|
||||
value, err := getFunc()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,12 @@ func AmbiguousTablesForLocale(locale translation.Locale) []*AmbiguousTable {
|
|||
key = key[:idx]
|
||||
}
|
||||
}
|
||||
if table == nil && (locale.Language() == "zh-CN" || locale.Language() == "zh_CN") {
|
||||
table = AmbiguousCharacters["zh-hans"]
|
||||
}
|
||||
if table == nil && strings.HasPrefix(locale.Language(), "zh") {
|
||||
table = AmbiguousCharacters["zh-hant"]
|
||||
}
|
||||
if table == nil {
|
||||
table = AmbiguousCharacters["_default"]
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package charset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
|
@ -32,7 +33,7 @@ func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune)
|
|||
return streamer.escaped, sb.String()
|
||||
}
|
||||
|
||||
// EscapeControlReaders escapes the unicode control sequences in a provider reader and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
|
||||
// EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
|
||||
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
|
||||
outputStream := &HTMLStreamerWriter{Writer: writer}
|
||||
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
|
||||
|
@ -44,6 +45,35 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
|
|||
return streamer.escaped, err
|
||||
}
|
||||
|
||||
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
|
||||
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
|
||||
bufRd := bufio.NewReader(reader)
|
||||
outputStream := &HTMLStreamerWriter{Writer: writer}
|
||||
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
|
||||
|
||||
for {
|
||||
line, rdErr := bufRd.ReadString('\n')
|
||||
if len(line) > 0 {
|
||||
if err := streamer.Text(line); err != nil {
|
||||
streamer.escaped.HasError = true
|
||||
log.Error("Error whilst escaping: %v", err)
|
||||
return streamer.escaped, err
|
||||
}
|
||||
}
|
||||
if rdErr != nil {
|
||||
if rdErr != io.EOF {
|
||||
err = rdErr
|
||||
}
|
||||
break
|
||||
}
|
||||
if err := streamer.SelfClosingTag("br"); err != nil {
|
||||
streamer.escaped.HasError = true
|
||||
return streamer.escaped, err
|
||||
}
|
||||
}
|
||||
return streamer.escaped, err
|
||||
}
|
||||
|
||||
// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
|
||||
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
|
||||
sb := &strings.Builder{}
|
||||
|
|
|
@ -220,7 +220,13 @@ func (ctx *APIContext) CheckForOTP() {
|
|||
func APIAuth(authMethod auth_service.Method) func(*APIContext) {
|
||||
return func(ctx *APIContext) {
|
||||
// Get user from session if logged in.
|
||||
ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
var err error
|
||||
ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnauthorized, "APIAuth", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Doer != nil {
|
||||
if ctx.Locale.Language() != ctx.Doer.Language {
|
||||
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
||||
|
@ -388,7 +394,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) == 40 {
|
||||
} else if len(refName) == git.SHAFullLength {
|
||||
ctx.Repo.CommitID = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
|
@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
|
|||
if statusPrefix == 4 || statusPrefix == 5 {
|
||||
log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
|
||||
}
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
ctx.Resp.WriteHeader(status)
|
||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
|
@ -345,34 +346,61 @@ func (ctx *Context) RespHeader() http.Header {
|
|||
return ctx.Resp.Header()
|
||||
}
|
||||
|
||||
type ServeHeaderOptions struct {
|
||||
ContentType string // defaults to "application/octet-stream"
|
||||
ContentTypeCharset string
|
||||
ContentLength *int64
|
||||
Disposition string // defaults to "attachment"
|
||||
Filename string
|
||||
CacheDuration time.Duration // defaults to 5 minutes
|
||||
LastModified time.Time
|
||||
}
|
||||
|
||||
// SetServeHeaders sets necessary content serve headers
|
||||
func (ctx *Context) SetServeHeaders(filename string) {
|
||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||
header := ctx.Resp.Header()
|
||||
|
||||
contentType := typesniffer.ApplicationOctetStream
|
||||
if opts.ContentType != "" {
|
||||
if opts.ContentTypeCharset != "" {
|
||||
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
||||
} else {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
}
|
||||
header.Set("Content-Type", contentType)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
if opts.ContentLength != nil {
|
||||
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
|
||||
}
|
||||
|
||||
if opts.Filename != "" {
|
||||
disposition := opts.Disposition
|
||||
if disposition == "" {
|
||||
disposition = "attachment"
|
||||
}
|
||||
|
||||
backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
|
||||
header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
|
||||
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
}
|
||||
|
||||
duration := opts.CacheDuration
|
||||
if duration == 0 {
|
||||
duration = 5 * time.Minute
|
||||
}
|
||||
httpcache.AddCacheControlToHeader(header, duration)
|
||||
|
||||
if !opts.LastModified.IsZero() {
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
}
|
||||
}
|
||||
|
||||
// ServeContent serves content to http request
|
||||
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
|
||||
ctx.SetServeHeaders(name)
|
||||
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
|
||||
}
|
||||
|
||||
// ServeFile serves given file to response.
|
||||
func (ctx *Context) ServeFile(file string, names ...string) {
|
||||
var name string
|
||||
if len(names) > 0 {
|
||||
name = names[0]
|
||||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
ctx.SetServeHeaders(name)
|
||||
http.ServeFile(ctx.Resp, ctx.Req, file)
|
||||
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
|
||||
ctx.SetServeHeaders(opts)
|
||||
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
|
||||
}
|
||||
|
||||
// UploadStream returns the request body or the first form file
|
||||
|
@ -635,7 +663,13 @@ func getCsrfOpts() CsrfOptions {
|
|||
// Auth converts auth.Auth as a middleware
|
||||
func Auth(authMethod auth.Method) func(*Context) {
|
||||
return func(ctx *Context) {
|
||||
ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
var err error
|
||||
ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
if err != nil {
|
||||
log.Error("Failed to verify user %v: %v", ctx.Req.RemoteAddr, err)
|
||||
ctx.Error(http.StatusUnauthorized, "Verify")
|
||||
return
|
||||
}
|
||||
if ctx.Doer != nil {
|
||||
if ctx.Locale.Language() != ctx.Doer.Language {
|
||||
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
||||
|
|
|
@ -816,7 +816,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
|||
}
|
||||
// For legacy and API support only full commit sha
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) == 40 {
|
||||
if len(parts) > 0 && len(parts[0]) == git.SHAFullLength {
|
||||
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
|
@ -852,7 +852,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
|||
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
|
||||
case RepoRefCommit:
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 {
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength {
|
||||
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
|
@ -961,7 +961,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= 40 {
|
||||
} else if len(refName) >= 7 && len(refName) <= git.SHAFullLength {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
|
@ -971,7 +971,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||
return
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < 40 {
|
||||
if len(refName) < git.SHAFullLength {
|
||||
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
||||
}
|
||||
|
@ -1087,6 +1087,9 @@ func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplat
|
|||
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
|
||||
invalidFiles[fullName] = err
|
||||
} else {
|
||||
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
|
||||
it.Ref = git.BranchPrefix + it.Ref
|
||||
}
|
||||
issueTemplates = append(issueTemplates, it)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,12 +110,11 @@ func ToAPIIssueList(il issues_model.IssueList) []*api.Issue {
|
|||
// ToTrackedTime converts TrackedTime to API format
|
||||
func ToTrackedTime(t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
|
||||
apiT = &api.TrackedTime{
|
||||
ID: t.ID,
|
||||
IssueID: t.IssueID,
|
||||
UserID: t.UserID,
|
||||
UserName: t.User.Name,
|
||||
Time: t.Time,
|
||||
Created: t.Created,
|
||||
ID: t.ID,
|
||||
IssueID: t.IssueID,
|
||||
UserID: t.UserID,
|
||||
Time: t.Time,
|
||||
Created: t.Created,
|
||||
}
|
||||
if t.Issue != nil {
|
||||
apiT.Issue = ToAPIIssue(t.Issue)
|
||||
|
|
|
@ -89,6 +89,10 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
|||
},
|
||||
}
|
||||
|
||||
if pr.Issue.ClosedUnix != 0 {
|
||||
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
|
||||
|
|
|
@ -205,6 +205,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
|||
// find stopwatches without existing issue
|
||||
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
|
||||
"stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
|
||||
// find redirects without existing user.
|
||||
genericOrphanCheck("Orphaned Redirects without existing redirect user",
|
||||
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
|
||||
)
|
||||
|
||||
for _, c := range consistencyChecks {
|
||||
|
|
|
@ -19,11 +19,9 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
|
|||
numReposUpdated := 0
|
||||
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||
numRepos++
|
||||
runOpts := &git.RunOpts{Dir: repo.RepoPath()}
|
||||
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(runOpts)
|
||||
|
||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(runOpts)
|
||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
// what we expect: default branch is valid, and HEAD points to it
|
||||
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
|
||||
|
@ -49,7 +47,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
|
|||
}
|
||||
|
||||
// otherwise, let's try fixing HEAD
|
||||
err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", repo.DefaultBranch).Run(runOpts)
|
||||
err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
if err != nil {
|
||||
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
||||
return nil
|
||||
|
@ -65,7 +63,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
|
|||
logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Info("All %d repos have their HEADs in the correct state")
|
||||
logger.Info("All %d repos have their HEADs in the correct state", numRepos)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
||||
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -202,8 +202,11 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||
if opts == nil {
|
||||
opts = &RunOpts{}
|
||||
}
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = defaultCommandExecutionTimeout
|
||||
|
||||
// We must not change the provided options
|
||||
timeout := opts.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = defaultCommandExecutionTimeout
|
||||
}
|
||||
|
||||
if len(opts.Dir) == 0 {
|
||||
|
@ -238,7 +241,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||
if opts.UseContextTimeout {
|
||||
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
||||
} else {
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
|
||||
}
|
||||
defer finished()
|
||||
|
||||
|
@ -339,9 +342,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
|
|||
}
|
||||
stdoutBuf := &bytes.Buffer{}
|
||||
stderrBuf := &bytes.Buffer{}
|
||||
opts.Stdout = stdoutBuf
|
||||
opts.Stderr = stderrBuf
|
||||
err := c.Run(opts)
|
||||
|
||||
// We must not change the provided options as it could break future calls - therefore make a copy.
|
||||
newOpts := &RunOpts{
|
||||
Env: opts.Env,
|
||||
Timeout: opts.Timeout,
|
||||
UseContextTimeout: opts.UseContextTimeout,
|
||||
Dir: opts.Dir,
|
||||
Stdout: stdoutBuf,
|
||||
Stderr: stderrBuf,
|
||||
Stdin: opts.Stdin,
|
||||
PipelineFunc: opts.PipelineFunc,
|
||||
}
|
||||
|
||||
err := c.Run(newOpts)
|
||||
stderr = stderrBuf.Bytes()
|
||||
if err != nil {
|
||||
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
|
||||
|
|
|
@ -53,7 +53,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
|
|||
|
||||
// IsBranchExist returns true if given branch exists in current repository.
|
||||
func (repo *Repository) IsBranchExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func (repo *Repository) RemoveReference(name string) error {
|
|||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == 40 {
|
||||
if len(commitID) == SHAFullLength {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
|
|
|
@ -138,7 +138,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
|
|||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) == 40 && IsValidSHAPattern(commitID) {
|
||||
if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
|
||||
// ReadTreeToIndex reads a treeish to the index
|
||||
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
|
||||
if len(treeish) != 40 {
|
||||
if len(treeish) != SHAFullLength {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
// IsTagExist returns true if given tag exists in the repository.
|
||||
func (repo *Repository) IsTagExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
|||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != 40 {
|
||||
if len(idStr) != SHAFullLength {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -67,7 +67,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
|||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != 40 {
|
||||
if len(idStr) != SHAFullLength {
|
||||
res, err := repo.GetRefCommitID(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -18,6 +18,9 @@ const EmptySHA = "0000000000000000000000000000000000000000"
|
|||
// EmptyTreeSHA is the SHA of an empty tree
|
||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||
|
||||
// SHAFullLength is the full length of a git SHA
|
||||
const SHAFullLength = 40
|
||||
|
||||
// SHAPattern can be used to determine if a string is an valid sha
|
||||
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
|
||||
|
||||
|
@ -51,7 +54,7 @@ func MustIDFromString(s string) SHA1 {
|
|||
func NewIDFromString(s string) (SHA1, error) {
|
||||
var id SHA1
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) != 40 {
|
||||
if len(s) != SHAFullLength {
|
||||
return id, fmt.Errorf("Length must be 40: %s", s)
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
|
|
|
@ -10,6 +10,7 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
|
@ -30,7 +31,9 @@ type Signature = object.Signature
|
|||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
||||
sig := new(Signature)
|
||||
emailStart := bytes.IndexByte(line, '<')
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
emailEnd := bytes.IndexByte(line, '>')
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -51,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
hasTime := emailEnd+2 < len(line)
|
||||
|
|
|
@ -100,6 +100,9 @@ func RefURL(repoURL, ref string) string {
|
|||
return repoURL + "/src/branch/" + refName
|
||||
case strings.HasPrefix(ref, TagPrefix):
|
||||
return repoURL + "/src/tag/" + refName
|
||||
case !IsValidSHAPattern(ref):
|
||||
// assume they mean a branch
|
||||
return repoURL + "/src/branch/" + refName
|
||||
default:
|
||||
return repoURL + "/src/commit/" + refName
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func validateOptions(field *api.IssueFormField, idx int) error {
|
|||
return position.Errorf("should be a string")
|
||||
}
|
||||
case api.IssueFormFieldTypeCheckboxes:
|
||||
opt, ok := option.(map[interface{}]interface{})
|
||||
opt, ok := option.(map[string]interface{})
|
||||
if !ok {
|
||||
return position.Errorf("should be a dictionary")
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ func (o *valuedOption) Label() string {
|
|||
return label
|
||||
}
|
||||
case api.IssueFormFieldTypeCheckboxes:
|
||||
if vs, ok := o.data.(map[interface{}]interface{}); ok {
|
||||
if vs, ok := o.data.(map[string]interface{}); ok {
|
||||
if v, ok := vs["label"].(string); ok {
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -6,18 +6,21 @@ package template
|
|||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
wantErr string
|
||||
name string
|
||||
filename string
|
||||
content string
|
||||
want *api.IssueTemplate
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "miss name",
|
||||
|
@ -316,21 +319,9 @@ body:
|
|||
`,
|
||||
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpl, err := unmarshal("test.yaml", []byte(tt.content))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Validate(tmpl); (err == nil) != (tt.wantErr == "") || err != nil && err.Error() != tt.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %q", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
content := `
|
||||
{
|
||||
name: "valid",
|
||||
content: `
|
||||
name: Name
|
||||
title: Title
|
||||
about: About
|
||||
|
@ -386,96 +377,227 @@ body:
|
|||
required: false
|
||||
- label: Option 3 of checkboxes
|
||||
required: true
|
||||
`
|
||||
want := &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: []string{"label1", "label2"},
|
||||
Ref: "Ref",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]interface{}{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "textarea",
|
||||
ID: "id2",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of textarea",
|
||||
"description": "Description of textarea",
|
||||
"placeholder": "Placeholder of textarea",
|
||||
"value": "Value of textarea",
|
||||
"render": "bash",
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "input",
|
||||
ID: "id3",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of input",
|
||||
"description": "Description of input",
|
||||
"placeholder": "Placeholder of input",
|
||||
"value": "Value of input",
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
"is_number": true,
|
||||
"regex": "[a-zA-Z0-9]+",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "dropdown",
|
||||
ID: "id4",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of dropdown",
|
||||
"description": "Description of dropdown",
|
||||
"multiple": true,
|
||||
"options": []interface{}{
|
||||
"Option 1 of dropdown",
|
||||
"Option 2 of dropdown",
|
||||
"Option 3 of dropdown",
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: []string{"label1", "label2"},
|
||||
Ref: "Ref",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]interface{}{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
{
|
||||
Type: "textarea",
|
||||
ID: "id2",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of textarea",
|
||||
"description": "Description of textarea",
|
||||
"placeholder": "Placeholder of textarea",
|
||||
"value": "Value of textarea",
|
||||
"render": "bash",
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "checkboxes",
|
||||
ID: "id5",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of checkboxes",
|
||||
"description": "Description of checkboxes",
|
||||
"options": []interface{}{
|
||||
map[interface{}]interface{}{"label": "Option 1 of checkboxes", "required": true},
|
||||
map[interface{}]interface{}{"label": "Option 2 of checkboxes", "required": false},
|
||||
map[interface{}]interface{}{"label": "Option 3 of checkboxes", "required": true},
|
||||
{
|
||||
Type: "input",
|
||||
ID: "id3",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of input",
|
||||
"description": "Description of input",
|
||||
"placeholder": "Placeholder of input",
|
||||
"value": "Value of input",
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
"is_number": true,
|
||||
"regex": "[a-zA-Z0-9]+",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "dropdown",
|
||||
ID: "id4",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of dropdown",
|
||||
"description": "Description of dropdown",
|
||||
"multiple": true,
|
||||
"options": []interface{}{
|
||||
"Option 1 of dropdown",
|
||||
"Option 2 of dropdown",
|
||||
"Option 3 of dropdown",
|
||||
},
|
||||
},
|
||||
Validations: map[string]interface{}{
|
||||
"required": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "checkboxes",
|
||||
ID: "id5",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "Label of checkboxes",
|
||||
"description": "Description of checkboxes",
|
||||
"options": []interface{}{
|
||||
map[string]interface{}{"label": "Option 1 of checkboxes", "required": true},
|
||||
map[string]interface{}{"label": "Option 2 of checkboxes", "required": false},
|
||||
map[string]interface{}{"label": "Option 3 of checkboxes", "required": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
}
|
||||
got, err := unmarshal("test.yaml", []byte(content))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Validate(got); err != nil {
|
||||
t.Errorf("Validate() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
jsonWant, _ := json.Marshal(want)
|
||||
jsonGot, _ := json.Marshal(got)
|
||||
t.Errorf("want:\n%s\ngot:\n%s", jsonWant, jsonGot)
|
||||
}
|
||||
})
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "single label",
|
||||
content: `
|
||||
name: Name
|
||||
title: Title
|
||||
about: About
|
||||
labels: label1
|
||||
ref: Ref
|
||||
body:
|
||||
- type: markdown
|
||||
id: id1
|
||||
attributes:
|
||||
value: Value of the markdown
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: []string{"label1"},
|
||||
Ref: "Ref",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]interface{}{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "comma-delimited labels",
|
||||
content: `
|
||||
name: Name
|
||||
title: Title
|
||||
about: About
|
||||
labels: label1,label2,,label3 ,,
|
||||
ref: Ref
|
||||
body:
|
||||
- type: markdown
|
||||
id: id1
|
||||
attributes:
|
||||
value: Value of the markdown
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: []string{"label1", "label2", "label3"},
|
||||
Ref: "Ref",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]interface{}{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "empty string as labels",
|
||||
content: `
|
||||
name: Name
|
||||
title: Title
|
||||
about: About
|
||||
labels: ''
|
||||
ref: Ref
|
||||
body:
|
||||
- type: markdown
|
||||
id: id1
|
||||
attributes:
|
||||
value: Value of the markdown
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: nil,
|
||||
Ref: "Ref",
|
||||
Fields: []*api.IssueFormField{
|
||||
{
|
||||
Type: "markdown",
|
||||
ID: "id1",
|
||||
Attributes: map[string]interface{}{
|
||||
"value": "Value of the markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
FileName: "test.yaml",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "comma delimited labels in markdown",
|
||||
filename: "test.md",
|
||||
content: `---
|
||||
name: Name
|
||||
title: Title
|
||||
about: About
|
||||
labels: label1,label2,,label3 ,,
|
||||
ref: Ref
|
||||
---
|
||||
Content
|
||||
`,
|
||||
want: &api.IssueTemplate{
|
||||
Name: "Name",
|
||||
Title: "Title",
|
||||
About: "About",
|
||||
Labels: []string{"label1", "label2", "label3"},
|
||||
Ref: "Ref",
|
||||
Fields: nil,
|
||||
Content: "Content\n",
|
||||
FileName: "test.md",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filename := "test.yaml"
|
||||
if tt.filename != "" {
|
||||
filename = tt.filename
|
||||
}
|
||||
tmpl, err := unmarshal(filename, []byte(tt.content))
|
||||
require.NoError(t, err)
|
||||
if tt.wantErr != "" {
|
||||
require.EqualError(t, Validate(tmpl), tt.wantErr)
|
||||
} else {
|
||||
require.NoError(t, Validate(tmpl))
|
||||
want, _ := json.Marshal(tt.want)
|
||||
got, _ := json.Marshal(tmpl)
|
||||
require.JSONEq(t, string(want), string(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderToMarkdown(t *testing.T) {
|
||||
|
|
|
@ -7,15 +7,16 @@ package template
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// CouldBe indicates a file with the filename could be a template,
|
||||
|
@ -43,7 +44,7 @@ func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
|
|||
|
||||
// UnmarshalFromEntry parses out a valid template from the blob in entry
|
||||
func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
|
||||
return unmarshalFromEntry(entry, filepath.Join(dir, entry.Name()))
|
||||
return unmarshalFromEntry(entry, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here
|
||||
}
|
||||
|
||||
// UnmarshalFromCommit parses out a valid template from the commit
|
||||
|
@ -95,14 +96,27 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
|
|||
}{}
|
||||
|
||||
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
|
||||
templateBody, err := markdown.ExtractMetadata(string(content), it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
it.Content = templateBody
|
||||
if it.About == "" {
|
||||
if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
|
||||
it.About = compatibleTemplate.About
|
||||
if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil {
|
||||
// The only thing we know here is that we can't extract metadata from the content,
|
||||
// it's hard to tell if metadata doesn't exist or metadata isn't valid.
|
||||
// There's an example template:
|
||||
//
|
||||
// ---
|
||||
// # Title
|
||||
// ---
|
||||
// Content
|
||||
//
|
||||
// It could be a valid markdown with two horizontal lines, or an invalid markdown with wrong metadata.
|
||||
|
||||
it.Content = string(content)
|
||||
it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
|
||||
it.About, _ = util.SplitStringAtByteN(it.Content, 80)
|
||||
} else {
|
||||
it.Content = templateBody
|
||||
if it.About == "" {
|
||||
if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
|
||||
it.About = compatibleTemplate.About
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if typ == api.IssueTemplateTypeYaml {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package external
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -133,11 +134,13 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
|||
if !p.IsInputFile {
|
||||
cmd.Stdin = input
|
||||
}
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = &stderr
|
||||
process.SetSysProcAttribute(cmd)
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("%s render run command %s %v failed: %w", p.Name(), commands[0], args, err)
|
||||
return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,82 +9,86 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func validateMetadata(it structs.IssueTemplate) bool {
|
||||
/*
|
||||
A legacy to keep the unit tests working.
|
||||
Copied from the method "func (it IssueTemplate) Valid() bool", the original method has been removed.
|
||||
Because it becomes quite complicated to validate an issue template which is support yaml form now.
|
||||
The new way to validate an issue template is to call the Validate in modules/issue/template,
|
||||
*/
|
||||
/*
|
||||
IssueTemplate is a legacy to keep the unit tests working.
|
||||
Copied from structs.IssueTemplate, the original type has been changed a lot to support yaml template.
|
||||
*/
|
||||
type IssueTemplate struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
About string `json:"about" yaml:"about"`
|
||||
Labels []string `json:"labels" yaml:"labels"`
|
||||
Ref string `json:"ref" yaml:"ref"`
|
||||
}
|
||||
|
||||
func (it *IssueTemplate) Valid() bool {
|
||||
return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
|
||||
}
|
||||
|
||||
func TestExtractMetadata(t *testing.T) {
|
||||
t.Run("ValidFrontAndBody", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bodyTest, body)
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
assert.True(t, meta.Valid())
|
||||
})
|
||||
|
||||
t.Run("NoFirstSeparator", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("NoLastSeparator", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("NoBody", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", body)
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
assert.True(t, meta.Valid())
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractMetadataBytes(t *testing.T) {
|
||||
t.Run("ValidFrontAndBody", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bodyTest, string(body))
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
assert.True(t, meta.Valid())
|
||||
})
|
||||
|
||||
t.Run("NoFirstSeparator", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("NoLastSeparator", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("NoBody", func(t *testing.T) {
|
||||
var meta structs.IssueTemplate
|
||||
var meta IssueTemplate
|
||||
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", string(body))
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
assert.True(t, meta.Valid())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -97,7 +101,7 @@ labels:
|
|||
- bug
|
||||
- "test label"`
|
||||
bodyTest = "This is the body"
|
||||
metaTest = structs.IssueTemplate{
|
||||
metaTest = IssueTemplate{
|
||||
Name: "Test",
|
||||
About: "A Test",
|
||||
Title: "Test Title",
|
||||
|
|
|
@ -30,6 +30,13 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
|||
return s.store.Open(KeyToRelativePath(key))
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func (s *ContentStore) Has(key BlobHash256Key) error {
|
||||
_, err := s.store.Stat(KeyToRelativePath(key))
|
||||
return err
|
||||
}
|
||||
|
||||
// Save stores a package blob
|
||||
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
|
||||
_, err := s.store.Save(KeyToRelativePath(key), r, size)
|
||||
|
|
|
@ -6,8 +6,10 @@ package nuget
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -183,7 +185,23 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
|||
return &Package{
|
||||
PackageType: packageType,
|
||||
ID: p.Metadata.ID,
|
||||
Version: v.String(),
|
||||
Version: toNormalizedVersion(v),
|
||||
Metadata: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
|
||||
// https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
|
||||
func toNormalizedVersion(v *version.Version) string {
|
||||
var buf bytes.Buffer
|
||||
segments := v.Segments64()
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
if len(segments) > 3 && segments[3] > 0 {
|
||||
fmt.Fprintf(&buf, ".%d", segments[3])
|
||||
}
|
||||
pre := v.Prerelease()
|
||||
if pre != "" {
|
||||
fmt.Fprint(&buf, "-", pre)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
@ -147,6 +147,19 @@ func TestParseNuspecMetaData(t *testing.T) {
|
|||
assert.Len(t, deps, 1)
|
||||
assert.Equal(t, dependencyID, deps[0].ID)
|
||||
assert.Equal(t, dependencyVersion, deps[0].Version)
|
||||
|
||||
t.Run("NormalizedVersion", func(t *testing.T) {
|
||||
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>test</id>
|
||||
<version>1.04.5.2.5-rc.1+metadata</version>
|
||||
</metadata>
|
||||
</package>`))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, np)
|
||||
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Symbols Package", func(t *testing.T) {
|
||||
|
|
|
@ -110,32 +110,6 @@ func (q *ChannelQueue) Flush(timeout time.Duration) error {
|
|||
return q.FlushWithContext(ctx)
|
||||
}
|
||||
|
||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
||||
func (q *ChannelQueue) FlushWithContext(ctx context.Context) error {
|
||||
log.Trace("ChannelQueue: %d Flush", q.qid)
|
||||
paused, _ := q.IsPausedIsResumed()
|
||||
for {
|
||||
select {
|
||||
case <-paused:
|
||||
return nil
|
||||
case data, ok := <-q.dataChan:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if unhandled := q.handle(data); unhandled != nil {
|
||||
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
|
||||
}
|
||||
atomic.AddInt64(&q.numInQueue, -1)
|
||||
case <-q.baseCtx.Done():
|
||||
return q.baseCtx.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown processing from this queue
|
||||
func (q *ChannelQueue) Shutdown() {
|
||||
q.lock.Lock()
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"fmt"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
|
@ -168,35 +167,6 @@ func (q *ChannelUniqueQueue) Flush(timeout time.Duration) error {
|
|||
return q.FlushWithContext(ctx)
|
||||
}
|
||||
|
||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
||||
func (q *ChannelUniqueQueue) FlushWithContext(ctx context.Context) error {
|
||||
log.Trace("ChannelUniqueQueue: %d Flush", q.qid)
|
||||
paused, _ := q.IsPausedIsResumed()
|
||||
for {
|
||||
select {
|
||||
case <-paused:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case data, ok := <-q.dataChan:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if unhandled := q.handle(data); unhandled != nil {
|
||||
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
|
||||
}
|
||||
atomic.AddInt64(&q.numInQueue, -1)
|
||||
case <-q.baseCtx.Done():
|
||||
return q.baseCtx.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown processing from this queue
|
||||
func (q *ChannelUniqueQueue) Shutdown() {
|
||||
log.Trace("ChannelUniqueQueue: %s Shutting down", q.name)
|
||||
|
|
|
@ -464,13 +464,43 @@ func (p *WorkerPool) IsEmpty() bool {
|
|||
return atomic.LoadInt64(&p.numInQueue) == 0
|
||||
}
|
||||
|
||||
// contextError returns either ctx.Done(), the base context's error or nil
|
||||
func (p *WorkerPool) contextError(ctx context.Context) error {
|
||||
select {
|
||||
case <-p.baseCtx.Done():
|
||||
return p.baseCtx.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
||||
// NB: The worker will not be registered with the manager.
|
||||
func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
|
||||
log.Trace("WorkerPool: %d Flush", p.qid)
|
||||
paused, _ := p.IsPausedIsResumed()
|
||||
for {
|
||||
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
|
||||
select {
|
||||
case data := <-p.dataChan:
|
||||
case <-paused:
|
||||
// Ensure that even if paused that the cancelled error is still sent
|
||||
return p.contextError(ctx)
|
||||
case <-p.baseCtx.Done():
|
||||
return p.baseCtx.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-paused:
|
||||
return p.contextError(ctx)
|
||||
case data, ok := <-p.dataChan:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if unhandled := p.handle(data); unhandled != nil {
|
||||
log.Error("Unhandled Data whilst flushing queue %d", p.qid)
|
||||
}
|
||||
|
@ -496,6 +526,7 @@ func (p *WorkerPool) doWork(ctx context.Context) {
|
|||
paused, _ := p.IsPausedIsResumed()
|
||||
data := make([]Data, 0, p.batchLength)
|
||||
for {
|
||||
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
|
||||
select {
|
||||
case <-paused:
|
||||
log.Trace("Worker for Queue %d Pausing", p.qid)
|
||||
|
@ -516,8 +547,19 @@ func (p *WorkerPool) doWork(ctx context.Context) {
|
|||
log.Trace("Worker shutting down")
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if len(data) > 0 {
|
||||
log.Trace("Handling: %d data, %v", len(data), data)
|
||||
if unhandled := p.handle(data...); unhandled != nil {
|
||||
log.Error("Unhandled Data in queue %d", p.qid)
|
||||
}
|
||||
atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
|
||||
}
|
||||
log.Trace("Worker shutting down")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-paused:
|
||||
// go back around
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
@ -286,9 +287,36 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
|
|||
return repo, nil
|
||||
}
|
||||
|
||||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
|
||||
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
||||
|
||||
// getDirectorySize returns the disk consumption for a given path
|
||||
func getDirectorySize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
f, err := info.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (f.Mode() & notRegularFileMode) == 0 {
|
||||
size += f.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
|
||||
size, err := util.GetDirectorySize(repo.RepoPath())
|
||||
size, err := getDirectorySize(repo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("updateSize: %w", err)
|
||||
}
|
||||
|
|
|
@ -169,3 +169,13 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.True(t, act.IsPrivate)
|
||||
}
|
||||
|
||||
func TestGetDirectorySize(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo, err := repo_model.GetRepositoryByID(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
size, err := getDirectorySize(repo.RepoPath())
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, size, repo.Size)
|
||||
}
|
||||
|
|
|
@ -13,78 +13,57 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
ini "gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Mailer represents mail service.
|
||||
type Mailer struct {
|
||||
// Mailer
|
||||
Name string
|
||||
From string
|
||||
EnvelopeFrom string
|
||||
OverrideEnvelopeFrom bool `ini:"-"`
|
||||
FromName string
|
||||
FromEmail string
|
||||
SendAsPlainText bool
|
||||
SubjectPrefix string
|
||||
Name string `ini:"NAME"`
|
||||
From string `ini:"FROM"`
|
||||
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
|
||||
OverrideEnvelopeFrom bool `ini:"-"`
|
||||
FromName string `ini:"-"`
|
||||
FromEmail string `ini:"-"`
|
||||
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
|
||||
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
|
||||
|
||||
// SMTP sender
|
||||
Protocol string
|
||||
SMTPAddr string
|
||||
SMTPPort string
|
||||
User, Passwd string
|
||||
EnableHelo bool
|
||||
HeloHostname string
|
||||
ForceTrustServerCert bool
|
||||
UseClientCert bool
|
||||
ClientCertFile string
|
||||
ClientKeyFile string
|
||||
Protocol string `ini:"PROTOCOL"`
|
||||
SMTPAddr string `ini:"SMTP_ADDR"`
|
||||
SMTPPort string `ini:"SMTP_PORT"`
|
||||
User string `ini:"USER"`
|
||||
Passwd string `ini:"PASSWD"`
|
||||
EnableHelo bool `ini:"ENABLE_HELO"`
|
||||
HeloHostname string `ini:"HELO_HOSTNAME"`
|
||||
ForceTrustServerCert bool `ini:"FORCE_TRUST_SERVER_CERT"`
|
||||
UseClientCert bool `ini:"USE_CLIENT_CERT"`
|
||||
ClientCertFile string `ini:"CLIENT_CERT_FILE"`
|
||||
ClientKeyFile string `ini:"CLIENT_KEY_FILE"`
|
||||
|
||||
// Sendmail sender
|
||||
SendmailPath string
|
||||
SendmailArgs []string
|
||||
SendmailTimeout time.Duration
|
||||
SendmailConvertCRLF bool
|
||||
SendmailPath string `ini:"SENDMAIL_PATH"`
|
||||
SendmailArgs []string `ini:"-"`
|
||||
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"`
|
||||
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"`
|
||||
}
|
||||
|
||||
// MailService the global mailer
|
||||
var MailService *Mailer
|
||||
|
||||
func newMailService() {
|
||||
sec := Cfg.Section("mailer")
|
||||
func parseMailerConfig(rootCfg *ini.File) {
|
||||
sec := rootCfg.Section("mailer")
|
||||
// Check mailer setting.
|
||||
if !sec.Key("ENABLED").MustBool() {
|
||||
return
|
||||
}
|
||||
|
||||
MailService = &Mailer{
|
||||
Name: sec.Key("NAME").MustString(AppName),
|
||||
SendAsPlainText: sec.Key("SEND_AS_PLAIN_TEXT").MustBool(false),
|
||||
|
||||
Protocol: sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy"}),
|
||||
SMTPAddr: sec.Key("SMTP_ADDR").String(),
|
||||
SMTPPort: sec.Key("SMTP_PORT").String(),
|
||||
User: sec.Key("USER").String(),
|
||||
Passwd: sec.Key("PASSWD").String(),
|
||||
EnableHelo: sec.Key("ENABLE_HELO").MustBool(true),
|
||||
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
|
||||
ForceTrustServerCert: sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false),
|
||||
UseClientCert: sec.Key("USE_CLIENT_CERT").MustBool(false),
|
||||
ClientCertFile: sec.Key("CLIENT_CERT_FILE").String(),
|
||||
ClientKeyFile: sec.Key("CLIENT_KEY_FILE").String(),
|
||||
SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString(""),
|
||||
|
||||
SendmailPath: sec.Key("SENDMAIL_PATH").MustString("sendmail"),
|
||||
SendmailTimeout: sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
|
||||
SendmailConvertCRLF: sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true),
|
||||
}
|
||||
MailService.From = sec.Key("FROM").MustString(MailService.User)
|
||||
MailService.EnvelopeFrom = sec.Key("ENVELOPE_FROM").MustString("")
|
||||
|
||||
// Handle Deprecations and map on to new configuration
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "MAILER_TYPE", "mailer", "PROTOCOL")
|
||||
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
|
||||
if sec.Key("MAILER_TYPE").String() == "sendmail" {
|
||||
MailService.Protocol = "sendmail"
|
||||
sec.Key("PROTOCOL").MustString("sendmail")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,34 +72,99 @@ func newMailService() {
|
|||
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
|
||||
givenHost := sec.Key("HOST").String()
|
||||
addr, port, err := net.SplitHostPort(givenHost)
|
||||
if err != nil {
|
||||
if err != nil && strings.Contains(err.Error(), "missing port in address") {
|
||||
addr = givenHost
|
||||
} else if err != nil {
|
||||
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
||||
}
|
||||
MailService.SMTPAddr = addr
|
||||
MailService.SMTPPort = port
|
||||
if addr == "" {
|
||||
addr = "127.0.0.1"
|
||||
}
|
||||
sec.Key("SMTP_ADDR").MustString(addr)
|
||||
sec.Key("SMTP_PORT").MustString(port)
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
|
||||
if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
|
||||
if sec.Key("IS_TLS_ENABLED").MustBool() {
|
||||
MailService.Protocol = "smtps"
|
||||
sec.Key("PROTOCOL").MustString("smtps")
|
||||
} else {
|
||||
MailService.Protocol = "smtp+startls"
|
||||
sec.Key("PROTOCOL").MustString("smtp+starttls")
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO")
|
||||
if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
|
||||
sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool())
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT")
|
||||
if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
|
||||
sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool())
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT")
|
||||
if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
|
||||
sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool())
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE")
|
||||
if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
|
||||
sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String())
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE")
|
||||
if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
|
||||
sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String())
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT")
|
||||
if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
|
||||
sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false))
|
||||
}
|
||||
|
||||
if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" {
|
||||
log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0")
|
||||
sec.Key("PROTOCOL").SetValue("smtp+starttls")
|
||||
}
|
||||
|
||||
// Set default values & validate
|
||||
sec.Key("NAME").MustString(AppName)
|
||||
sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"})
|
||||
sec.Key("ENABLE_HELO").MustBool(true)
|
||||
sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false)
|
||||
sec.Key("USE_CLIENT_CERT").MustBool(false)
|
||||
sec.Key("SENDMAIL_PATH").MustString("sendmail")
|
||||
sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute)
|
||||
sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true)
|
||||
sec.Key("FROM").MustString(sec.Key("USER").String())
|
||||
|
||||
// Now map the values on to the MailService
|
||||
MailService = &Mailer{}
|
||||
if err := sec.MapTo(MailService); err != nil {
|
||||
log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
|
||||
}
|
||||
|
||||
// Infer SMTPPort if not set
|
||||
if MailService.SMTPPort == "" {
|
||||
switch MailService.Protocol {
|
||||
case "smtp":
|
||||
MailService.SMTPPort = "25"
|
||||
case "smtps":
|
||||
MailService.SMTPPort = "465"
|
||||
case "smtp+startls":
|
||||
case "smtp+starttls":
|
||||
MailService.SMTPPort = "587"
|
||||
}
|
||||
}
|
||||
|
||||
// Infer Protocol
|
||||
if MailService.Protocol == "" {
|
||||
if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
|
||||
MailService.Protocol = "smtp+unix"
|
||||
|
@ -131,60 +175,38 @@ func newMailService() {
|
|||
case "465":
|
||||
MailService.Protocol = "smtps"
|
||||
case "587":
|
||||
MailService.Protocol = "smtp+startls"
|
||||
MailService.Protocol = "smtp+starttls"
|
||||
default:
|
||||
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
||||
MailService.Protocol = "smtps"
|
||||
if MailService.SMTPPort == "" {
|
||||
MailService.SMTPPort = "465"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we want to warn if users use SMTP on a non-local IP;
|
||||
// we might as well take the opportunity to check that it has an IP at all
|
||||
ips := tryResolveAddr(MailService.SMTPAddr)
|
||||
if MailService.Protocol == "smtp" {
|
||||
for _, ip := range ips {
|
||||
if !ip.IsLoopback() {
|
||||
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
|
||||
break
|
||||
// This check is not needed for sendmail
|
||||
switch MailService.Protocol {
|
||||
case "sendmail":
|
||||
var err error
|
||||
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
|
||||
if err != nil {
|
||||
log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
|
||||
}
|
||||
case "smtp", "smtps", "smtp+starttls", "smtp+unix":
|
||||
ips := tryResolveAddr(MailService.SMTPAddr)
|
||||
if MailService.Protocol == "smtp" {
|
||||
for _, ip := range ips {
|
||||
if !ip.IsLoopback() {
|
||||
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO")
|
||||
if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
|
||||
MailService.EnableHelo = !sec.Key("DISABLE_HELO").MustBool()
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT")
|
||||
if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
|
||||
MailService.ForceTrustServerCert = sec.Key("SKIP_VERIFY").MustBool()
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT")
|
||||
if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
|
||||
MailService.UseClientCert = sec.Key("USE_CLIENT_CERT").MustBool()
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE")
|
||||
if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
|
||||
MailService.ClientCertFile = sec.Key("CERT_FILE").String()
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE")
|
||||
if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
|
||||
MailService.ClientKeyFile = sec.Key("KEY_FILE").String()
|
||||
}
|
||||
|
||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||
deprecatedSetting("mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT")
|
||||
if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
|
||||
MailService.SendAsPlainText = !sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)
|
||||
case "dummy": // just mention and do nothing
|
||||
}
|
||||
|
||||
if MailService.From != "" {
|
||||
|
@ -213,14 +235,6 @@ func newMailService() {
|
|||
MailService.EnvelopeFrom = parsed.Address
|
||||
}
|
||||
|
||||
if MailService.Protocol == "sendmail" {
|
||||
var err error
|
||||
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
|
||||
if err != nil {
|
||||
log.Error("Failed to parse Sendmail args: %s with error %v", CustomConf, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
ini "gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func TestParseMailerConfig(t *testing.T) {
|
||||
iniFile := ini.Empty()
|
||||
kases := map[string]*Mailer{
|
||||
"smtp.mydomain.com": {
|
||||
SMTPAddr: "smtp.mydomain.com",
|
||||
SMTPPort: "465",
|
||||
},
|
||||
"smtp.mydomain.com:123": {
|
||||
SMTPAddr: "smtp.mydomain.com",
|
||||
SMTPPort: "123",
|
||||
},
|
||||
":123": {
|
||||
SMTPAddr: "127.0.0.1",
|
||||
SMTPPort: "123",
|
||||
},
|
||||
}
|
||||
for host, kase := range kases {
|
||||
t.Run(host, func(t *testing.T) {
|
||||
iniFile.DeleteSection("mailer")
|
||||
sec := iniFile.Section("mailer")
|
||||
sec.NewKey("ENABLED", "true")
|
||||
sec.NewKey("HOST", host)
|
||||
|
||||
// Check mailer setting
|
||||
parseMailerConfig(iniFile)
|
||||
|
||||
assert.EqualValues(t, kase.SMTPAddr, MailService.SMTPAddr)
|
||||
assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ func newPictureService() {
|
|||
}
|
||||
|
||||
func GetDefaultDisableGravatar() bool {
|
||||
return !OfflineMode
|
||||
return OfflineMode
|
||||
}
|
||||
|
||||
func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool {
|
||||
|
|
|
@ -83,6 +83,7 @@ var (
|
|||
DefaultMergeMessageOfficialApproversOnly bool
|
||||
PopulateSquashCommentWithCommitMessages bool
|
||||
AddCoCommitterTrailers bool
|
||||
TestConflictingPatchesWithGitApply bool
|
||||
} `ini:"repository.pull-request"`
|
||||
|
||||
// Issue Setting
|
||||
|
@ -205,6 +206,7 @@ var (
|
|||
DefaultMergeMessageOfficialApproversOnly bool
|
||||
PopulateSquashCommentWithCommitMessages bool
|
||||
AddCoCommitterTrailers bool
|
||||
TestConflictingPatchesWithGitApply bool
|
||||
}{
|
||||
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
|
||||
// Same as GitHub. See
|
||||
|
@ -219,6 +221,7 @@ var (
|
|||
DefaultMergeMessageOfficialApproversOnly: true,
|
||||
PopulateSquashCommentWithCommitMessages: false,
|
||||
AddCoCommitterTrailers: true,
|
||||
TestConflictingPatchesWithGitApply: true,
|
||||
},
|
||||
|
||||
// Issue settings
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/generate"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
|
@ -463,6 +464,13 @@ func getAppPath() (string, error) {
|
|||
appPath, err = exec.LookPath(os.Args[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// FIXME: Once we switch to go 1.19 use !errors.Is(err, exec.ErrDot)
|
||||
if !strings.Contains(err.Error(), "cannot run executable found relative to current directory") {
|
||||
return "", err
|
||||
}
|
||||
appPath, err = filepath.Abs(os.Args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -602,7 +610,7 @@ func LoadForTest(extraConfigs ...string) {
|
|||
|
||||
func deprecatedSetting(oldSection, oldKey, newSection, newKey string) {
|
||||
if Cfg.Section(oldSection).HasKey(oldKey) {
|
||||
log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be removed in v1.18.0", oldSection, oldKey, newSection, newKey)
|
||||
log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be removed in v1.19.0", oldSection, oldKey, newSection, newKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -962,6 +970,11 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
|||
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
||||
|
||||
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
|
||||
if InstallLock && InternalToken == "" {
|
||||
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
|
||||
// some users do cluster deployment, they still depend on this auto-generating behavior.
|
||||
generateSaveInternalToken()
|
||||
}
|
||||
|
||||
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")
|
||||
if len(cfgdata) == 0 {
|
||||
|
@ -1026,7 +1039,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
|||
// The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches.
|
||||
// Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly.
|
||||
unsafeAllowRunAsRoot := Cfg.Section("").Key("I_AM_BEING_UNSAFE_RUNNING_AS_ROOT").MustBool(false)
|
||||
RunMode = Cfg.Section("").Key("RUN_MODE").MustString("prod")
|
||||
RunMode = os.Getenv("GITEA_RUN_MODE")
|
||||
if RunMode == "" {
|
||||
RunMode = Cfg.Section("").Key("RUN_MODE").MustString("prod")
|
||||
}
|
||||
IsProd = strings.EqualFold(RunMode, "prod")
|
||||
// Does not check run user when the install lock is off.
|
||||
if InstallLock {
|
||||
|
@ -1150,6 +1166,8 @@ func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
|
|||
return authorizedPrincipalsAllow, true
|
||||
}
|
||||
|
||||
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
|
||||
// If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear.
|
||||
func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
|
||||
// don't allow setting both URI and verbatim string
|
||||
uri := sec.Key(uriKey).String()
|
||||
|
@ -1173,7 +1191,15 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
|
|||
if err != nil {
|
||||
log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err)
|
||||
}
|
||||
return strings.TrimSpace(string(buf))
|
||||
val := strings.TrimSpace(string(buf))
|
||||
if val == "" {
|
||||
// The file shouldn't be empty, otherwise we can not know whether the user has ever set the KEY or KEY_URI
|
||||
// For example: if INTERNAL_TOKEN_URI=file:///empty-file,
|
||||
// Then if the token is re-generated during installation and saved to INTERNAL_TOKEN
|
||||
// Then INTERNAL_TOKEN and INTERNAL_TOKEN_URI both exist, that's a fatal error (they shouldn't)
|
||||
log.Fatal("Failed to read %s (%s): the file is empty", uriKey, tempURI.RequestURI())
|
||||
}
|
||||
return val
|
||||
|
||||
// only file URIs are allowed
|
||||
default:
|
||||
|
@ -1182,6 +1208,19 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
|
|||
}
|
||||
}
|
||||
|
||||
// generateSaveInternalToken generates and saves the internal token to app.ini
|
||||
func generateSaveInternalToken() {
|
||||
token, err := generate.NewInternalToken()
|
||||
if err != nil {
|
||||
log.Fatal("Error generate internal token: %v", err)
|
||||
}
|
||||
|
||||
InternalToken = token
|
||||
CreateOrAppendToCustomConf("security.INTERNAL_TOKEN", func(cfg *ini.File) {
|
||||
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
|
||||
})
|
||||
}
|
||||
|
||||
// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
|
||||
func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
|
||||
parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
|
||||
|
@ -1295,7 +1334,7 @@ func NewServices() {
|
|||
newCacheService()
|
||||
newSessionService()
|
||||
newCORSService()
|
||||
newMailService()
|
||||
parseMailerConfig(Cfg)
|
||||
newRegisterMailService()
|
||||
newNotifyMailService()
|
||||
newProxyService()
|
||||
|
@ -1312,5 +1351,5 @@ func NewServices() {
|
|||
// NewServicesForInstall initializes the services for install
|
||||
func NewServicesForInstall() {
|
||||
newService()
|
||||
newMailService()
|
||||
parseMailerConfig(Cfg)
|
||||
}
|
||||
|
|
|
@ -12,48 +12,62 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// sitemapFileLimit contains the maximum size of a sitemap file
|
||||
const sitemapFileLimit = 50 * 1024 * 1024
|
||||
const (
|
||||
sitemapFileLimit = 50 * 1024 * 1024 // the maximum size of a sitemap file
|
||||
urlsLimit = 50000
|
||||
|
||||
// Url represents a single sitemap entry
|
||||
schemaURL = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
urlsetName = "urlset"
|
||||
sitemapindexName = "sitemapindex"
|
||||
)
|
||||
|
||||
// URL represents a single sitemap entry
|
||||
type URL struct {
|
||||
URL string `xml:"loc"`
|
||||
LastMod *time.Time `xml:"lastmod,omitempty"`
|
||||
}
|
||||
|
||||
// SitemapUrl represents a sitemap
|
||||
// Sitemap represents a sitemap
|
||||
type Sitemap struct {
|
||||
XMLName xml.Name
|
||||
Namespace string `xml:"xmlns,attr"`
|
||||
|
||||
URLs []URL `xml:"url"`
|
||||
URLs []URL `xml:"url"`
|
||||
Sitemaps []URL `xml:"sitemap"`
|
||||
}
|
||||
|
||||
// NewSitemap creates a sitemap
|
||||
func NewSitemap() *Sitemap {
|
||||
return &Sitemap{
|
||||
XMLName: xml.Name{Local: "urlset"},
|
||||
Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
XMLName: xml.Name{Local: urlsetName},
|
||||
Namespace: schemaURL,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSitemap creates a sitemap index.
|
||||
// NewSitemapIndex creates a sitemap index.
|
||||
func NewSitemapIndex() *Sitemap {
|
||||
return &Sitemap{
|
||||
XMLName: xml.Name{Local: "sitemapindex"},
|
||||
Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
XMLName: xml.Name{Local: sitemapindexName},
|
||||
Namespace: schemaURL,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a URL to the sitemap
|
||||
func (s *Sitemap) Add(u URL) {
|
||||
s.URLs = append(s.URLs, u)
|
||||
if s.XMLName.Local == sitemapindexName {
|
||||
s.Sitemaps = append(s.Sitemaps, u)
|
||||
} else {
|
||||
s.URLs = append(s.URLs, u)
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the sitemap to a response
|
||||
// WriteTo writes the sitemap to a response
|
||||
func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
||||
if len(s.URLs) > 50000 {
|
||||
return 0, fmt.Errorf("The sitemap contains too many URLs: %d", len(s.URLs))
|
||||
if l := len(s.URLs); l > urlsLimit {
|
||||
return 0, fmt.Errorf("The sitemap contains %d URLs, but only %d are allowed", l, urlsLimit)
|
||||
}
|
||||
if l := len(s.Sitemaps); l > urlsLimit {
|
||||
return 0, fmt.Errorf("The sitemap contains %d sub-sitemaps, but only %d are allowed", l, urlsLimit)
|
||||
}
|
||||
buf := bytes.NewBufferString(xml.Header)
|
||||
if err := xml.NewEncoder(buf).Encode(s); err != nil {
|
||||
|
@ -63,7 +77,7 @@ func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
if buf.Len() > sitemapFileLimit {
|
||||
return 0, fmt.Errorf("The sitemap is too big: %d", buf.Len())
|
||||
return 0, fmt.Errorf("The sitemap has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit)
|
||||
}
|
||||
return buf.WriteTo(w)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package sitemap
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -15,63 +14,154 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOk(t *testing.T) {
|
||||
testReal := func(s *Sitemap, name string, urls []URL, expected string) {
|
||||
for _, url := range urls {
|
||||
s.Add(url)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := s.WriteTo(buf)
|
||||
assert.NoError(t, nil, err)
|
||||
assert.Equal(t, xml.Header+"<"+name+" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"+expected+"</"+name+">\n", buf.String())
|
||||
}
|
||||
test := func(urls []URL, expected string) {
|
||||
testReal(NewSitemap(), "urlset", urls, expected)
|
||||
testReal(NewSitemapIndex(), "sitemapindex", urls, expected)
|
||||
}
|
||||
|
||||
func TestNewSitemap(t *testing.T) {
|
||||
ts := time.Unix(1651322008, 0).UTC()
|
||||
|
||||
test(
|
||||
[]URL{},
|
||||
"",
|
||||
)
|
||||
test(
|
||||
[]URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
tests := []struct {
|
||||
name string
|
||||
urls []URL
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
urls: []URL{},
|
||||
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"" +
|
||||
"</urlset>\n",
|
||||
},
|
||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>",
|
||||
)
|
||||
test(
|
||||
[]URL{
|
||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||
{
|
||||
name: "regular",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
},
|
||||
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
|
||||
"</urlset>\n",
|
||||
},
|
||||
"<url><loc>https://gitea.io/test2</loc></url>",
|
||||
)
|
||||
test(
|
||||
[]URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||
{
|
||||
name: "without lastmod",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1"},
|
||||
},
|
||||
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<url><loc>https://gitea.io/test1</loc></url>" +
|
||||
"</urlset>\n",
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||
},
|
||||
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
|
||||
"<url><loc>https://gitea.io/test2</loc></url>" +
|
||||
"</urlset>\n",
|
||||
},
|
||||
{
|
||||
name: "too many urls",
|
||||
urls: make([]URL, 50001),
|
||||
wantErr: "The sitemap contains 50001 URLs, but only 50000 are allowed",
|
||||
},
|
||||
{
|
||||
name: "too big file",
|
||||
urls: []URL{
|
||||
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||
},
|
||||
wantErr: "The sitemap has 52428932 bytes, but only 52428800 are allowed",
|
||||
},
|
||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>"+
|
||||
"<url><loc>https://gitea.io/test2</loc></url>",
|
||||
)
|
||||
}
|
||||
|
||||
func TestTooManyURLs(t *testing.T) {
|
||||
s := NewSitemap()
|
||||
for i := 0; i < 50001; i++ {
|
||||
s.Add(URL{URL: fmt.Sprintf("https://gitea.io/test%d", i)})
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := s.WriteTo(buf)
|
||||
assert.EqualError(t, err, "The sitemap contains too many URLs: 50001")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := NewSitemap()
|
||||
for _, url := range tt.urls {
|
||||
s.Add(url)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := s.WriteTo(buf)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, tt.want, buf.String(), "NewSitemap()")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSitemapTooBig(t *testing.T) {
|
||||
s := NewSitemap()
|
||||
s.Add(URL{URL: strings.Repeat("b", sitemapFileLimit)})
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := s.WriteTo(buf)
|
||||
assert.EqualError(t, err, "The sitemap is too big: 52428931")
|
||||
func TestNewSitemapIndex(t *testing.T) {
|
||||
ts := time.Unix(1651322008, 0).UTC()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
urls []URL
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
urls: []URL{},
|
||||
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"" +
|
||||
"</sitemapindex>\n",
|
||||
},
|
||||
{
|
||||
name: "regular",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
},
|
||||
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
|
||||
"</sitemapindex>\n",
|
||||
},
|
||||
{
|
||||
name: "without lastmod",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1"},
|
||||
},
|
||||
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<sitemap><loc>https://gitea.io/test1</loc></sitemap>" +
|
||||
"</sitemapindex>\n",
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
urls: []URL{
|
||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||
},
|
||||
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
|
||||
"<sitemap><loc>https://gitea.io/test2</loc></sitemap>" +
|
||||
"</sitemapindex>\n",
|
||||
},
|
||||
{
|
||||
name: "too many sitemaps",
|
||||
urls: make([]URL, 50001),
|
||||
wantErr: "The sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
|
||||
},
|
||||
{
|
||||
name: "too big file",
|
||||
urls: []URL{
|
||||
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||
},
|
||||
wantErr: "The sitemap has 52428952 bytes, but only 52428800 are allowed",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := NewSitemapIndex()
|
||||
for _, url := range tt.urls {
|
||||
s.Add(url)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := s.WriteTo(buf)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,8 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
|
|||
return 0, err
|
||||
}
|
||||
// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
|
||||
if err := util.ApplyUmask(p, os.ModePerm); err != nil {
|
||||
// but we don't want to make these files executable - so ensure that we mask out the executable bits
|
||||
if err := util.ApplyUmask(p, os.ModePerm&0o666); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// StateType issue state type
|
||||
|
@ -143,14 +147,47 @@ type IssueFormField struct {
|
|||
// IssueTemplate represents an issue template for a repository
|
||||
// swagger:model
|
||||
type IssueTemplate struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
|
||||
Labels []string `json:"labels" yaml:"labels"`
|
||||
Ref string `json:"ref" yaml:"ref"`
|
||||
Content string `json:"content" yaml:"-"`
|
||||
Fields []*IssueFormField `json:"body" yaml:"body"`
|
||||
FileName string `json:"file_name" yaml:"-"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
|
||||
Labels IssueTemplateLabels `json:"labels" yaml:"labels"`
|
||||
Ref string `json:"ref" yaml:"ref"`
|
||||
Content string `json:"content" yaml:"-"`
|
||||
Fields []*IssueFormField `json:"body" yaml:"body"`
|
||||
FileName string `json:"file_name" yaml:"-"`
|
||||
}
|
||||
|
||||
type IssueTemplateLabels []string
|
||||
|
||||
func (l *IssueTemplateLabels) UnmarshalYAML(value *yaml.Node) error {
|
||||
var labels []string
|
||||
if value.IsZero() {
|
||||
*l = labels
|
||||
return nil
|
||||
}
|
||||
switch value.Kind {
|
||||
case yaml.ScalarNode:
|
||||
str := ""
|
||||
err := value.Decode(&str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range strings.Split(str, ",") {
|
||||
if v = strings.TrimSpace(v); v == "" {
|
||||
continue
|
||||
}
|
||||
labels = append(labels, v)
|
||||
}
|
||||
*l = labels
|
||||
return nil
|
||||
case yaml.SequenceNode:
|
||||
if err := value.Decode(&labels); err != nil {
|
||||
return err
|
||||
}
|
||||
*l = labels
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("line %d: cannot unmarshal %s into IssueTemplateLabels", value.Line, value.ShortTag())
|
||||
}
|
||||
|
||||
// IssueTemplateType defines issue template type
|
||||
|
@ -163,14 +200,14 @@ const (
|
|||
|
||||
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
|
||||
func (it IssueTemplate) Type() IssueTemplateType {
|
||||
if it.Name == "config.yaml" || it.Name == "config.yml" {
|
||||
if base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" {
|
||||
// ignore config.yaml which is a special configuration file
|
||||
return ""
|
||||
}
|
||||
if ext := filepath.Ext(it.FileName); ext == ".md" {
|
||||
if ext := path.Ext(it.FileName); ext == ".md" {
|
||||
return IssueTemplateTypeMarkdown
|
||||
} else if ext == ".yaml" || ext == ".yml" {
|
||||
return "yaml"
|
||||
return IssueTemplateTypeYaml
|
||||
}
|
||||
return IssueTemplateTypeYaml
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// 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 structs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestIssueTemplate_Type(t *testing.T) {
|
||||
tests := []struct {
|
||||
fileName string
|
||||
want IssueTemplateType
|
||||
}{
|
||||
{
|
||||
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.yaml",
|
||||
want: IssueTemplateTypeYaml,
|
||||
},
|
||||
{
|
||||
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.md",
|
||||
want: IssueTemplateTypeMarkdown,
|
||||
},
|
||||
{
|
||||
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.txt",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
fileName: ".gitea/ISSUE_TEMPLATE/config.yaml",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.fileName, func(t *testing.T) {
|
||||
it := IssueTemplate{
|
||||
FileName: tt.fileName,
|
||||
}
|
||||
assert.Equal(t, tt.want, it.Type())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueTemplateLabels_UnmarshalYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
tmpl *IssueTemplate
|
||||
want *IssueTemplate
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "array",
|
||||
content: `labels: ["a", "b", "c"]`,
|
||||
tmpl: &IssueTemplate{
|
||||
Labels: []string{"should_be_overwrote"},
|
||||
},
|
||||
want: &IssueTemplate{
|
||||
Labels: []string{"a", "b", "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
content: `labels: "a,b,c"`,
|
||||
tmpl: &IssueTemplate{
|
||||
Labels: []string{"should_be_overwrote"},
|
||||
},
|
||||
want: &IssueTemplate{
|
||||
Labels: []string{"a", "b", "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
content: `labels:`,
|
||||
tmpl: &IssueTemplate{
|
||||
Labels: []string{"should_be_overwrote"},
|
||||
},
|
||||
want: &IssueTemplate{
|
||||
Labels: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
content: `
|
||||
labels:
|
||||
a: aa
|
||||
b: bb
|
||||
`,
|
||||
tmpl: &IssueTemplate{},
|
||||
wantErr: "line 3: cannot unmarshal !!map into IssueTemplateLabels",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(tt.content), tt.tmpl)
|
||||
if tt.wantErr != "" {
|
||||
assert.EqualError(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, tt.tmpl)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ type CreatePushMirrorOption struct {
|
|||
RemoteUsername string `json:"remote_username"`
|
||||
RemotePassword string `json:"remote_password"`
|
||||
Interval string `json:"interval"`
|
||||
SyncOnCommit bool `json:"sync_on_commit"`
|
||||
}
|
||||
|
||||
// PushMirror represents information of a push mirror
|
||||
|
@ -22,4 +23,5 @@ type PushMirror struct {
|
|||
LastUpdateUnix string `json:"last_update"`
|
||||
LastError string `json:"last_error"`
|
||||
Interval string `json:"interval"`
|
||||
SyncOnCommit bool `json:"sync_on_commit"`
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ package system
|
|||
|
||||
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
|
||||
type RuntimeState struct {
|
||||
LastAppPath string `json:"last_app_path"`
|
||||
LastAppPath string `json:"last_app_path"`
|
||||
LastCustomConf string `json:"last_custom_conf"`
|
||||
}
|
||||
|
||||
// Name returns the item name
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// 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 system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
)
|
||||
|
||||
func genKey(key string) string {
|
||||
return "system.setting." + key
|
||||
}
|
||||
|
||||
// GetSetting returns the setting value via the key
|
||||
func GetSetting(key string) (string, error) {
|
||||
return cache.GetString(genKey(key), func() (string, error) {
|
||||
res, err := system.GetSetting(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetSettingBool return bool value of setting,
|
||||
// none existing keys and errors are ignored and result in false
|
||||
func GetSettingBool(key string) bool {
|
||||
s, _ := GetSetting(key)
|
||||
b, _ := strconv.ParseBool(s)
|
||||
return b
|
||||
}
|
||||
|
||||
// SetSetting sets the setting value
|
||||
func SetSetting(key, value string, version int) error {
|
||||
cache.Remove(genKey(key))
|
||||
|
||||
return system.SetSetting(&system.Setting{
|
||||
SettingKey: key,
|
||||
SettingValue: value,
|
||||
Version: version,
|
||||
})
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// 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 system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
)
|
||||
|
||||
func genUserKey(userID int64, key string) string {
|
||||
return fmt.Sprintf("user_%d.setting.%s", userID, key)
|
||||
}
|
||||
|
||||
// GetUserSetting returns the user setting value via the key
|
||||
func GetUserSetting(userID int64, key string) (string, error) {
|
||||
return cache.GetString(genUserKey(userID, key), func() (string, error) {
|
||||
res, err := user.GetSetting(userID, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
})
|
||||
}
|
||||
|
||||
// SetUserSetting sets the user setting value
|
||||
func SetUserSetting(userID int64, key, value string) error {
|
||||
cache.Remove(genUserKey(userID, key))
|
||||
|
||||
return user.SetUserSetting(userID, key, value)
|
||||
}
|
|
@ -42,7 +42,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
system_module "code.gitea.io/gitea/modules/system"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
|
@ -87,7 +86,7 @@ func NewFuncMap() []template.FuncMap {
|
|||
return setting.AssetVersion
|
||||
},
|
||||
"DisableGravatar": func() bool {
|
||||
return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||
return system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||
},
|
||||
"DefaultShowFullName": func() bool {
|
||||
return setting.UI.DefaultShowFullName
|
||||
|
@ -647,7 +646,7 @@ func SVG(icon string, others ...interface{}) template.HTML {
|
|||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
func Avatar(item interface{}, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar vm", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
switch t := item.(type) {
|
||||
case *user_model.User:
|
||||
|
@ -678,7 +677,7 @@ func AvatarByAction(action *activities_model.Action, others ...interface{}) temp
|
|||
|
||||
// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
|
||||
func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
src := repo.RelAvatarLink()
|
||||
if src != "" {
|
||||
|
@ -689,7 +688,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
|
|||
|
||||
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
|
||||
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
|
||||
|
||||
if src != "" {
|
||||
|
|
|
@ -76,8 +76,15 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
|||
compilingTemplates = false
|
||||
if !setting.IsProd {
|
||||
watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{
|
||||
PathsCallback: walkTemplateFiles,
|
||||
BetweenCallback: renderer.CompileTemplates,
|
||||
PathsCallback: walkTemplateFiles,
|
||||
BetweenCallback: func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("PANIC: %v\n%s", err, log.Stack(2))
|
||||
}
|
||||
}()
|
||||
renderer.CompileTemplates()
|
||||
},
|
||||
})
|
||||
}
|
||||
return context.WithValue(ctx, rendererKey, renderer), renderer
|
||||
|
|
|
@ -13,8 +13,13 @@ import (
|
|||
// TimeStamp defines a timestamp
|
||||
type TimeStamp int64
|
||||
|
||||
// mock is NOT concurrency-safe!!
|
||||
var mock time.Time
|
||||
var (
|
||||
// mock is NOT concurrency-safe!!
|
||||
mock time.Time
|
||||
|
||||
// Used for IsZero, to check if timestamp is the zero time instant.
|
||||
timeZeroUnix = time.Time{}.Unix()
|
||||
)
|
||||
|
||||
// Set sets the time to a mocked time.Time
|
||||
func Set(now time.Time) {
|
||||
|
@ -103,5 +108,5 @@ func (ts TimeStamp) FormatDate() string {
|
|||
|
||||
// IsZero is zero time
|
||||
func (ts TimeStamp) IsZero() bool {
|
||||
return ts.AsTimeInLocation(time.Local).IsZero()
|
||||
return int64(ts) == 0 || int64(ts) == timeZeroUnix
|
||||
}
|
||||
|
|
|
@ -23,20 +23,6 @@ func EnsureAbsolutePath(path, absoluteBase string) string {
|
|||
return filepath.Join(absoluteBase, path)
|
||||
}
|
||||
|
||||
const notRegularFileMode os.FileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
||||
|
||||
// GetDirectorySize returns the disk consumption for a given path
|
||||
func GetDirectorySize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if info != nil && (info.Mode()¬RegularFileMode) == 0 {
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// IsDir returns true if given path is a directory,
|
||||
// or returns false when it's a file or does not exist.
|
||||
func IsDir(dir string) (bool, error) {
|
||||
|
|
|
@ -497,6 +497,7 @@ team_not_exist = The team does not exist.
|
|||
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
|
||||
cannot_add_org_to_team = An organization cannot be added as a team member.
|
||||
duplicate_invite_to_team = The user was already invited as a team member.
|
||||
organization_leave_success = You have successfully left the organization %s.
|
||||
|
||||
invalid_ssh_key = Can not verify your SSH key: %s
|
||||
invalid_gpg_key = Can not verify your GPG key: %s
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/packages/composer"
|
||||
|
@ -57,7 +58,13 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||
|
||||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
var err error
|
||||
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
if err != nil {
|
||||
log.Error("Verify: %v", err)
|
||||
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
||||
return
|
||||
}
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
|
@ -179,6 +186,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||
r.Group("/maven", func() {
|
||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
|
||||
r.Get("/*", maven.DownloadPackageFile)
|
||||
r.Head("/*", maven.ProvidePackageFileHeader)
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/nuget", func() {
|
||||
r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
|
||||
|
@ -316,7 +324,13 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
|
|||
|
||||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
var err error
|
||||
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
if err != nil {
|
||||
log.Error("Failed to verify user: %v", err)
|
||||
ctx.Error(http.StatusUnauthorized, "Verify")
|
||||
return
|
||||
}
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
|
|
|
@ -184,7 +184,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
|
|
|
@ -20,22 +20,22 @@ func (a *Auth) Name() string {
|
|||
}
|
||||
|
||||
// Verify extracts the user from the Bearer token
|
||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) *user_model.User {
|
||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
||||
uid, err := packages.ParseAuthorizationToken(req)
|
||||
if err != nil {
|
||||
log.Trace("ParseAuthorizationToken: %v", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uid == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u
|
||||
return u, nil
|
||||
}
|
||||
|
|
|
@ -473,7 +473,10 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
|
|||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||
Filename: pf.Name,
|
||||
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteRecipeV1 deletes the requested recipe(s)
|
||||
|
|
|
@ -21,25 +21,25 @@ func (a *Auth) Name() string {
|
|||
|
||||
// Verify extracts the user from the Bearer token
|
||||
// If it's an anonymous session a ghost user is returned
|
||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) *user_model.User {
|
||||
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
|
||||
uid, err := packages.ParseAuthorizationToken(req)
|
||||
if err != nil {
|
||||
log.Trace("ParseAuthorizationToken: %v", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uid == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
if uid == -1 {
|
||||
return user_model.NewGhostUser()
|
||||
return user_model.NewGhostUser(), nil
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
log.Error("GetUserByID: %v", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u
|
||||
return u, nil
|
||||
}
|
||||
|
|
|
@ -7,8 +7,11 @@ package container
|
|||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
|
@ -16,9 +19,12 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
var uploadVersionMutex sync.Mutex
|
||||
|
||||
// saveAsPackageBlob creates a package blob from an upload
|
||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
|
||||
|
@ -28,6 +34,65 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||
|
||||
contentStore := packages_module.NewContentStore()
|
||||
|
||||
uploadVersion, err := getOrCreateUploadVersion(pi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.WithTx(func(ctx context.Context) error {
|
||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||
if err != nil {
|
||||
log.Error("Error inserting package blob: %v", err)
|
||||
return err
|
||||
}
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
if exists {
|
||||
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||
log.Error("Error saving package blob in content store: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return createFileForBlob(ctx, uploadVersion, pb)
|
||||
})
|
||||
if err != nil {
|
||||
if !exists {
|
||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||
log.Error("Error deleting package blob from content store: %v", err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// mountBlob mounts the specific blob to a different package
|
||||
func mountBlob(pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
|
||||
uploadVersion, err := getOrCreateUploadVersion(pi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.WithTx(func(ctx context.Context) error {
|
||||
return createFileForBlob(ctx, uploadVersion, pb)
|
||||
})
|
||||
}
|
||||
|
||||
func getOrCreateUploadVersion(pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
||||
var uploadVersion *packages_model.PackageVersion
|
||||
|
||||
// FIXME: Replace usage of mutex with database transaction
|
||||
// https://github.com/go-gitea/gitea/pull/21862
|
||||
uploadVersionMutex.Lock()
|
||||
err := db.WithTx(func(ctx context.Context) error {
|
||||
created := true
|
||||
p := &packages_model.Package{
|
||||
|
@ -68,52 +133,40 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||
}
|
||||
}
|
||||
|
||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||
if err != nil {
|
||||
log.Error("Error inserting package blob: %v", err)
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||
log.Error("Error saving package blob in content store: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||
|
||||
pf := &packages_model.PackageFile{
|
||||
VersionID: pv.ID,
|
||||
BlobID: pb.ID,
|
||||
Name: filename,
|
||||
LowerName: filename,
|
||||
CompositeKey: packages_model.EmptyFileKey,
|
||||
}
|
||||
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
||||
if err == packages_model.ErrDuplicatePackageFile {
|
||||
return nil
|
||||
}
|
||||
log.Error("Error inserting package file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
|
||||
log.Error("Error setting package file property: %v", err)
|
||||
return err
|
||||
}
|
||||
uploadVersion = pv
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if !exists {
|
||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||
log.Error("Error deleting package blob from content store: %v", err)
|
||||
}
|
||||
uploadVersionMutex.Unlock()
|
||||
|
||||
return uploadVersion, err
|
||||
}
|
||||
|
||||
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
|
||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||
|
||||
pf := &packages_model.PackageFile{
|
||||
VersionID: pv.ID,
|
||||
BlobID: pb.ID,
|
||||
Name: filename,
|
||||
LowerName: filename,
|
||||
CompositeKey: packages_model.EmptyFileKey,
|
||||
}
|
||||
var err error
|
||||
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
||||
if err == packages_model.ErrDuplicatePackageFile {
|
||||
return nil
|
||||
}
|
||||
return nil, err
|
||||
log.Error("Error inserting package file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
|
||||
log.Error("Error setting package file property: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteBlob(ownerID int64, image, digest string) error {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue