mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-13 02:02:53 +09:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53db977e7e | ||
|
|
4fdd4fb2c4 | ||
|
|
900e158064 | ||
|
|
e9bc2c77c3 | ||
|
|
9b4da56963 | ||
|
|
5583eaa904 | ||
|
|
2a5e7f8f92 | ||
|
|
d2777444d9 | ||
|
|
198342efe4 | ||
|
|
f7258aa42b | ||
|
|
9a0a4086e2 | ||
|
|
145e11bc39 | ||
|
|
72524adf3f | ||
|
|
2d4083f03c | ||
|
|
56bded9d8d | ||
|
|
e88218f4be | ||
|
|
4297aced93 | ||
|
|
dd2343d01f | ||
|
|
9e49270676 | ||
|
|
194b780cd7 | ||
|
|
1409b348c6 | ||
|
|
c36a1bc766 | ||
|
|
079ef56824 | ||
|
|
b54c064f89 | ||
|
|
c0ca9c612b | ||
|
|
e39bb2d05a | ||
|
|
ac54331549 | ||
|
|
35fc9ad984 | ||
|
|
6e4ba04843 | ||
|
|
09794b4259 | ||
|
|
757b49ec5e | ||
|
|
9819a47717 | ||
|
|
c7770fa502 | ||
|
|
da956b863b | ||
|
|
888384a631 | ||
|
|
cddceb9dca | ||
|
|
b56d269cf8 | ||
|
|
ff4e292b3f | ||
|
|
9ba4ef93ff | ||
|
|
9bccc60cf5 | ||
|
|
16772ffde3 | ||
|
|
c844c4ff88 | ||
|
|
f4ec03a4e5 | ||
|
|
b2369830bb | ||
|
|
ef08998bf6 | ||
|
|
7a004ad7eb | ||
|
|
af8b2250c4 | ||
|
|
8917af8701 | ||
|
|
0d25292fbc | ||
|
|
ac409fcfba | ||
|
|
df512f77b7 | ||
|
|
e4bf9cad1e | ||
|
|
169eeee101 | ||
|
|
3aacc9b4ac | ||
|
|
87d05d376d | ||
|
|
b9dcf991b9 | ||
|
|
a2a42cd5de | ||
|
|
805a14cc91 | ||
|
|
69a54545a8 | ||
|
|
e054f80fe0 | ||
|
|
89d52922d0 | ||
|
|
3a0d000b94 | ||
|
|
fd4e7447e7 | ||
|
|
7a8e34b255 | ||
|
|
e4a10f8c78 | ||
|
|
6dba648e5d | ||
|
|
4d39fd8aae | ||
|
|
4869f9c3c8 | ||
|
|
79275d9db4 |
@@ -5,6 +5,6 @@ tmp_dir = ".air"
|
|||||||
cmd = "make backend"
|
cmd = "make backend"
|
||||||
bin = "gitea"
|
bin = "gitea"
|
||||||
include_ext = ["go", "tmpl"]
|
include_ext = ["go", "tmpl"]
|
||||||
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
|
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", "templates"]
|
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||||
|
|||||||
@@ -941,7 +941,8 @@ steps:
|
|||||||
image: plugins/hugo:latest
|
image: plugins/hugo:latest
|
||||||
pull: always
|
pull: always
|
||||||
commands:
|
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
|
- cd docs
|
||||||
- make trans-copy clean build
|
- make trans-copy clean build
|
||||||
|
|
||||||
|
|||||||
250
CHANGELOG.md
250
CHANGELOG.md
@@ -4,6 +4,256 @@ 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
|
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).
|
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||||
|
|
||||||
|
## [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
|
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ EXPOSE 2222 3000
|
|||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
bash \
|
bash \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
dumb-init \
|
||||||
gettext \
|
gettext \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
@@ -68,6 +69,6 @@ ENV HOME "/var/lib/gitea/git"
|
|||||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||||
WORKDIR /var/lib/gitea
|
WORKDIR /var/lib/gitea
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD []
|
CMD []
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -358,7 +358,7 @@ watch-frontend: node-check node_modules
|
|||||||
|
|
||||||
.PHONY: watch-backend
|
.PHONY: watch-backend
|
||||||
watch-backend: go-check
|
watch-backend: go-check
|
||||||
$(GO) run $(AIR_PACKAGE) -c .air.toml
|
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-frontend test-backend
|
test: test-frontend test-backend
|
||||||
|
|||||||
@@ -413,9 +413,9 @@ var (
|
|||||||
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
|
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "addr",
|
Name: "host",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "SMTP Addr",
|
Usage: "SMTP Host",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "port",
|
Name: "port",
|
||||||
@@ -955,8 +955,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
|||||||
}
|
}
|
||||||
conf.Auth = c.String("auth-type")
|
conf.Auth = c.String("auth-type")
|
||||||
}
|
}
|
||||||
if c.IsSet("addr") {
|
if c.IsSet("host") {
|
||||||
conf.Addr = c.String("addr")
|
conf.Host = c.String("host")
|
||||||
}
|
}
|
||||||
if c.IsSet("port") {
|
if c.IsSet("port") {
|
||||||
conf.Port = c.Int("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-authored-by and co-committed-by trailers if committer does not match author
|
||||||
;ADD_CO_COMMITTER_TRAILERS = true
|
;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
|
;; Prefix displayed before subject in mail
|
||||||
;SUBJECT_PREFIX =
|
;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.
|
;; - 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.
|
;; - 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,
|
;; 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.
|
- `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.
|
- `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.
|
- `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`)
|
### 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)
|
[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.
|
- `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.
|
- 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.
|
- **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.
|
- **dummy** Send email messages to the log as a testing phase.
|
||||||
|
|||||||
32
go.mod
32
go.mod
@@ -15,8 +15,8 @@ require (
|
|||||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/alecthomas/chroma/v2 v2.3.0
|
github.com/alecthomas/chroma/v2 v2.4.0
|
||||||
github.com/blevesearch/bleve/v2 v2.3.4
|
github.com/blevesearch/bleve/v2 v2.3.5
|
||||||
github.com/buildkite/terminal-to-html/v3 v3.7.0
|
github.com/buildkite/terminal-to-html/v3 v3.7.0
|
||||||
github.com/caddyserver/certmagic v0.17.2
|
github.com/caddyserver/certmagic v0.17.2
|
||||||
github.com/chi-middleware/proxy v1.1.1
|
github.com/chi-middleware/proxy v1.1.1
|
||||||
@@ -94,11 +94,11 @@ require (
|
|||||||
github.com/yuin/goldmark-meta v1.1.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
go.jolheiser.com/hcaptcha v0.0.4
|
go.jolheiser.com/hcaptcha v0.0.4
|
||||||
go.jolheiser.com/pwn v0.0.3
|
go.jolheiser.com/pwn v0.0.3
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
|
||||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
golang.org/x/net v0.2.0
|
||||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
golang.org/x/sys v0.2.0
|
||||||
golang.org/x/text v0.3.8
|
golang.org/x/text v0.4.0
|
||||||
golang.org/x/tools v0.1.12
|
golang.org/x/tools v0.1.12
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
@@ -129,21 +129,21 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.3.3 // indirect
|
github.com/bits-and-blooms/bitset v1.3.3 // indirect
|
||||||
github.com/blevesearch/bleve_index_api v1.0.3 // indirect
|
github.com/blevesearch/bleve_index_api v1.0.4 // indirect
|
||||||
github.com/blevesearch/geo v0.1.14 // indirect
|
github.com/blevesearch/geo v0.1.15 // indirect
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||||
github.com/blevesearch/mmap-go v1.0.4 // 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/segment v0.9.0 // indirect
|
||||||
github.com/blevesearch/snowballstem 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/upsidedown_store_api v1.0.1 // indirect
|
||||||
github.com/blevesearch/vellum v1.0.8 // indirect
|
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||||
github.com/blevesearch/zapx/v11 v11.3.5 // indirect
|
github.com/blevesearch/zapx/v11 v11.3.6 // indirect
|
||||||
github.com/blevesearch/zapx/v12 v12.3.5 // indirect
|
github.com/blevesearch/zapx/v12 v12.3.6 // indirect
|
||||||
github.com/blevesearch/zapx/v13 v13.3.5 // indirect
|
github.com/blevesearch/zapx/v13 v13.3.6 // indirect
|
||||||
github.com/blevesearch/zapx/v14 v14.3.5 // indirect
|
github.com/blevesearch/zapx/v14 v14.3.6 // indirect
|
||||||
github.com/blevesearch/zapx/v15 v15.3.5 // indirect
|
github.com/blevesearch/zapx/v15 v15.3.6 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // 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/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
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v4.0.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/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.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||||
github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
|
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 h1:58/LJlg/81wfEHd5L9qsHduznOIhyv4qb1yWcSvVq9A=
|
||||||
github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
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=
|
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/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/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/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.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
|
||||||
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
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/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.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
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/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/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.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.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.4/go.mod h1:Ot0zYum8XQRfPcwhae8bZmNyYubynsoMjVvl1jPqL30=
|
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.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/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/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU=
|
||||||
github.com/blevesearch/geo v0.1.14 h1:TTDpJN6l9ck/cUYbXSn4aCElNls0Whe44rcQKsB7EfU=
|
github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||||
github.com/blevesearch/geo v0.1.14/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
|
github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4=
|
||||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
|
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 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
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 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
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.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 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
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.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.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.2/go.mod h1:rvoQXZGq8drq7vXbNeyiRzdEOwZkjkiYGf1822i6CRA=
|
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 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
||||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
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 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
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 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
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.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo=
|
||||||
github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRvNBlNMrEVgY=
|
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.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||||
github.com/blevesearch/vellum v1.0.8/go.mod h1:+cpRi/tqq49xUYSQN2P7A5zNSNrS+MscLeeaZ3J46UA=
|
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.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.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc=
|
||||||
github.com/blevesearch/zapx/v11 v11.3.5/go.mod h1:5UdIa/HRMdeRCiLQOyFESsnqBGiip7vQmYReA9toevU=
|
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.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.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4=
|
||||||
github.com/blevesearch/zapx/v12 v12.3.5/go.mod h1:ANcthYRZQycpbRut/6ArF5gP5HxQyJqiFcuJCBju/ss=
|
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.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.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig=
|
||||||
github.com/blevesearch/zapx/v13 v13.3.5/go.mod h1:FV+dRnScFgKnRDIp08RQL4JhVXt1x2HE3AOzqYa6fjo=
|
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.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.5 h1:hEvVjZaagFCvOUJrlFQ6/Z6Jjy0opM3g7TMEo58TwP4=
|
github.com/blevesearch/zapx/v14 v14.3.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.5/go.mod h1:954A/eKFb+pg/ncIYWLWCKY+mIjReM9FGTGIO2Wu1cU=
|
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.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-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 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
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 h1:4KDlx3vjalrHD/EfsjCpV91HNX3JPaIqRtt83zZ7x+Y=
|
||||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
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.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 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-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=
|
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/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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
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/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.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
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 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
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/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix h1:fKZ9OxEDoJKgM0KBXRbSb5IgKUEXis6C3zEIiMtzzQ0=
|
||||||
|
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
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/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
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-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-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-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.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
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-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-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-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
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-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-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
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-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-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-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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-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-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.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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
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-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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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))
|
return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
|
||||||
case strings.HasPrefix(a.RefName, git.TagPrefix):
|
case strings.HasPrefix(a.RefName, git.TagPrefix):
|
||||||
return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(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
|
return a.GetRepoLink() + "/src/commit/" + a.RefName
|
||||||
default:
|
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.
|
// 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.
|
||||||
|
|||||||
@@ -20,8 +20,12 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
const (
|
||||||
const DefaultAvatarPixelSize = 28
|
// 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)
|
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
|
||||||
type EmailHash struct {
|
type EmailHash struct {
|
||||||
@@ -150,10 +154,11 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
|||||||
return DefaultAvatarLink()
|
return DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
|
||||||
|
enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
|
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||||
emailHash := saveEmailHash(email)
|
emailHash := saveEmailHash(email)
|
||||||
if final {
|
if final {
|
||||||
// for final link, we can spend more time on slow external query
|
// for final link, we can spend more time on slow external query
|
||||||
@@ -171,8 +176,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
|||||||
return urlStr
|
return urlStr
|
||||||
}
|
}
|
||||||
|
|
||||||
disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
||||||
if disableGravatar != nil && !disableGravatar.GetValueBool() {
|
|
||||||
|
disableGravatar := disableGravatarSetting.GetValueBool()
|
||||||
|
if !disableGravatar {
|
||||||
// copy GravatarSourceURL, because we will modify its Path.
|
// copy GravatarSourceURL, because we will modify its Path.
|
||||||
avatarURLCopy := *system_model.GravatarSourceURL
|
avatarURLCopy := *system_model.GravatarSourceURL
|
||||||
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
|
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceIndex represents a resource index which could be used as issue/release and others
|
// 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")
|
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
|
// SyncMaxResourceIndex sync the max index with the resource
|
||||||
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
|
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
|
||||||
e := GetEngine(ctx)
|
e := GetEngine(ctx)
|
||||||
@@ -61,8 +59,25 @@ func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxInd
|
|||||||
return nil
|
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
|
// 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) {
|
func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
|
||||||
|
if setting.Database.UsePostgreSQL {
|
||||||
|
return postgresGetNextResourceIndex(ctx, tableName, groupID)
|
||||||
|
}
|
||||||
|
|
||||||
e := GetEngine(ctx)
|
e := GetEngine(ctx)
|
||||||
|
|
||||||
// try to update the max_index to next value, and acquire the write-lock for the record
|
// try to update the max_index to next value, and acquire the write-lock for the record
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ package git
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -49,79 +51,67 @@ func init() {
|
|||||||
db.RegisterModel(new(CommitStatusIndex))
|
db.RegisterModel(new(CommitStatusIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
// upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
|
func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||||
func upsertCommitStatusIndex(ctx context.Context, repoID int64, sha string) (err error) {
|
res, err := db.GetEngine(ctx).Query("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
|
||||||
// An atomic UPSERT operation (INSERT/UPDATE) is the only operation
|
"VALUES (?,?,1) ON CONFLICT (repo_id, sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1 RETURNING max_index",
|
||||||
// that ensures that the key is actually locked.
|
repoID, sha)
|
||||||
switch {
|
if err != nil {
|
||||||
case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
|
return 0, err
|
||||||
_, 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")
|
|
||||||
}
|
}
|
||||||
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
|
// GetNextCommitStatusIndex retried 3 times to generate a resource index
|
||||||
func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
|
func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
|
||||||
for i := 0; i < db.MaxDupIndexAttempts; i++ {
|
if setting.Database.UsePostgreSQL {
|
||||||
idx, err := getNextCommitStatusIndex(repoID, sha)
|
return postgresGetCommitStatusIndex(ctx, repoID, sha)
|
||||||
if err == db.ErrResouceOutdated {
|
}
|
||||||
continue
|
|
||||||
}
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return idx, nil
|
|
||||||
}
|
|
||||||
return 0, db.ErrGetResourceIndexFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNextCommitStatusIndex return the next index
|
affected, err = res.RowsAffected()
|
||||||
func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
|
if err != nil {
|
||||||
ctx, commiter, err := db.TxContext()
|
return 0, err
|
||||||
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 {
|
||||||
defer commiter.Close()
|
if errIns == nil {
|
||||||
|
return 0, errors.New("impossible error when GetNextCommitStatusIndex, insert and update both succeeded but no record is updated")
|
||||||
var preIdx int64
|
}
|
||||||
_, err = db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx)
|
return 0, errIns
|
||||||
if err != nil {
|
}
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := upsertCommitStatusIndex(ctx, repoID, sha); err != nil {
|
// now, the new index is in database (protected by the transaction and write-lock)
|
||||||
return 0, err
|
var newIdx int64
|
||||||
}
|
has, err := e.SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id=? AND sha=?", repoID, sha).Get(&newIdx)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if !has {
|
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 newIdx, nil
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return curIdx, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (status *CommitStatus) loadAttributes(ctx context.Context) (err error) {
|
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)
|
return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next Status Index
|
if _, err := git.NewIDFromString(opts.SHA); err != nil {
|
||||||
idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
|
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("generate commit status index failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext()
|
ctx, committer, err := db.TxContext()
|
||||||
@@ -303,6 +291,12 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
|||||||
}
|
}
|
||||||
defer committer.Close()
|
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.Description = strings.TrimSpace(opts.CommitStatus.Description)
|
||||||
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
|
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
|
||||||
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
|
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
|
||||||
@@ -316,7 +310,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
|
|||||||
|
|
||||||
// Insert new CommitStatus
|
// Insert new CommitStatus
|
||||||
if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil {
|
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()
|
return committer.Commit()
|
||||||
|
|||||||
@@ -1010,12 +1010,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.IsPull {
|
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
|
||||||
_, 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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package issues
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"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) {
|
func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
|
||||||
|
// Load the issue
|
||||||
if t.Issue == nil {
|
if t.Issue == nil {
|
||||||
t.Issue, err = GetIssueByID(ctx, t.IssueID)
|
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
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||||
func (tl TrackedTimeList) LoadAttributes() (err error) {
|
|
||||||
for _, t := range tl {
|
|
||||||
if err = t.LoadAttributes(); err != nil {
|
|
||||||
return err
|
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.
|
// FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"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
|
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
|
// Migrate
|
||||||
for i, m := range migrations[v-minDBVersion:] {
|
for i, m := range migrations[v-minDBVersion:] {
|
||||||
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
|
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
|
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||||
// Not SignedUser
|
// If user is nil, it's an anonymous user/request.
|
||||||
if user == nil {
|
// The Ghost user is handled like an anonymous user.
|
||||||
|
if user == nil || user.IsGhost() {
|
||||||
return orgOrUser.Visibility == structs.VisibleTypePublic
|
return orgOrUser.Visibility == structs.VisibleTypePublic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
|||||||
|
|
||||||
sess := db.GetEngine(ctx).
|
sess := db.GetEngine(ctx).
|
||||||
Table("package_version").
|
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").
|
Join("INNER", "package", "package.id = package_version.package_id").
|
||||||
Where(cond)
|
Where(cond)
|
||||||
|
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
// Repository.NumIssues
|
// 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,
|
repoStatsCorrectNumIssues,
|
||||||
"repository count 'num_issues'",
|
"repository count 'num_issues'",
|
||||||
},
|
},
|
||||||
@@ -456,7 +456,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
// Repository.NumPulls
|
// 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,
|
repoStatsCorrectNumPulls,
|
||||||
"repository count 'num_pulls'",
|
"repository count 'num_pulls'",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
@@ -35,6 +36,10 @@ func (s *Setting) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Setting) GetValueBool() bool {
|
func (s *Setting) GetValueBool() bool {
|
||||||
|
if s == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
b, _ := strconv.ParseBool(s.SettingValue)
|
b, _ := strconv.ParseBool(s.SettingValue)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@@ -75,8 +80,8 @@ func IsErrDataExpired(err error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSetting returns specific setting
|
// GetSettingNoCache returns specific setting without using the cache
|
||||||
func GetSetting(key string) (*Setting, error) {
|
func GetSettingNoCache(key string) (*Setting, error) {
|
||||||
v, err := GetSettings([]string{key})
|
v, err := GetSettings([]string{key})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -87,6 +92,24 @@ func GetSetting(key string) (*Setting, error) {
|
|||||||
return v[key], nil
|
return v[key], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSetting returns the setting value via the key
|
||||||
|
func GetSetting(key string) (*Setting, error) {
|
||||||
|
return cache.Get(genSettingCacheKey(key), func() (*Setting, error) {
|
||||||
|
res, err := GetSettingNoCache(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, 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)
|
||||||
|
return s.GetValueBool()
|
||||||
|
}
|
||||||
|
|
||||||
// GetSettings returns specific settings
|
// GetSettings returns specific settings
|
||||||
func GetSettings(keys []string) (map[string]*Setting, error) {
|
func GetSettings(keys []string) (map[string]*Setting, error) {
|
||||||
for i := 0; i < len(keys); i++ {
|
for i := 0; i < len(keys); i++ {
|
||||||
@@ -139,12 +162,13 @@ func GetAllSettings() (AllSettings, error) {
|
|||||||
|
|
||||||
// DeleteSetting deletes a specific setting for a user
|
// DeleteSetting deletes a specific setting for a user
|
||||||
func DeleteSetting(setting *Setting) error {
|
func DeleteSetting(setting *Setting) error {
|
||||||
|
cache.Remove(genSettingCacheKey(setting.SettingKey))
|
||||||
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
|
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSettingNoVersion(key, value string) error {
|
func SetSettingNoVersion(key, value string) error {
|
||||||
s, err := GetSetting(key)
|
s, err := GetSettingNoCache(key)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
return SetSetting(&Setting{
|
return SetSetting(&Setting{
|
||||||
SettingKey: key,
|
SettingKey: key,
|
||||||
@@ -160,9 +184,13 @@ func SetSettingNoVersion(key, value string) error {
|
|||||||
|
|
||||||
// SetSetting updates a users' setting for a specific key
|
// SetSetting updates a users' setting for a specific key
|
||||||
func SetSetting(setting *Setting) error {
|
func SetSetting(setting *Setting) error {
|
||||||
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
|
_, err := cache.Set(genSettingCacheKey(setting.SettingKey), func() (*Setting, error) {
|
||||||
|
return setting, upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.Version++
|
setting.Version++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -213,7 +241,7 @@ var (
|
|||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
var disableGravatar bool
|
var disableGravatar bool
|
||||||
disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
|
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
disableGravatar = setting.GetDefaultDisableGravatar()
|
disableGravatar = setting.GetDefaultDisableGravatar()
|
||||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||||
@@ -224,7 +252,7 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var enableFederatedAvatar bool
|
var enableFederatedAvatar bool
|
||||||
enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
|
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||||
|
|||||||
@@ -9,3 +9,8 @@ const (
|
|||||||
KeyPictureDisableGravatar = "picture.disable_gravatar"
|
KeyPictureDisableGravatar = "picture.disable_gravatar"
|
||||||
KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar"
|
KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// genSettingCacheKey returns the cache key for some configuration
|
||||||
|
func genSettingCacheKey(key string) string {
|
||||||
|
return "system.setting." + key
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
|||||||
&user_model.UserBadge{UserID: u.ID},
|
&user_model.UserBadge{UserID: u.ID},
|
||||||
&pull_model.AutoMerge{DoerID: u.ID},
|
&pull_model.AutoMerge{DoerID: u.ID},
|
||||||
&pull_model.ReviewState{UserID: u.ID},
|
&pull_model.ReviewState{UserID: u.ID},
|
||||||
|
&user_model.Redirect{RedirectUserID: u.ID},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("deleteBeans: %w", err)
|
return fmt.Errorf("deleteBeans: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,11 +68,9 @@ func (u *User) AvatarLinkWithSize(size int) string {
|
|||||||
useLocalAvatar := false
|
useLocalAvatar := false
|
||||||
autoGenerateAvatar := false
|
autoGenerateAvatar := false
|
||||||
|
|
||||||
var disableGravatar bool
|
|
||||||
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
||||||
if disableGravatarSetting != nil {
|
|
||||||
disableGravatar = disableGravatarSetting.GetValueBool()
|
disableGravatar := disableGravatarSetting.GetValueBool()
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case u.UseCustomAvatar:
|
case u.UseCustomAvatar:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@@ -47,9 +48,25 @@ func IsErrUserSettingIsNotExist(err error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSetting returns specific setting
|
// 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) (*Setting, error) {
|
func GetSetting(uid int64, key string) (*Setting, error) {
|
||||||
v, err := GetUserSettings(uid, []string{key})
|
return cache.Get(genSettingCacheKey(uid, key), func() (*Setting, error) {
|
||||||
|
res, err := GetSettingNoCache(uid, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -59,8 +76,8 @@ func GetSetting(uid int64, key string) (*Setting, error) {
|
|||||||
return v[key], nil
|
return v[key], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserSettings returns specific settings from user
|
// GetSettings returns specific settings from user
|
||||||
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
|
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
|
||||||
settings := make([]*Setting, 0, len(keys))
|
settings := make([]*Setting, 0, len(keys))
|
||||||
if err := db.GetEngine(db.DefaultContext).
|
if err := db.GetEngine(db.DefaultContext).
|
||||||
Where("user_id=?", uid).
|
Where("user_id=?", uid).
|
||||||
@@ -105,6 +122,7 @@ func GetUserSetting(userID int64, key string, def ...string) (string, error) {
|
|||||||
if err := validateUserSettingKey(key); err != nil {
|
if err := validateUserSettingKey(key); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
setting := &Setting{UserID: userID, SettingKey: key}
|
setting := &Setting{UserID: userID, SettingKey: key}
|
||||||
has, err := db.GetEngine(db.DefaultContext).Get(setting)
|
has, err := db.GetEngine(db.DefaultContext).Get(setting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,7 +142,10 @@ func DeleteUserSetting(userID int64, key string) error {
|
|||||||
if err := validateUserSettingKey(key); err != nil {
|
if err := validateUserSettingKey(key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.Remove(genSettingCacheKey(userID, key))
|
||||||
_, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key})
|
_, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +154,12 @@ func SetUserSetting(userID int64, key, value string) error {
|
|||||||
if err := validateUserSettingKey(key); err != nil {
|
if err := validateUserSettingKey(key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return upsertUserSettingValue(userID, key, value)
|
|
||||||
|
_, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
|
||||||
|
return value, upsertUserSettingValue(userID, key, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func upsertUserSettingValue(userID int64, key, value string) error {
|
func upsertUserSettingValue(userID int64, key, value string) error {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestSettings(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// get specific setting
|
// get specific setting
|
||||||
settings, err := user_model.GetUserSettings(99, []string{keyName})
|
settings, err := user_model.GetSettings(99, []string{keyName})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, settings, 1)
|
assert.Len(t, settings, 1)
|
||||||
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
|
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
// GetKeyPair function returns a user's private and public keys
|
// GetKeyPair function returns a user's private and public keys
|
||||||
func GetKeyPair(user *user_model.User) (pub, priv string, err error) {
|
func GetKeyPair(user *user_model.User) (pub, priv string, err error) {
|
||||||
var settings map[string]*user_model.Setting
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
} else if len(settings) == 0 {
|
} else if len(settings) == 0 {
|
||||||
|
|||||||
131
modules/cache/cache.go
vendored
131
modules/cache/cache.go
vendored
@@ -46,32 +46,64 @@ func GetCache() mc.Cache {
|
|||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the key value from cache with callback when no key exists in cache
|
||||||
|
func Get[V interface{}](key string, getFunc func() (V, error)) (V, error) {
|
||||||
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
|
return getFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
cached := conn.Get(key)
|
||||||
|
if value, ok := cached.(V); ok {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getFunc()
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set updates and returns the key value in the cache with callback. The old value is only removed if the updateFunc() is successful
|
||||||
|
func Set[V interface{}](key string, valueFunc func() (V, error)) (V, error) {
|
||||||
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
|
return valueFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := valueFunc()
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||||
|
}
|
||||||
|
|
||||||
// GetString returns the key value from cache with callback when no key exists in cache
|
// GetString returns the key value from cache with callback when no key exists in cache
|
||||||
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
return getFunc()
|
return getFunc()
|
||||||
}
|
}
|
||||||
if !conn.IsExist(key) {
|
|
||||||
var (
|
cached := conn.Get(key)
|
||||||
value string
|
|
||||||
err error
|
if cached == nil {
|
||||||
)
|
value, err := getFunc()
|
||||||
if value, err = getFunc(); err != nil {
|
if err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
value := conn.Get(key)
|
|
||||||
if v, ok := value.(string); ok {
|
if value, ok := cached.(string); ok {
|
||||||
return v, nil
|
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
|
// GetInt returns key value from cache with callback when no key exists in cache
|
||||||
@@ -79,30 +111,33 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
|
|||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
return getFunc()
|
return getFunc()
|
||||||
}
|
}
|
||||||
if !conn.IsExist(key) {
|
|
||||||
var (
|
cached := conn.Get(key)
|
||||||
value int
|
|
||||||
err error
|
if cached == nil {
|
||||||
)
|
value, err := getFunc()
|
||||||
if value, err = getFunc(); err != nil {
|
if err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
|
||||||
if err != nil {
|
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch value := conn.Get(key).(type) {
|
|
||||||
|
switch v := cached.(type) {
|
||||||
case int:
|
case int:
|
||||||
return value, nil
|
return v, nil
|
||||||
case string:
|
case string:
|
||||||
v, err := strconv.Atoi(value)
|
value, err := strconv.Atoi(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return value, nil
|
||||||
default:
|
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 +146,34 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
|
|||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
return getFunc()
|
return getFunc()
|
||||||
}
|
}
|
||||||
if !conn.IsExist(key) {
|
|
||||||
var (
|
cached := conn.Get(key)
|
||||||
value int64
|
|
||||||
err error
|
if cached == nil {
|
||||||
)
|
value, err := getFunc()
|
||||||
if value, err = getFunc(); err != nil {
|
if err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
|
|
||||||
if err != nil {
|
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch value := conn.Get(key).(type) {
|
|
||||||
|
switch v := conn.Get(key).(type) {
|
||||||
case int64:
|
case int64:
|
||||||
return value, nil
|
return v, nil
|
||||||
case string:
|
case string:
|
||||||
v, err := strconv.ParseInt(value, 10, 64)
|
value, err := strconv.ParseInt(v, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return value, nil
|
||||||
default:
|
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]
|
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 {
|
if table == nil {
|
||||||
table = AmbiguousCharacters["_default"]
|
table = AmbiguousCharacters["_default"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package charset
|
package charset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune)
|
|||||||
return streamer.escaped, sb.String()
|
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) {
|
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
|
||||||
outputStream := &HTMLStreamerWriter{Writer: writer}
|
outputStream := &HTMLStreamerWriter{Writer: writer}
|
||||||
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
|
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
|
||||||
@@ -44,6 +45,35 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
|
|||||||
return streamer.escaped, err
|
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
|
// 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) {
|
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
|
||||||
sb := &strings.Builder{}
|
sb := &strings.Builder{}
|
||||||
|
|||||||
@@ -220,7 +220,13 @@ func (ctx *APIContext) CheckForOTP() {
|
|||||||
func APIAuth(authMethod auth_service.Method) func(*APIContext) {
|
func APIAuth(authMethod auth_service.Method) func(*APIContext) {
|
||||||
return func(ctx *APIContext) {
|
return func(ctx *APIContext) {
|
||||||
// Get user from session if logged in.
|
// 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.Doer != nil {
|
||||||
if ctx.Locale.Language() != ctx.Doer.Language {
|
if ctx.Locale.Language() != ctx.Doer.Language {
|
||||||
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
||||||
@@ -388,7 +394,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
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.CommitID = refName
|
||||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
@@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
|
|||||||
if statusPrefix == 4 || statusPrefix == 5 {
|
if statusPrefix == 4 || statusPrefix == 5 {
|
||||||
log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
|
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("Content-Type", "text/plain;charset=utf-8")
|
||||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
ctx.Resp.WriteHeader(status)
|
||||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
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()
|
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
|
// SetServeHeaders sets necessary content serve headers
|
||||||
func (ctx *Context) SetServeHeaders(filename string) {
|
func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
header := ctx.Resp.Header()
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
contentType := typesniffer.ApplicationOctetStream
|
||||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
if opts.ContentType != "" {
|
||||||
ctx.Resp.Header().Set("Expires", "0")
|
if opts.ContentTypeCharset != "" {
|
||||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
||||||
ctx.Resp.Header().Set("Pragma", "public")
|
} else {
|
||||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
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
|
// ServeContent serves content to http request
|
||||||
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
|
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
|
||||||
ctx.SetServeHeaders(name)
|
ctx.SetServeHeaders(opts)
|
||||||
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
|
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadStream returns the request body or the first form file
|
// UploadStream returns the request body or the first form file
|
||||||
@@ -635,7 +663,13 @@ func getCsrfOpts() CsrfOptions {
|
|||||||
// Auth converts auth.Auth as a middleware
|
// Auth converts auth.Auth as a middleware
|
||||||
func Auth(authMethod auth.Method) func(*Context) {
|
func Auth(authMethod auth.Method) func(*Context) {
|
||||||
return func(ctx *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.Doer != nil {
|
||||||
if ctx.Locale.Language() != ctx.Doer.Language {
|
if ctx.Locale.Language() != ctx.Doer.Language {
|
||||||
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
|
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
|
// For legacy and API support only full commit sha
|
||||||
parts := strings.Split(path, "/")
|
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:], "/")
|
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||||
return parts[0]
|
return parts[0]
|
||||||
}
|
}
|
||||||
@@ -852,7 +852,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
|||||||
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
|
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
|
||||||
case RepoRefCommit:
|
case RepoRefCommit:
|
||||||
parts := strings.Split(path, "/")
|
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:], "/")
|
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
|
||||||
return parts[0]
|
return parts[0]
|
||||||
}
|
}
|
||||||
@@ -961,7 +961,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
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.IsViewCommit = true
|
||||||
ctx.Repo.CommitID = refName
|
ctx.Repo.CommitID = refName
|
||||||
|
|
||||||
@@ -971,7 +971,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If short commit ID add canonical link header
|
// 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\"",
|
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))))
|
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,12 +110,11 @@ func ToAPIIssueList(il issues_model.IssueList) []*api.Issue {
|
|||||||
// ToTrackedTime converts TrackedTime to API format
|
// ToTrackedTime converts TrackedTime to API format
|
||||||
func ToTrackedTime(t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
|
func ToTrackedTime(t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
|
||||||
apiT = &api.TrackedTime{
|
apiT = &api.TrackedTime{
|
||||||
ID: t.ID,
|
ID: t.ID,
|
||||||
IssueID: t.IssueID,
|
IssueID: t.IssueID,
|
||||||
UserID: t.UserID,
|
UserID: t.UserID,
|
||||||
UserName: t.User.Name,
|
Time: t.Time,
|
||||||
Time: t.Time,
|
Created: t.Created,
|
||||||
Created: t.Created,
|
|
||||||
}
|
}
|
||||||
if t.Issue != nil {
|
if t.Issue != nil {
|
||||||
apiT.Issue = ToAPIIssue(t.Issue)
|
apiT.Issue = ToAPIIssue(t.Issue)
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
|||||||
// find stopwatches without existing issue
|
// find stopwatches without existing issue
|
||||||
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
|
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
|
||||||
"stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
|
"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 {
|
for _, c := range consistencyChecks {
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
|
|||||||
numReposUpdated := 0
|
numReposUpdated := 0
|
||||||
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||||
numRepos++
|
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(&git.RunOpts{Dir: repo.RepoPath()})
|
||||||
|
|
||||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(runOpts)
|
|
||||||
|
|
||||||
// what we expect: default branch is valid, and HEAD points to it
|
// what we expect: default branch is valid, and HEAD points to it
|
||||||
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
|
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
|
// 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 {
|
if err != nil {
|
||||||
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
||||||
return nil
|
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)
|
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 {
|
} else {
|
||||||
if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
|
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 {
|
} else {
|
||||||
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
||||||
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
||||||
|
|||||||
@@ -202,8 +202,11 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &RunOpts{}
|
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 {
|
if len(opts.Dir) == 0 {
|
||||||
@@ -238,7 +241,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||||||
if opts.UseContextTimeout {
|
if opts.UseContextTimeout {
|
||||||
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
||||||
} else {
|
} else {
|
||||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
|
||||||
}
|
}
|
||||||
defer finished()
|
defer finished()
|
||||||
|
|
||||||
@@ -339,9 +342,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
|
|||||||
}
|
}
|
||||||
stdoutBuf := &bytes.Buffer{}
|
stdoutBuf := &bytes.Buffer{}
|
||||||
stderrBuf := &bytes.Buffer{}
|
stderrBuf := &bytes.Buffer{}
|
||||||
opts.Stdout = stdoutBuf
|
|
||||||
opts.Stderr = stderrBuf
|
// We must not change the provided options as it could break future calls - therefore make a copy.
|
||||||
err := c.Run(opts)
|
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()
|
stderr = stderrBuf.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
|
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.
|
// IsBranchExist returns true if given branch exists in current repository.
|
||||||
func (repo *Repository) IsBranchExist(name string) bool {
|
func (repo *Repository) IsBranchExist(name string) bool {
|
||||||
if name == "" {
|
if repo == nil || name == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (repo *Repository) RemoveReference(name string) error {
|
|||||||
|
|
||||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||||
if len(commitID) == 40 {
|
if len(commitID) == SHAFullLength {
|
||||||
sha1, err := NewIDFromString(commitID)
|
sha1, err := NewIDFromString(commitID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return sha1, 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
|
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||||
if len(commitID) == 40 && IsValidSHAPattern(commitID) {
|
if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) {
|
||||||
sha1, err := NewIDFromString(commitID)
|
sha1, err := NewIDFromString(commitID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return sha1, nil
|
return sha1, nil
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
// ReadTreeToIndex reads a treeish to the index
|
// ReadTreeToIndex reads a treeish to the index
|
||||||
func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
|
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})
|
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// IsTagExist returns true if given tag exists in the repository.
|
// IsTagExist returns true if given tag exists in the repository.
|
||||||
func (repo *Repository) IsTagExist(name string) bool {
|
func (repo *Repository) IsTagExist(name string) bool {
|
||||||
if name == "" {
|
if repo == nil || name == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
|||||||
|
|
||||||
// GetTree find the tree object in the repository.
|
// GetTree find the tree object in the repository.
|
||||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
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})
|
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
|
|||||||
|
|
||||||
// GetTree find the tree object in the repository.
|
// GetTree find the tree object in the repository.
|
||||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||||
if len(idStr) != 40 {
|
if len(idStr) != SHAFullLength {
|
||||||
res, err := repo.GetRefCommitID(idStr)
|
res, err := repo.GetRefCommitID(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ const EmptySHA = "0000000000000000000000000000000000000000"
|
|||||||
// EmptyTreeSHA is the SHA of an empty tree
|
// EmptyTreeSHA is the SHA of an empty tree
|
||||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
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
|
// SHAPattern can be used to determine if a string is an valid sha
|
||||||
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
|
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
|
||||||
|
|
||||||
@@ -51,7 +54,7 @@ func MustIDFromString(s string) SHA1 {
|
|||||||
func NewIDFromString(s string) (SHA1, error) {
|
func NewIDFromString(s string) (SHA1, error) {
|
||||||
var id SHA1
|
var id SHA1
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
if len(s) != 40 {
|
if len(s) != SHAFullLength {
|
||||||
return id, fmt.Errorf("Length must be 40: %s", s)
|
return id, fmt.Errorf("Length must be 40: %s", s)
|
||||||
}
|
}
|
||||||
b, err := hex.DecodeString(s)
|
b, err := hex.DecodeString(s)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ package git
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
@@ -30,7 +31,9 @@ type Signature = object.Signature
|
|||||||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
||||||
sig := new(Signature)
|
sig := new(Signature)
|
||||||
emailStart := bytes.IndexByte(line, '<')
|
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, '>')
|
emailEnd := bytes.IndexByte(line, '>')
|
||||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
|
|||||||
return
|
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])
|
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||||
|
|
||||||
hasTime := emailEnd+2 < len(line)
|
hasTime := emailEnd+2 < len(line)
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func validateOptions(field *api.IssueFormField, idx int) error {
|
|||||||
return position.Errorf("should be a string")
|
return position.Errorf("should be a string")
|
||||||
}
|
}
|
||||||
case api.IssueFormFieldTypeCheckboxes:
|
case api.IssueFormFieldTypeCheckboxes:
|
||||||
opt, ok := option.(map[interface{}]interface{})
|
opt, ok := option.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return position.Errorf("should be a dictionary")
|
return position.Errorf("should be a dictionary")
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ func (o *valuedOption) Label() string {
|
|||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
case api.IssueFormFieldTypeCheckboxes:
|
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 {
|
if v, ok := vs["label"].(string); ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
content string
|
filename string
|
||||||
wantErr string
|
content string
|
||||||
|
want *api.IssueTemplate
|
||||||
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "miss name",
|
name: "miss name",
|
||||||
@@ -316,21 +319,9 @@ body:
|
|||||||
`,
|
`,
|
||||||
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
|
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
for _, tt := range tests {
|
name: "valid",
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
content: `
|
||||||
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: Name
|
name: Name
|
||||||
title: Title
|
title: Title
|
||||||
about: About
|
about: About
|
||||||
@@ -386,96 +377,227 @@ body:
|
|||||||
required: false
|
required: false
|
||||||
- label: Option 3 of checkboxes
|
- label: Option 3 of checkboxes
|
||||||
required: true
|
required: true
|
||||||
`
|
`,
|
||||||
want := &api.IssueTemplate{
|
want: &api.IssueTemplate{
|
||||||
Name: "Name",
|
Name: "Name",
|
||||||
Title: "Title",
|
Title: "Title",
|
||||||
About: "About",
|
About: "About",
|
||||||
Labels: []string{"label1", "label2"},
|
Labels: []string{"label1", "label2"},
|
||||||
Ref: "Ref",
|
Ref: "Ref",
|
||||||
Fields: []*api.IssueFormField{
|
Fields: []*api.IssueFormField{
|
||||||
{
|
{
|
||||||
Type: "markdown",
|
Type: "markdown",
|
||||||
ID: "id1",
|
ID: "id1",
|
||||||
Attributes: map[string]interface{}{
|
Attributes: map[string]interface{}{
|
||||||
"value": "Value of the markdown",
|
"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",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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: "input",
|
||||||
Type: "checkboxes",
|
ID: "id3",
|
||||||
ID: "id5",
|
Attributes: map[string]interface{}{
|
||||||
Attributes: map[string]interface{}{
|
"label": "Label of input",
|
||||||
"label": "Label of checkboxes",
|
"description": "Description of input",
|
||||||
"description": "Description of checkboxes",
|
"placeholder": "Placeholder of input",
|
||||||
"options": []interface{}{
|
"value": "Value of input",
|
||||||
map[interface{}]interface{}{"label": "Option 1 of checkboxes", "required": true},
|
},
|
||||||
map[interface{}]interface{}{"label": "Option 2 of checkboxes", "required": false},
|
Validations: map[string]interface{}{
|
||||||
map[interface{}]interface{}{"label": "Option 3 of checkboxes", "required": true},
|
"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",
|
wantErr: "",
|
||||||
}
|
},
|
||||||
got, err := unmarshal("test.yaml", []byte(content))
|
{
|
||||||
if err != nil {
|
name: "single label",
|
||||||
t.Fatal(err)
|
content: `
|
||||||
}
|
name: Name
|
||||||
if err := Validate(got); err != nil {
|
title: Title
|
||||||
t.Errorf("Validate() error = %v", err)
|
about: About
|
||||||
}
|
labels: label1
|
||||||
if !reflect.DeepEqual(want, got) {
|
ref: Ref
|
||||||
jsonWant, _ := json.Marshal(want)
|
body:
|
||||||
jsonGot, _ := json.Marshal(got)
|
- type: markdown
|
||||||
t.Errorf("want:\n%s\ngot:\n%s", jsonWant, jsonGot)
|
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) {
|
func TestRenderToMarkdown(t *testing.T) {
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ package template
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
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,
|
// 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
|
// UnmarshalFromEntry parses out a valid template from the blob in entry
|
||||||
func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
|
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
|
// 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 {
|
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
|
||||||
templateBody, err := markdown.ExtractMetadata(string(content), it)
|
if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil {
|
||||||
if err != nil {
|
// The only thing we know here is that we can't extract metadata from the content,
|
||||||
return nil, err
|
// it's hard to tell if metadata doesn't exist or metadata isn't valid.
|
||||||
}
|
// There's an example template:
|
||||||
it.Content = templateBody
|
//
|
||||||
if it.About == "" {
|
// ---
|
||||||
if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
|
// # Title
|
||||||
it.About = compatibleTemplate.About
|
// ---
|
||||||
|
// 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 {
|
} else if typ == api.IssueTemplateTypeYaml {
|
||||||
|
|||||||
@@ -9,82 +9,86 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateMetadata(it structs.IssueTemplate) bool {
|
/*
|
||||||
/*
|
IssueTemplate is a legacy to keep the unit tests working.
|
||||||
A legacy to keep the unit tests working.
|
Copied from structs.IssueTemplate, the original type has been changed a lot to support yaml template.
|
||||||
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.
|
type IssueTemplate struct {
|
||||||
The new way to validate an issue template is to call the Validate in modules/issue/template,
|
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) != ""
|
return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractMetadata(t *testing.T) {
|
func TestExtractMetadata(t *testing.T) {
|
||||||
t.Run("ValidFrontAndBody", func(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)
|
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, bodyTest, body)
|
assert.Equal(t, bodyTest, body)
|
||||||
assert.Equal(t, metaTest, meta)
|
assert.Equal(t, metaTest, meta)
|
||||||
assert.True(t, validateMetadata(meta))
|
assert.True(t, meta.Valid())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoFirstSeparator", func(t *testing.T) {
|
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)
|
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoLastSeparator", func(t *testing.T) {
|
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)
|
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoBody", func(t *testing.T) {
|
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)
|
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "", body)
|
assert.Equal(t, "", body)
|
||||||
assert.Equal(t, metaTest, meta)
|
assert.Equal(t, metaTest, meta)
|
||||||
assert.True(t, validateMetadata(meta))
|
assert.True(t, meta.Valid())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractMetadataBytes(t *testing.T) {
|
func TestExtractMetadataBytes(t *testing.T) {
|
||||||
t.Run("ValidFrontAndBody", func(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)
|
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, bodyTest, string(body))
|
assert.Equal(t, bodyTest, string(body))
|
||||||
assert.Equal(t, metaTest, meta)
|
assert.Equal(t, metaTest, meta)
|
||||||
assert.True(t, validateMetadata(meta))
|
assert.True(t, meta.Valid())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoFirstSeparator", func(t *testing.T) {
|
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)
|
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoLastSeparator", func(t *testing.T) {
|
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)
|
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("NoBody", func(t *testing.T) {
|
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)
|
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "", string(body))
|
assert.Equal(t, "", string(body))
|
||||||
assert.Equal(t, metaTest, meta)
|
assert.Equal(t, metaTest, meta)
|
||||||
assert.True(t, validateMetadata(meta))
|
assert.True(t, meta.Valid())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ labels:
|
|||||||
- bug
|
- bug
|
||||||
- "test label"`
|
- "test label"`
|
||||||
bodyTest = "This is the body"
|
bodyTest = "This is the body"
|
||||||
metaTest = structs.IssueTemplate{
|
metaTest = IssueTemplate{
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
About: "A Test",
|
About: "A Test",
|
||||||
Title: "Test Title",
|
Title: "Test Title",
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
|||||||
return s.store.Open(KeyToRelativePath(key))
|
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
|
// Save stores a package blob
|
||||||
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
|
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
|
||||||
_, err := s.store.Save(KeyToRelativePath(key), r, size)
|
_, err := s.store.Save(KeyToRelativePath(key), r, size)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ package nuget
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -183,7 +185,23 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
|||||||
return &Package{
|
return &Package{
|
||||||
PackageType: packageType,
|
PackageType: packageType,
|
||||||
ID: p.Metadata.ID,
|
ID: p.Metadata.ID,
|
||||||
Version: v.String(),
|
Version: toNormalizedVersion(v),
|
||||||
Metadata: m,
|
Metadata: m,
|
||||||
}, nil
|
}, 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.Len(t, deps, 1)
|
||||||
assert.Equal(t, dependencyID, deps[0].ID)
|
assert.Equal(t, dependencyID, deps[0].ID)
|
||||||
assert.Equal(t, dependencyVersion, deps[0].Version)
|
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) {
|
t.Run("Symbols Package", func(t *testing.T) {
|
||||||
|
|||||||
@@ -18,32 +18,33 @@ import (
|
|||||||
// Mailer represents mail service.
|
// Mailer represents mail service.
|
||||||
type Mailer struct {
|
type Mailer struct {
|
||||||
// Mailer
|
// Mailer
|
||||||
Name string
|
Name string `ini:"NAME"`
|
||||||
From string
|
From string `ini:"FROM"`
|
||||||
EnvelopeFrom string
|
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
|
||||||
OverrideEnvelopeFrom bool `ini:"-"`
|
OverrideEnvelopeFrom bool `ini:"-"`
|
||||||
FromName string
|
FromName string `ini:"-"`
|
||||||
FromEmail string
|
FromEmail string `ini:"-"`
|
||||||
SendAsPlainText bool
|
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
|
||||||
SubjectPrefix string
|
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
|
||||||
|
|
||||||
// SMTP sender
|
// SMTP sender
|
||||||
Protocol string
|
Protocol string `ini:"PROTOCOL"`
|
||||||
SMTPAddr string
|
SMTPAddr string `ini:"SMTP_ADDR"`
|
||||||
SMTPPort string
|
SMTPPort string `ini:"SMTP_PORT"`
|
||||||
User, Passwd string
|
User string `ini:"USER"`
|
||||||
EnableHelo bool
|
Passwd string `ini:"PASSWD"`
|
||||||
HeloHostname string
|
EnableHelo bool `ini:"ENABLE_HELO"`
|
||||||
ForceTrustServerCert bool
|
HeloHostname string `ini:"HELO_HOSTNAME"`
|
||||||
UseClientCert bool
|
ForceTrustServerCert bool `ini:"FORCE_TRUST_SERVER_CERT"`
|
||||||
ClientCertFile string
|
UseClientCert bool `ini:"USE_CLIENT_CERT"`
|
||||||
ClientKeyFile string
|
ClientCertFile string `ini:"CLIENT_CERT_FILE"`
|
||||||
|
ClientKeyFile string `ini:"CLIENT_KEY_FILE"`
|
||||||
|
|
||||||
// Sendmail sender
|
// Sendmail sender
|
||||||
SendmailPath string
|
SendmailPath string `ini:"SENDMAIL_PATH"`
|
||||||
SendmailArgs []string
|
SendmailArgs []string `ini:"-"`
|
||||||
SendmailTimeout time.Duration
|
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"`
|
||||||
SendmailConvertCRLF bool
|
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailService the global mailer
|
// MailService the global mailer
|
||||||
@@ -56,35 +57,12 @@ func newMailService() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
MailService = &Mailer{
|
// Handle Deprecations and map on to new configuration
|
||||||
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("")
|
|
||||||
|
|
||||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||||
deprecatedSetting("mailer", "MAILER_TYPE", "mailer", "PROTOCOL")
|
deprecatedSetting("mailer", "MAILER_TYPE", "mailer", "PROTOCOL")
|
||||||
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
|
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
|
||||||
if sec.Key("MAILER_TYPE").String() == "sendmail" {
|
if sec.Key("MAILER_TYPE").String() == "sendmail" {
|
||||||
MailService.Protocol = "sendmail"
|
sec.Key("PROTOCOL").MustString("sendmail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,31 +74,91 @@ func newMailService() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
||||||
}
|
}
|
||||||
MailService.SMTPAddr = addr
|
sec.Key("SMTP_ADDR").MustString(addr)
|
||||||
MailService.SMTPPort = port
|
sec.Key("SMTP_PORT").MustString(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: DEPRECATED to be removed in v1.19.0
|
// FIXME: DEPRECATED to be removed in v1.19.0
|
||||||
deprecatedSetting("mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
|
deprecatedSetting("mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
|
||||||
if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
|
if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
|
||||||
if sec.Key("IS_TLS_ENABLED").MustBool() {
|
if sec.Key("IS_TLS_ENABLED").MustBool() {
|
||||||
MailService.Protocol = "smtps"
|
sec.Key("PROTOCOL").MustString("smtps")
|
||||||
} else {
|
} 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 == "" {
|
if MailService.SMTPPort == "" {
|
||||||
switch MailService.Protocol {
|
switch MailService.Protocol {
|
||||||
case "smtp":
|
case "smtp":
|
||||||
MailService.SMTPPort = "25"
|
MailService.SMTPPort = "25"
|
||||||
case "smtps":
|
case "smtps":
|
||||||
MailService.SMTPPort = "465"
|
MailService.SMTPPort = "465"
|
||||||
case "smtp+startls":
|
case "smtp+starttls":
|
||||||
MailService.SMTPPort = "587"
|
MailService.SMTPPort = "587"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Infer Protocol
|
||||||
if MailService.Protocol == "" {
|
if MailService.Protocol == "" {
|
||||||
if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
|
if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
|
||||||
MailService.Protocol = "smtp+unix"
|
MailService.Protocol = "smtp+unix"
|
||||||
@@ -131,7 +169,7 @@ func newMailService() {
|
|||||||
case "465":
|
case "465":
|
||||||
MailService.Protocol = "smtps"
|
MailService.Protocol = "smtps"
|
||||||
case "587":
|
case "587":
|
||||||
MailService.Protocol = "smtp+startls"
|
MailService.Protocol = "smtp+starttls"
|
||||||
default:
|
default:
|
||||||
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
||||||
MailService.Protocol = "smtps"
|
MailService.Protocol = "smtps"
|
||||||
@@ -151,42 +189,6 @@ func newMailService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if MailService.From != "" {
|
if MailService.From != "" {
|
||||||
parsed, err := mail.ParseAddress(MailService.From)
|
parsed, err := mail.ParseAddress(MailService.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ var (
|
|||||||
DefaultMergeMessageOfficialApproversOnly bool
|
DefaultMergeMessageOfficialApproversOnly bool
|
||||||
PopulateSquashCommentWithCommitMessages bool
|
PopulateSquashCommentWithCommitMessages bool
|
||||||
AddCoCommitterTrailers bool
|
AddCoCommitterTrailers bool
|
||||||
|
TestConflictingPatchesWithGitApply bool
|
||||||
} `ini:"repository.pull-request"`
|
} `ini:"repository.pull-request"`
|
||||||
|
|
||||||
// Issue Setting
|
// Issue Setting
|
||||||
@@ -205,6 +206,7 @@ var (
|
|||||||
DefaultMergeMessageOfficialApproversOnly bool
|
DefaultMergeMessageOfficialApproversOnly bool
|
||||||
PopulateSquashCommentWithCommitMessages bool
|
PopulateSquashCommentWithCommitMessages bool
|
||||||
AddCoCommitterTrailers bool
|
AddCoCommitterTrailers bool
|
||||||
|
TestConflictingPatchesWithGitApply bool
|
||||||
}{
|
}{
|
||||||
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
|
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
|
||||||
// Same as GitHub. See
|
// Same as GitHub. See
|
||||||
@@ -219,6 +221,7 @@ var (
|
|||||||
DefaultMergeMessageOfficialApproversOnly: true,
|
DefaultMergeMessageOfficialApproversOnly: true,
|
||||||
PopulateSquashCommentWithCommitMessages: false,
|
PopulateSquashCommentWithCommitMessages: false,
|
||||||
AddCoCommitterTrailers: true,
|
AddCoCommitterTrailers: true,
|
||||||
|
TestConflictingPatchesWithGitApply: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Issue settings
|
// Issue settings
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
"code.gitea.io/gitea/modules/generate"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/user"
|
"code.gitea.io/gitea/modules/user"
|
||||||
@@ -463,6 +464,13 @@ func getAppPath() (string, error) {
|
|||||||
appPath, err = exec.LookPath(os.Args[0])
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -602,7 +610,7 @@ func LoadForTest(extraConfigs ...string) {
|
|||||||
|
|
||||||
func deprecatedSetting(oldSection, oldKey, newSection, newKey string) {
|
func deprecatedSetting(oldSection, oldKey, newSection, newKey string) {
|
||||||
if Cfg.Section(oldSection).HasKey(oldKey) {
|
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)
|
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
||||||
|
|
||||||
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
|
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(",")
|
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")
|
||||||
if len(cfgdata) == 0 {
|
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.
|
// 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.
|
// 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)
|
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")
|
IsProd = strings.EqualFold(RunMode, "prod")
|
||||||
// Does not check run user when the install lock is off.
|
// Does not check run user when the install lock is off.
|
||||||
if InstallLock {
|
if InstallLock {
|
||||||
@@ -1150,6 +1166,8 @@ func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
|
|||||||
return authorizedPrincipalsAllow, true
|
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 {
|
func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
|
||||||
// don't allow setting both URI and verbatim string
|
// don't allow setting both URI and verbatim string
|
||||||
uri := sec.Key(uriKey).String()
|
uri := sec.Key(uriKey).String()
|
||||||
@@ -1173,7 +1191,15 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err)
|
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
|
// only file URIs are allowed
|
||||||
default:
|
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
|
// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
|
||||||
func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
|
func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
|
||||||
parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
|
parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
|
|||||||
return 0, err
|
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)
|
// 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
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateType issue state type
|
// StateType issue state type
|
||||||
@@ -143,14 +147,47 @@ type IssueFormField struct {
|
|||||||
// IssueTemplate represents an issue template for a repository
|
// IssueTemplate represents an issue template for a repository
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type IssueTemplate struct {
|
type IssueTemplate struct {
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Title string `json:"title" yaml:"title"`
|
Title string `json:"title" yaml:"title"`
|
||||||
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
|
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
|
||||||
Labels []string `json:"labels" yaml:"labels"`
|
Labels IssueTemplateLabels `json:"labels" yaml:"labels"`
|
||||||
Ref string `json:"ref" yaml:"ref"`
|
Ref string `json:"ref" yaml:"ref"`
|
||||||
Content string `json:"content" yaml:"-"`
|
Content string `json:"content" yaml:"-"`
|
||||||
Fields []*IssueFormField `json:"body" yaml:"body"`
|
Fields []*IssueFormField `json:"body" yaml:"body"`
|
||||||
FileName string `json:"file_name" yaml:"-"`
|
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
|
// IssueTemplateType defines issue template type
|
||||||
@@ -163,14 +200,14 @@ const (
|
|||||||
|
|
||||||
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
|
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
|
||||||
func (it IssueTemplate) Type() IssueTemplateType {
|
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
|
// ignore config.yaml which is a special configuration file
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if ext := filepath.Ext(it.FileName); ext == ".md" {
|
if ext := path.Ext(it.FileName); ext == ".md" {
|
||||||
return IssueTemplateTypeMarkdown
|
return IssueTemplateTypeMarkdown
|
||||||
} else if ext == ".yaml" || ext == ".yml" {
|
} else if ext == ".yaml" || ext == ".yml" {
|
||||||
return "yaml"
|
return IssueTemplateTypeYaml
|
||||||
}
|
}
|
||||||
return IssueTemplateTypeYaml
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
106
modules/structs/issue_test.go
Normal file
106
modules/structs/issue_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,8 @@ package system
|
|||||||
|
|
||||||
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
|
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
|
||||||
type RuntimeState struct {
|
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
|
// 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/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/svg"
|
"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/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
@@ -87,7 +86,7 @@ func NewFuncMap() []template.FuncMap {
|
|||||||
return setting.AssetVersion
|
return setting.AssetVersion
|
||||||
},
|
},
|
||||||
"DisableGravatar": func() bool {
|
"DisableGravatar": func() bool {
|
||||||
return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
return system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||||
},
|
},
|
||||||
"DefaultShowFullName": func() bool {
|
"DefaultShowFullName": func() bool {
|
||||||
return setting.UI.DefaultShowFullName
|
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)
|
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||||
func Avatar(item interface{}, others ...interface{}) template.HTML {
|
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) {
|
switch t := item.(type) {
|
||||||
case *user_model.User:
|
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)
|
// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
|
||||||
func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
|
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()
|
src := repo.RelAvatarLink()
|
||||||
if src != "" {
|
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)
|
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
|
||||||
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
|
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)
|
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
|
||||||
|
|
||||||
if src != "" {
|
if src != "" {
|
||||||
|
|||||||
@@ -13,8 +13,13 @@ import (
|
|||||||
// TimeStamp defines a timestamp
|
// TimeStamp defines a timestamp
|
||||||
type TimeStamp int64
|
type TimeStamp int64
|
||||||
|
|
||||||
// mock is NOT concurrency-safe!!
|
var (
|
||||||
var mock time.Time
|
// 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
|
// Set sets the time to a mocked time.Time
|
||||||
func Set(now time.Time) {
|
func Set(now time.Time) {
|
||||||
@@ -103,5 +108,5 @@ func (ts TimeStamp) FormatDate() string {
|
|||||||
|
|
||||||
// IsZero is zero time
|
// IsZero is zero time
|
||||||
func (ts TimeStamp) IsZero() bool {
|
func (ts TimeStamp) IsZero() bool {
|
||||||
return ts.AsTimeInLocation(time.Local).IsZero()
|
return int64(ts) == 0 || int64(ts) == timeZeroUnix
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/packages/composer"
|
"code.gitea.io/gitea/routers/api/packages/composer"
|
||||||
@@ -57,7 +58,13 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||||||
|
|
||||||
authGroup := auth.NewGroup(authMethods...)
|
authGroup := auth.NewGroup(authMethods...)
|
||||||
r.Use(func(ctx *context.Context) {
|
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
|
ctx.IsSigned = ctx.Doer != nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -179,6 +186,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||||||
r.Group("/maven", func() {
|
r.Group("/maven", func() {
|
||||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
|
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
|
||||||
r.Get("/*", maven.DownloadPackageFile)
|
r.Get("/*", maven.DownloadPackageFile)
|
||||||
|
r.Head("/*", maven.ProvidePackageFileHeader)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/nuget", func() {
|
r.Group("/nuget", func() {
|
||||||
r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
|
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...)
|
authGroup := auth.NewGroup(authMethods...)
|
||||||
r.Use(func(ctx *context.Context) {
|
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
|
ctx.IsSigned = ctx.Doer != nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -184,7 +184,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
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
|
// UploadPackage creates a new package
|
||||||
|
|||||||
@@ -20,22 +20,22 @@ func (a *Auth) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify extracts the user from the Bearer token
|
// 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)
|
uid, err := packages.ParseAuthorizationToken(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(uid)
|
u, err := user_model.GetUserByID(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
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()
|
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)
|
// DeleteRecipeV1 deletes the requested recipe(s)
|
||||||
|
|||||||
@@ -21,25 +21,25 @@ func (a *Auth) Name() string {
|
|||||||
|
|
||||||
// Verify extracts the user from the Bearer token
|
// Verify extracts the user from the Bearer token
|
||||||
// If it's an anonymous session a ghost user is returned
|
// 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)
|
uid, err := packages.ParseAuthorizationToken(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("ParseAuthorizationToken: %v", err)
|
log.Trace("ParseAuthorizationToken: %v", err)
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == 0 {
|
if uid == 0 {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if uid == -1 {
|
if uid == -1 {
|
||||||
return user_model.NewGhostUser()
|
return user_model.NewGhostUser(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(uid)
|
u, err := user_model.GetUserByID(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
log.Error("GetUserByID: %v", err)
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return u
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ package container
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@@ -16,9 +19,12 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var uploadVersionMutex sync.Mutex
|
||||||
|
|
||||||
// saveAsPackageBlob creates a package blob from an upload
|
// 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
|
// 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) {
|
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
|
||||||
@@ -28,6 +34,11 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||||||
|
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
|
|
||||||
|
var uploadVersion *packages_model.PackageVersion
|
||||||
|
|
||||||
|
// FIXME: Replace usage of mutex with database transaction
|
||||||
|
// https://github.com/go-gitea/gitea/pull/21862
|
||||||
|
uploadVersionMutex.Lock()
|
||||||
err := db.WithTx(func(ctx context.Context) error {
|
err := db.WithTx(func(ctx context.Context) error {
|
||||||
created := true
|
created := true
|
||||||
p := &packages_model.Package{
|
p := &packages_model.Package{
|
||||||
@@ -68,11 +79,30 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadVersion = pv
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
uploadVersionMutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.WithTx(func(ctx context.Context) error {
|
||||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error inserting package blob: %v", err)
|
log.Error("Error inserting package blob: %v", err)
|
||||||
return 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 !exists {
|
||||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
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)
|
log.Error("Error saving package blob in content store: %v", err)
|
||||||
@@ -83,7 +113,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||||
|
|
||||||
pf := &packages_model.PackageFile{
|
pf := &packages_model.PackageFile{
|
||||||
VersionID: pv.ID,
|
VersionID: uploadVersion.ID,
|
||||||
BlobID: pb.ID,
|
BlobID: pb.ID,
|
||||||
Name: filename,
|
Name: filename,
|
||||||
LowerName: filename,
|
LowerName: filename,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -24,6 +25,7 @@ import (
|
|||||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
"code.gitea.io/gitea/modules/packages/container/oci"
|
"code.gitea.io/gitea/modules/packages/container/oci"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers/api/packages/helper"
|
"code.gitea.io/gitea/routers/api/packages/helper"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
container_service "code.gitea.io/gitea/services/packages/container"
|
container_service "code.gitea.io/gitea/services/packages/container"
|
||||||
@@ -193,7 +195,7 @@ func InitiateUploadBlob(ctx *context.Context) {
|
|||||||
mount := ctx.FormTrim("mount")
|
mount := ctx.FormTrim("mount")
|
||||||
from := ctx.FormTrim("from")
|
from := ctx.FormTrim("from")
|
||||||
if mount != "" {
|
if mount != "" {
|
||||||
blob, _ := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||||
Image: from,
|
Image: from,
|
||||||
Digest: mount,
|
Digest: mount,
|
||||||
})
|
})
|
||||||
@@ -406,7 +408,7 @@ func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescri
|
|||||||
return nil, container_model.ErrContainerBlobNotExist
|
return nil, container_model.ErrContainerBlobNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
return container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
Image: ctx.Params("image"),
|
Image: ctx.Params("image"),
|
||||||
Digest: digest,
|
Digest: digest,
|
||||||
@@ -548,7 +550,7 @@ func getManifestFromContext(ctx *context.Context) (*packages_model.PackageFileDe
|
|||||||
return nil, container_model.ErrContainerBlobNotExist
|
return nil, container_model.ErrContainerBlobNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
return container_model.GetContainerBlob(ctx, opts)
|
return workaroundGetContainerBlob(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#checking-if-content-exists-in-the-registry
|
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#checking-if-content-exists-in-the-registry
|
||||||
@@ -688,3 +690,23 @@ func GetTagList(ctx *context.Context) {
|
|||||||
Tags: tags,
|
Tags: tags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Workaround to be removed in v1.20
|
||||||
|
// https://github.com/go-gitea/gitea/issues/19586
|
||||||
|
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
|
||||||
|
blob, err := container_model.GetContainerBlob(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(blob.Blob.HashSHA256))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Debug("Package registry inconsistent: blob %s does not exist on file system", blob.Blob.HashSHA256)
|
||||||
|
return nil, container_model.ErrContainerBlobNotExist
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -19,6 +21,7 @@ import (
|
|||||||
packages_module "code.gitea.io/gitea/modules/packages"
|
packages_module "code.gitea.io/gitea/modules/packages"
|
||||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||||
"code.gitea.io/gitea/modules/packages/container/oci"
|
"code.gitea.io/gitea/modules/packages/container/oci"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,6 +406,15 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
|
|||||||
log.Error("Error inserting package blob: %v", err)
|
log.Error("Error inserting package blob: %v", err)
|
||||||
return nil, false, "", err
|
return nil, false, "", err
|
||||||
}
|
}
|
||||||
|
// FIXME: Workaround to be removed in v1.20
|
||||||
|
// https://github.com/go-gitea/gitea/issues/19586
|
||||||
|
if exists {
|
||||||
|
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||||
|
if err != nil && (errors.Is(err, 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 !exists {
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
|
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadPackage uploads the specific generic package.
|
// UploadPackage uploads the specific generic package.
|
||||||
|
|||||||
@@ -138,7 +138,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
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
|
// UploadPackage creates a new package
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package maven
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@@ -23,12 +22,8 @@ type MetadataResponse struct {
|
|||||||
Version []string `xml:"versioning>versions>version"`
|
Version []string `xml:"versioning>versions>version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pds is expected to be sorted ascending by CreatedUnix
|
||||||
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
|
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
|
||||||
sort.Slice(pds, func(i, j int) bool {
|
|
||||||
// Maven and Gradle order packages by their creation timestamp and not by their version string
|
|
||||||
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
|
|
||||||
})
|
|
||||||
|
|
||||||
var release *packages_model.PackageDescriptor
|
var release *packages_model.PackageDescriptor
|
||||||
|
|
||||||
versions := make([]string, 0, len(pds))
|
versions := make([]string, 0, len(pds))
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
@@ -34,6 +36,10 @@ const (
|
|||||||
extensionSHA1 = ".sha1"
|
extensionSHA1 = ".sha1"
|
||||||
extensionSHA256 = ".sha256"
|
extensionSHA256 = ".sha256"
|
||||||
extensionSHA512 = ".sha512"
|
extensionSHA512 = ".sha512"
|
||||||
|
extensionPom = ".pom"
|
||||||
|
extensionJar = ".jar"
|
||||||
|
contentTypeJar = "application/java-archive"
|
||||||
|
contentTypeXML = "text/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -49,6 +55,15 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
|
|||||||
|
|
||||||
// DownloadPackageFile serves the content of a package
|
// DownloadPackageFile serves the content of a package
|
||||||
func DownloadPackageFile(ctx *context.Context) {
|
func DownloadPackageFile(ctx *context.Context) {
|
||||||
|
handlePackageFile(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProvidePackageFileHeader provides only the headers describing a package
|
||||||
|
func ProvidePackageFileHeader(ctx *context.Context) {
|
||||||
|
handlePackageFile(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePackageFile(ctx *context.Context, serveContent bool) {
|
||||||
params, err := extractPathParameters(ctx)
|
params, err := extractPathParameters(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
apiError(ctx, http.StatusBadRequest, err)
|
||||||
@@ -58,7 +73,7 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
if params.IsMeta && params.Version == "" {
|
if params.IsMeta && params.Version == "" {
|
||||||
serveMavenMetadata(ctx, params)
|
serveMavenMetadata(ctx, params)
|
||||||
} else {
|
} else {
|
||||||
servePackageFile(ctx, params)
|
servePackageFile(ctx, params, serveContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +97,11 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(pds, func(i, j int) bool {
|
||||||
|
// Maven and Gradle order packages by their creation timestamp and not by their version string
|
||||||
|
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
|
||||||
|
})
|
||||||
|
|
||||||
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
|
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
@@ -89,6 +109,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||||||
}
|
}
|
||||||
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
|
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
|
||||||
|
|
||||||
|
latest := pds[len(pds)-1]
|
||||||
|
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
|
||||||
|
|
||||||
ext := strings.ToLower(filepath.Ext(params.Filename))
|
ext := strings.ToLower(filepath.Ext(params.Filename))
|
||||||
if isChecksumExtension(ext) {
|
if isChecksumExtension(ext) {
|
||||||
var hash []byte
|
var hash []byte
|
||||||
@@ -110,10 +133,15 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.PlainTextBytes(http.StatusOK, xmlMetadataWithHeader)
|
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
||||||
|
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
||||||
|
|
||||||
|
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
||||||
|
log.Error("write bytes failed: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func servePackageFile(ctx *context.Context, params parameters) {
|
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
||||||
packageName := params.GroupID + "-" + params.ArtifactID
|
packageName := params.GroupID + "-" + params.ArtifactID
|
||||||
|
|
||||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version)
|
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version)
|
||||||
@@ -165,6 +193,23 @@ func servePackageFile(ctx *context.Context, params parameters) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts := &context.ServeHeaderOptions{
|
||||||
|
ContentLength: &pb.Size,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
}
|
||||||
|
switch ext {
|
||||||
|
case extensionJar:
|
||||||
|
opts.ContentType = contentTypeJar
|
||||||
|
case extensionPom:
|
||||||
|
opts.ContentType = contentTypeXML
|
||||||
|
}
|
||||||
|
|
||||||
|
if !serveContent {
|
||||||
|
ctx.SetServeHeaders(opts)
|
||||||
|
ctx.Status(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
|
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
@@ -177,7 +222,9 @@ func servePackageFile(ctx *context.Context, params parameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
opts.Filename = pf.Name
|
||||||
|
|
||||||
|
ctx.ServeContent(s, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||||
@@ -272,7 +319,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it's the package pom file extract the metadata
|
// If it's the package pom file extract the metadata
|
||||||
if ext == ".pom" {
|
if ext == extensionPom {
|
||||||
pfci.IsLead = true
|
pfci.IsLead = true
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadPackageFileByName finds the version and serves the contents of a package
|
// DownloadPackageFileByName finds the version and serves the contents of a package
|
||||||
@@ -146,7 +149,10 @@ func DownloadPackageFileByName(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
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
|
// UploadPackage creates a new package
|
||||||
@@ -396,8 +402,9 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo
|
|||||||
|
|
||||||
func PackageSearch(ctx *context.Context) {
|
func PackageSearch(ctx *context.Context) {
|
||||||
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
Type: packages_model.TypeNpm,
|
Type: packages_model.TypeNpm,
|
||||||
|
IsInternal: util.OptionalBoolFalse,
|
||||||
Name: packages_model.SearchValue{
|
Name: packages_model.SearchValue{
|
||||||
ExactMatch: false,
|
ExactMatch: false,
|
||||||
Value: ctx.FormTrim("text"),
|
Value: ctx.FormTrim("text"),
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames
|
|||||||
Content: content,
|
Content: content,
|
||||||
Properties: &FeedEntryProperties{
|
Properties: &FeedEntryProperties{
|
||||||
Version: pd.Version.Version,
|
Version: pd.Version.Version,
|
||||||
NormalizedVersion: normalizeVersion(pd.SemVer),
|
NormalizedVersion: pd.Version.Version,
|
||||||
Authors: metadata.Authors,
|
Authors: metadata.Authors,
|
||||||
Dependencies: buildDependencyString(metadata),
|
Dependencies: buildDependencyString(metadata),
|
||||||
Description: metadata.Description,
|
Description: metadata.Description,
|
||||||
|
|||||||
@@ -5,15 +5,11 @@
|
|||||||
package nuget
|
package nuget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
|
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
|
||||||
@@ -96,8 +92,8 @@ func createRegistrationIndexResponse(l *linkBuilder, pds []*packages_model.Packa
|
|||||||
{
|
{
|
||||||
RegistrationPageURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
|
RegistrationPageURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
|
||||||
Count: len(pds),
|
Count: len(pds),
|
||||||
Lower: normalizeVersion(pds[0].SemVer),
|
Lower: pds[0].Version.Version,
|
||||||
Upper: normalizeVersion(pds[len(pds)-1].SemVer),
|
Upper: pds[len(pds)-1].Version.Version,
|
||||||
Items: items,
|
Items: items,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -174,7 +170,7 @@ type PackageVersionsResponse struct {
|
|||||||
func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *PackageVersionsResponse {
|
func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *PackageVersionsResponse {
|
||||||
versions := make([]string, 0, len(pds))
|
versions := make([]string, 0, len(pds))
|
||||||
for _, pd := range pds {
|
for _, pd := range pds {
|
||||||
versions = append(versions, normalizeVersion(pd.SemVer))
|
versions = append(versions, pd.Version.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PackageVersionsResponse{
|
return &PackageVersionsResponse{
|
||||||
@@ -249,15 +245,3 @@ func createSearchResult(l *linkBuilder, pds []*packages_model.PackageDescriptor)
|
|||||||
RegistrationIndexURL: l.GetRegistrationIndexURL(latest.Package.Name),
|
RegistrationIndexURL: l.GetRegistrationIndexURL(latest.Package.Name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeVersion removes the metadata
|
|
||||||
func normalizeVersion(v *version.Version) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
segments := v.Segments64()
|
|
||||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
|
||||||
pre := v.Prerelease()
|
|
||||||
if pre != "" {
|
|
||||||
fmt.Fprintf(&buf, "-%s", pre)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,19 +21,20 @@ func (a *Auth) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
|
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
|
||||||
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) {
|
||||||
token, err := auth_model.GetAccessTokenBySHA(req.Header.Get("X-NuGet-ApiKey"))
|
token, err := auth_model.GetAccessTokenBySHA(req.Header.Get("X-NuGet-ApiKey"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !(auth_model.IsErrAccessTokenNotExist(err) || auth_model.IsErrAccessTokenEmpty(err)) {
|
if !(auth_model.IsErrAccessTokenNotExist(err) || auth_model.IsErrAccessTokenEmpty(err)) {
|
||||||
log.Error("GetAccessTokenBySHA: %v", err)
|
log.Error("GetAccessTokenBySHA: %v", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := user_model.GetUserByID(token.UID)
|
u, err := user_model.GetUserByID(token.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
log.Error("GetUserByID: %v", err)
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token.UpdatedUnix = timeutil.TimeStampNow()
|
token.UpdatedUnix = timeutil.TimeStampNow()
|
||||||
@@ -41,5 +42,5 @@ func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataS
|
|||||||
log.Error("UpdateAccessToken: %v", err)
|
log.Error("UpdateAccessToken: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,7 +342,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
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 with the metadata contained in the uploaded nupgk file
|
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
|
||||||
@@ -552,7 +555,10 @@ func DownloadSymbolFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePackage hard deletes the package
|
// DeletePackage hard deletes the package
|
||||||
|
|||||||
@@ -271,5 +271,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,19 @@ import (
|
|||||||
packages_service "code.gitea.io/gitea/services/packages"
|
packages_service "code.gitea.io/gitea/services/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://www.python.org/dev/peps/pep-0503/#normalized-names
|
// https://peps.python.org/pep-0426/#name
|
||||||
var normalizer = strings.NewReplacer(".", "-", "_", "-")
|
var normalizer = strings.NewReplacer(".", "-", "_", "-")
|
||||||
var nameMatcher = regexp.MustCompile(`\A[a-zA-Z0-9\.\-_]+\z`)
|
var nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
|
||||||
|
|
||||||
// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||||
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)
|
var versionMatcher = regexp.MustCompile(`\Av?` +
|
||||||
|
`(?:[0-9]+!)?` + // epoch
|
||||||
|
`[0-9]+(?:\.[0-9]+)*` + // release segment
|
||||||
|
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
|
||||||
|
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
|
||||||
|
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
|
||||||
|
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
|
||||||
|
`\z`)
|
||||||
|
|
||||||
func apiError(ctx *context.Context, status int, obj interface{}) {
|
func apiError(ctx *context.Context, status int, obj interface{}) {
|
||||||
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
||||||
@@ -88,7 +95,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||||
@@ -121,7 +131,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
|
|
||||||
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
|
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
|
||||||
packageVersion := ctx.Req.FormValue("version")
|
packageVersion := ctx.Req.FormValue("version")
|
||||||
if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
|
if !isValidNameAndVersion(packageName, packageVersion) {
|
||||||
apiError(ctx, http.StatusBadRequest, "invalid name or version")
|
apiError(ctx, http.StatusBadRequest, "invalid name or version")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -139,7 +149,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
Name: packageName,
|
Name: packageName,
|
||||||
Version: packageVersion,
|
Version: packageVersion,
|
||||||
},
|
},
|
||||||
SemverCompatible: true,
|
SemverCompatible: false,
|
||||||
Creator: ctx.Doer,
|
Creator: ctx.Doer,
|
||||||
Metadata: &pypi_module.Metadata{
|
Metadata: &pypi_module.Metadata{
|
||||||
Author: ctx.Req.FormValue("author"),
|
Author: ctx.Req.FormValue("author"),
|
||||||
@@ -170,3 +180,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusCreated)
|
ctx.Status(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidNameAndVersion(packageName, packageVersion string) bool {
|
||||||
|
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
|
||||||
|
}
|
||||||
|
|||||||
39
routers/api/packages/pypi/pypi_test.go
Normal file
39
routers/api/packages/pypi/pypi_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pypi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsValidNameAndVersion(t *testing.T) {
|
||||||
|
// The test cases below were created from the following Python PEPs:
|
||||||
|
// https://peps.python.org/pep-0426/#name
|
||||||
|
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||||
|
|
||||||
|
// Valid Cases
|
||||||
|
assert.True(t, isValidNameAndVersion("A", "1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("Test.Name.1234", "1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test_name", "1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "v1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "2012.4"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1.0.1-alpha"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1.0.1a1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1.0b2.r345.dev456"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1!1.0.1"))
|
||||||
|
assert.True(t, isValidNameAndVersion("test-name", "1.0.1+local.1"))
|
||||||
|
|
||||||
|
// Invalid Cases
|
||||||
|
assert.False(t, isValidNameAndVersion(".test-name", "1.0.1"))
|
||||||
|
assert.False(t, isValidNameAndVersion("test!name", "1.0.1"))
|
||||||
|
assert.False(t, isValidNameAndVersion("-test-name", "1.0.1"))
|
||||||
|
assert.False(t, isValidNameAndVersion("test-name-", "1.0.1"))
|
||||||
|
assert.False(t, isValidNameAndVersion("test-name", "a1.0.1"))
|
||||||
|
assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa"))
|
||||||
|
assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta"))
|
||||||
|
}
|
||||||
@@ -77,7 +77,9 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetServeHeaders(filename + ".gz")
|
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||||
|
Filename: filename + ".gz",
|
||||||
|
})
|
||||||
|
|
||||||
zw := gzip.NewWriter(ctx.Resp)
|
zw := gzip.NewWriter(ctx.Resp)
|
||||||
defer zw.Close()
|
defer zw.Close()
|
||||||
@@ -115,7 +117,9 @@ func ServePackageSpecification(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetServeHeaders(filename)
|
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||||
|
Filename: filename,
|
||||||
|
})
|
||||||
|
|
||||||
zw := zlib.NewWriter(ctx.Resp)
|
zw := zlib.NewWriter(ctx.Resp)
|
||||||
defer zw.Close()
|
defer zw.Close()
|
||||||
@@ -188,7 +192,10 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
|
||||||
|
|||||||
@@ -235,5 +235,8 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
|
ctx.ServeContent(s, &context.ServeHeaderOptions{
|
||||||
|
Filename: pf.Name,
|
||||||
|
LastModified: pf.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,13 +232,10 @@ func reqExploreSignIn() func(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
func reqBasicAuth() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if ctx.IsSigned && setting.Service.EnableReverseProxyAuth && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !ctx.Context.IsBasicAuth {
|
if !ctx.Context.IsBasicAuth {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqBasicOrRevProxyAuth", "auth required")
|
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.CheckForOTP()
|
ctx.CheckForOTP()
|
||||||
@@ -597,9 +594,6 @@ func buildAuthGroup() *auth.Group {
|
|||||||
&auth.HTTPSign{},
|
&auth.HTTPSign{},
|
||||||
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
||||||
)
|
)
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
|
||||||
group.Add(&auth.ReverseProxy{})
|
|
||||||
}
|
|
||||||
specialAdd(group)
|
specialAdd(group)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
@@ -689,7 +683,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||||||
m.Combo("").Get(user.ListAccessTokens).
|
m.Combo("").Get(user.ListAccessTokens).
|
||||||
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
|
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
|
||||||
m.Combo("/{id}").Delete(user.DeleteAccessToken)
|
m.Combo("/{id}").Delete(user.DeleteAccessToken)
|
||||||
}, reqBasicOrRevProxyAuth())
|
}, reqBasicAuth())
|
||||||
}, context_service.UserAssignmentAPI())
|
}, context_service.UserAssignmentAPI())
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -898,7 +892,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||||||
m.Group("/{index}", func() {
|
m.Group("/{index}", func() {
|
||||||
m.Combo("").Get(repo.GetIssue).
|
m.Combo("").Get(repo.GetIssue).
|
||||||
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
||||||
Delete(reqToken(), reqAdmin(), repo.DeleteIssue)
|
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
|
||||||
m.Group("/comments", func() {
|
m.Group("/comments", func() {
|
||||||
m.Combo("").Get(repo.ListIssueComments).
|
m.Combo("").Get(repo.ListIssueComments).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||||
|
|||||||
@@ -252,42 +252,50 @@ func ListBranches(ctx *context.APIContext) {
|
|||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/BranchList"
|
// "$ref": "#/responses/BranchList"
|
||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
var totalNumOfBranches int
|
||||||
skip, _ := listOptions.GetStartEnd()
|
var apiBranches []*api.Branch
|
||||||
branches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
apiBranches := make([]*api.Branch, 0, len(branches))
|
listOptions := utils.GetListOptions(ctx)
|
||||||
for i := range branches {
|
|
||||||
c, err := branches[i].GetCommit()
|
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
|
||||||
|
skip, _ := listOptions.GetStartEnd()
|
||||||
|
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Skip if this branch doesn't exist anymore.
|
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
|
||||||
if git.IsErrNotExist(err) {
|
return
|
||||||
totalNumOfBranches--
|
}
|
||||||
continue
|
|
||||||
|
apiBranches = make([]*api.Branch, 0, len(branches))
|
||||||
|
for i := range branches {
|
||||||
|
c, err := branches[i].GetCommit()
|
||||||
|
if err != nil {
|
||||||
|
// Skip if this branch doesn't exist anymore.
|
||||||
|
if git.IsErrNotExist(err) {
|
||||||
|
total--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||||
return
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiBranches = append(apiBranches, apiBranch)
|
||||||
}
|
}
|
||||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
|
||||||
if err != nil {
|
totalNumOfBranches = total
|
||||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiBranches = append(apiBranches, apiBranch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
|
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
|
||||||
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
|
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
|
||||||
ctx.JSON(http.StatusOK, &apiBranches)
|
ctx.JSON(http.StatusOK, apiBranches)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranchProtection gets a branch protection
|
// GetBranchProtection gets a branch protection
|
||||||
|
|||||||
@@ -341,7 +341,11 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fr.Close()
|
defer fr.Close()
|
||||||
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
|
|
||||||
|
ctx.ServeContent(fr, &context.ServeHeaderOptions{
|
||||||
|
Filename: downloadName,
|
||||||
|
LastModified: archiver.CreatedUnix.AsLocalTime(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEditorconfig get editor config of a repository
|
// GetEditorconfig get editor config of a repository
|
||||||
|
|||||||
@@ -491,6 +491,11 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||||||
issue := pr.Issue
|
issue := pr.Issue
|
||||||
issue.Repo = ctx.Repo.Repository
|
issue.Repo = ctx.Repo.Repository
|
||||||
|
|
||||||
|
if err := issue.LoadAttributes(ctx); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWrite(unit.TypePullRequests) {
|
if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWrite(unit.TypePullRequests) {
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@@ -1443,7 +1448,11 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
|||||||
end = totalNumberOfFiles
|
end = totalNumberOfFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
apiFiles := make([]*api.ChangedFile, 0, end-start)
|
lenFiles := end - start
|
||||||
|
if lenFiles < 0 {
|
||||||
|
lenFiles = 0
|
||||||
|
}
|
||||||
|
apiFiles := make([]*api.ChangedFile, 0, lenFiles)
|
||||||
for i := start; i < end; i++ {
|
for i := start; i < end; i++ {
|
||||||
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
|
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
|
|||||||
ctx.Error(http.StatusBadRequest, "ref/sha not given", nil)
|
ctx.Error(http.StatusBadRequest, "ref/sha not given", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sha = utils.MustConvertToSHA1(ctx.Context, sha)
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ func ResolveRefOrSha(ctx *context.APIContext, ref string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sha = MustConvertToSHA1(ctx.Context, sha)
|
||||||
|
|
||||||
if ctx.Repo.GitRepo != nil {
|
if ctx.Repo.GitRepo != nil {
|
||||||
err := ctx.Repo.GitRepo.AddLastCommitCache(ctx.Repo.Repository.GetCommitsCountCacheKey(ref, ref != sha), ctx.Repo.Repository.FullName(), sha)
|
err := ctx.Repo.GitRepo.AddLastCommitCache(ctx.Repo.Repository.GetCommitsCountCacheKey(ref, ref != sha), ctx.Repo.Repository.FullName(), sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,3 +68,30 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
|
|||||||
}
|
}
|
||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertToSHA1 returns a full-length SHA1 from a potential ID string
|
||||||
|
func ConvertToSHA1(ctx *context.Context, commitID string) (git.SHA1, error) {
|
||||||
|
if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) {
|
||||||
|
sha1, err := git.NewIDFromString(commitID)
|
||||||
|
if err == nil {
|
||||||
|
return sha1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, ctx.Repo.Repository.RepoPath())
|
||||||
|
if err != nil {
|
||||||
|
return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
return gitRepo.ConvertToSHA1(commitID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1
|
||||||
|
func MustConvertToSHA1(ctx *context.Context, commitID string) string {
|
||||||
|
sha, err := ConvertToSHA1(ctx, commitID)
|
||||||
|
if err != nil {
|
||||||
|
return commitID
|
||||||
|
}
|
||||||
|
return sha.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -53,50 +51,44 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
|
|||||||
buf = buf[:n]
|
buf = buf[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute)
|
opts := &context.ServeHeaderOptions{
|
||||||
|
Filename: path.Base(filePath),
|
||||||
|
}
|
||||||
|
|
||||||
if size >= 0 {
|
if size >= 0 {
|
||||||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
|
opts.ContentLength = &size
|
||||||
} else {
|
} else {
|
||||||
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
|
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := path.Base(filePath)
|
|
||||||
sniffedType := typesniffer.DetectContentType(buf)
|
sniffedType := typesniffer.DetectContentType(buf)
|
||||||
isPlain := sniffedType.IsText() || ctx.FormBool("render")
|
isPlain := sniffedType.IsText() || ctx.FormBool("render")
|
||||||
mimeType := ""
|
|
||||||
charset := ""
|
|
||||||
|
|
||||||
if setting.MimeTypeMap.Enabled {
|
if setting.MimeTypeMap.Enabled {
|
||||||
fileExtension := strings.ToLower(filepath.Ext(fileName))
|
fileExtension := strings.ToLower(filepath.Ext(filePath))
|
||||||
mimeType = setting.MimeTypeMap.Map[fileExtension]
|
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
|
||||||
}
|
}
|
||||||
|
|
||||||
if mimeType == "" {
|
if opts.ContentType == "" {
|
||||||
if sniffedType.IsBrowsableBinaryType() {
|
if sniffedType.IsBrowsableBinaryType() {
|
||||||
mimeType = sniffedType.GetMimeType()
|
opts.ContentType = sniffedType.GetMimeType()
|
||||||
} else if isPlain {
|
} else if isPlain {
|
||||||
mimeType = "text/plain"
|
opts.ContentType = "text/plain"
|
||||||
} else {
|
} else {
|
||||||
mimeType = typesniffer.ApplicationOctetStream
|
opts.ContentType = typesniffer.ApplicationOctetStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPlain {
|
if isPlain {
|
||||||
|
var charset string
|
||||||
charset, err = charsetModule.DetectEncoding(buf)
|
charset, err = charsetModule.DetectEncoding(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
|
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
|
||||||
charset = "utf-8"
|
charset = "utf-8"
|
||||||
}
|
}
|
||||||
|
opts.ContentTypeCharset = strings.ToLower(charset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if charset != "" {
|
|
||||||
ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset))
|
|
||||||
} else {
|
|
||||||
ctx.Resp.Header().Set("Content-Type", mimeType)
|
|
||||||
}
|
|
||||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
|
||||||
|
|
||||||
isSVG := sniffedType.IsSvgImage()
|
isSVG := sniffedType.IsSvgImage()
|
||||||
|
|
||||||
// serve types that can present a security risk with CSP
|
// serve types that can present a security risk with CSP
|
||||||
@@ -109,16 +101,12 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
|
|||||||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
||||||
}
|
}
|
||||||
|
|
||||||
disposition := "inline"
|
opts.Disposition = "inline"
|
||||||
if isSVG && !setting.UI.SVG.Enabled {
|
if isSVG && !setting.UI.SVG.Enabled {
|
||||||
disposition = "attachment"
|
opts.Disposition = "attachment"
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode filename per https://datatracker.ietf.org/doc/html/rfc5987
|
ctx.SetServeHeaders(opts)
|
||||||
encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName)
|
|
||||||
|
|
||||||
ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName)
|
|
||||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
|
||||||
|
|
||||||
_, err = ctx.Resp.Write(buf)
|
_, err = ctx.Resp.Write(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -76,21 +76,31 @@ func InitGitServices() {
|
|||||||
mustInit(repo_service.Init)
|
mustInit(repo_service.Init)
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncAppPathForGit(ctx context.Context) error {
|
func syncAppConfForGit(ctx context.Context) error {
|
||||||
runtimeState := new(system.RuntimeState)
|
runtimeState := new(system.RuntimeState)
|
||||||
if err := system.AppState.Get(runtimeState); err != nil {
|
if err := system.AppState.Get(runtimeState); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated := false
|
||||||
if runtimeState.LastAppPath != setting.AppPath {
|
if runtimeState.LastAppPath != setting.AppPath {
|
||||||
log.Info("AppPath changed from '%s' to '%s'", runtimeState.LastAppPath, setting.AppPath)
|
log.Info("AppPath changed from '%s' to '%s'", runtimeState.LastAppPath, setting.AppPath)
|
||||||
|
runtimeState.LastAppPath = setting.AppPath
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if runtimeState.LastCustomConf != setting.CustomConf {
|
||||||
|
log.Info("CustomConf changed from '%s' to '%s'", runtimeState.LastCustomConf, setting.CustomConf)
|
||||||
|
runtimeState.LastCustomConf = setting.CustomConf
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
log.Info("re-sync repository hooks ...")
|
log.Info("re-sync repository hooks ...")
|
||||||
mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
|
mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
|
||||||
|
|
||||||
log.Info("re-write ssh public keys ...")
|
log.Info("re-write ssh public keys ...")
|
||||||
mustInit(asymkey_model.RewriteAllPublicKeys)
|
mustInit(asymkey_model.RewriteAllPublicKeys)
|
||||||
|
|
||||||
runtimeState.LastAppPath = setting.AppPath
|
|
||||||
return system.AppState.Set(runtimeState)
|
return system.AppState.Set(runtimeState)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -153,7 +163,7 @@ func GlobalInitInstalled(ctx context.Context) {
|
|||||||
mustInit(repo_migrations.Init)
|
mustInit(repo_migrations.Init)
|
||||||
eventsource.GetManager().Init()
|
eventsource.GetManager().Init()
|
||||||
|
|
||||||
mustInitCtx(ctx, syncAppPathForGit)
|
mustInitCtx(ctx, syncAppConfForGit)
|
||||||
|
|
||||||
mustInit(ssh.Init)
|
mustInit(ssh.Init)
|
||||||
|
|
||||||
|
|||||||
@@ -473,12 +473,16 @@ func SubmitInstall(ctx *context.Context) {
|
|||||||
|
|
||||||
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
|
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
|
||||||
|
|
||||||
var internalToken string
|
// the internal token could be read from INTERNAL_TOKEN or INTERNAL_TOKEN_URI (the file is guaranteed to be non-empty)
|
||||||
if internalToken, err = generate.NewInternalToken(); err != nil {
|
// if there is no InternalToken, generate one and save to security.INTERNAL_TOKEN
|
||||||
ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
|
if setting.InternalToken == "" {
|
||||||
return
|
var internalToken string
|
||||||
|
if internalToken, err = generate.NewInternalToken(); err != nil {
|
||||||
|
ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
|
||||||
}
|
}
|
||||||
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
|
|
||||||
|
|
||||||
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
|
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
|
||||||
if setting.SecretKey == "" {
|
if setting.SecretKey == "" {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
|
|||||||
func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
|
func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
|
||||||
return &smtp.Source{
|
return &smtp.Source{
|
||||||
Auth: form.SMTPAuth,
|
Auth: form.SMTPAuth,
|
||||||
Addr: form.SMTPAddr,
|
Host: form.SMTPHost,
|
||||||
Port: form.SMTPPort,
|
Port: form.SMTPPort,
|
||||||
AllowedDomains: form.AllowedDomains,
|
AllowedDomains: form.AllowedDomains,
|
||||||
ForceSMTPS: form.ForceSMTPS,
|
ForceSMTPS: form.ForceSMTPS,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
system_module "code.gitea.io/gitea/modules/system"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
|
|
||||||
@@ -203,7 +202,11 @@ func ChangeConfig(ctx *context.Context) {
|
|||||||
value := ctx.FormString("value")
|
value := ctx.FormString("value")
|
||||||
version := ctx.FormInt("version")
|
version := ctx.FormInt("version")
|
||||||
|
|
||||||
if err := system_module.SetSetting(key, value, version); err != nil {
|
if err := system_model.SetSetting(&system_model.Setting{
|
||||||
|
SettingKey: key,
|
||||||
|
SettingValue: value,
|
||||||
|
Version: version,
|
||||||
|
}); err != nil {
|
||||||
log.Error("set setting failed: %v", err)
|
log.Error("set setting failed: %v", err)
|
||||||
ctx.JSON(http.StatusOK, map[string]string{
|
ctx.JSON(http.StatusOK, map[string]string{
|
||||||
"err": ctx.Tr("admin.config.set_setting_failed", key),
|
"err": ctx.Tr("admin.config.set_setting_failed", key),
|
||||||
|
|||||||
@@ -783,6 +783,13 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register last login
|
||||||
|
user.SetLastLogin()
|
||||||
|
if err := user_model.UpdateUserCols(ctx, user, "last_login_unix"); err != nil {
|
||||||
|
ctx.ServerError("UpdateUserCols", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
|
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
|
||||||
ctx.Redirect(setting.AppSubURL + "/")
|
ctx.Redirect(setting.AppSubURL + "/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
|
|||||||
orderBy = "`user`.updated_unix ASC"
|
orderBy = "`user`.updated_unix ASC"
|
||||||
case "reversealphabetically":
|
case "reversealphabetically":
|
||||||
orderBy = "`user`.name DESC"
|
orderBy = "`user`.name DESC"
|
||||||
|
case "lastlogin":
|
||||||
|
orderBy = "`user`.last_login_unix ASC"
|
||||||
|
case "reverselastlogin":
|
||||||
|
orderBy = "`user`.last_login_unix DESC"
|
||||||
case UserSearchDefaultSortType: // "alphabetically"
|
case UserSearchDefaultSortType: // "alphabetically"
|
||||||
default:
|
default:
|
||||||
orderBy = "`user`.name ASC"
|
orderBy = "`user`.name ASC"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user