Compare commits

..

1 Commits

Author SHA1 Message Date
techknowlogick
49a4e4555a [skip ci] Updated translations via Crowdin 2022-10-26 00:20:58 +00:00
254 changed files with 1829 additions and 3828 deletions

View File

@@ -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", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"] exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services"] include_dir = ["cmd", "models", "modules", "options", "routers", "services", "templates"]
exclude_regex = ["_test.go$", "_gen.go$"] exclude_regex = ["_test.go$", "_gen.go$"]

View File

@@ -941,8 +941,7 @@ steps:
image: plugins/hugo:latest image: plugins/hugo:latest
pull: always pull: always
commands: commands:
# https://github.com/drone-plugins/drone-hugo/issues/36 - apk add --no-cache make bash curl
- 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

View File

@@ -4,317 +4,6 @@ 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.3](https://github.com/go-gitea/gitea/releases/tag/v1.18.3) - 2023-01-23
* SECURITY
* Prevent multiple `To` recipients (#22566) (#22569)
* BUGFIXES
* Truncate commit summary on repo files table. (#22551) (#22552)
* Mute all links in issue timeline (#22534)
## [1.18.2](https://github.com/go-gitea/gitea/releases/tag/v1.18.2) - 2023-01-19
* BUGFIXES
* When updating by rebase we need to set the environment for head repo (#22535) (#22536)
* Fix issue not auto-closing when it includes a reference to a branch (#22514) (#22521)
* Fix invalid issue branch reference if not specified in template (#22513) (#22520)
* Fix 500 error viewing pull request when fork has pull requests disabled (#22512) (#22515)
* Reliable selection of admin user (#22509) (#22511)
* Set disable_gravatar/enable_federated_avatar when offline mode is true (#22479) (#22496)
* BUILD
* cgo cross-compile for freebsd (#22397) (#22519)
## [1.18.1](https://github.com/go-gitea/gitea/releases/tag/v1.18.1) - 2023-01-17
* API
* Add `sync_on_commit` option for push mirrors api (#22271) (#22292)
* BUGFIXES
* Update `github.com/zeripath/zapx/v15` (#22485)
* Fix pull request API field `closed_at` always being `null` (#22482) (#22483)
* Fix container blob mount (#22226) (#22476)
* Fix error when calculating repository size (#22392) (#22474)
* Fix Operator does not exist bug on explore page with ONLY_SHOW_RELEVANT_REPOS (#22454) (#22472)
* Fix environments for KaTeX and error reporting (#22453) (#22473)
* Remove the netgo tag for Windows build (#22467) (#22468)
* Fix migration from GitBucket (#22477) (#22465)
* Prevent panic on looking at api "git" endpoints for empty repos (#22457) (#22458)
* Fix PR status layout on mobile (#21547) (#22441)
* Fix wechatwork webhook sends empty content in PR review (#21762) (#22440)
* Remove duplicate "Actions" label in mobile view (#21974) (#22439)
* Fix leaving organization bug on user settings -> orgs (#21983) (#22438)
* Fixed colour transparency regex matching in project board sorting (#22092) (#22437)
* Correctly handle select on multiple channels in Queues (#22146) (#22428)
* Prepend refs/heads/ to issue template refs (#20461) (#22427)
* Restore function to "Show more" buttons (#22399) (#22426)
* Continue GCing other repos on error in one repo (#22422) (#22425)
* Allow HOST has no port (#22280) (#22409)
* Fix omit avatar_url in discord payload when empty (#22393) (#22394)
* Don't display stop watch top bar icon when disabled and hidden when click other place (#22374) (#22387)
* Don't lookup mail server when using sendmail (#22300) (#22383)
* Fix gravatar disable bug (#22337)
* Fix update settings table on install (#22326) (#22327)
* Fix sitemap (#22272) (#22320)
* Fix code search title translation (#22285) (#22316)
* Fix due date rendering the wrong date in issue (#22302) (#22306)
* Fix get system setting bug when enabled redis cache (#22298)
* Fix bug of DisableGravatar default value (#22297)
* Fix key signature error page (#22229) (#22230)
* TESTING
* Remove test session cache to reduce possible concurrent problem (#22199) (#22429)
* MISC
* Restore previous official review when an official review is deleted (#22449) (#22460)
* Log STDERR of external renderer when it fails (#22442) (#22444)
## [1.18.0](https://github.com/go-gitea/gitea/releases/tag/1.18.0) - 2022-12-22
* SECURITY
* Remove ReverseProxy authentication from the API (#22219) (#22251)
* Support Go Vulnerability Management (#21139)
* Forbid HTML string tooltips (#20935)
* BREAKING
* Rework mailer settings (#18982)
* Remove U2F support (#20141)
* Refactor `i18n` to `locale` (#20153)
* Enable contenthash in filename for dynamic assets (#20813)
* FEATURES
* Add color previews in markdown (#21474)
* Allow package version sorting (#21453)
* Add support for Chocolatey/NuGet v2 API (#21393)
* Add API endpoint to get changed files of a PR (#21177)
* Add filetree on left of diff view (#21012)
* Support Issue forms and PR forms (#20987)
* Add support for Vagrant packages (#20930)
* Add support for `npm unpublish` (#20688)
* Add badge capabilities to users (#20607)
* Add issue filter for Author (#20578)
* Add KaTeX rendering to Markdown. (#20571)
* Add support for Pub packages (#20560)
* Support localized README (#20508)
* Add support mCaptcha as captcha provider (#20458)
* Add team member invite by email (#20307)
* Added email notification option to receive all own messages (#20179)
* Switch Unicode Escaping to a VSCode-like system (#19990)
* Add user/organization code search (#19977)
* Only show relevant repositories on explore page (#19361)
* User keypairs and HTTP signatures for ActivityPub federation using go-ap (#19133)
* Add sitemap support (#18407)
* Allow creation of OAuth2 applications for orgs (#18084)
* Add system setting table with cache and also add cache supports for user setting (#18058)
* Add pages to view watched repos and subscribed issues/PRs (#17156)
* Support Proxy protocol (#12527)
* Implement sync push mirror on commit (#19411)
* API
* Allow empty assignees on pull request edit (#22150) (#22214)
* Make external issue tracker regexp configurable via API (#21338)
* Add name field for org api (#21270)
* Show teams with no members if user is admin (#21204)
* Add latest commit's SHA to content response (#20398)
* Add allow_rebase_update, default_delete_branch_after_merge to repository api response (#20079)
* Add new endpoints for push mirrors management (#19841)
* ENHANCEMENTS
* Add setting to disable the git apply step in test patch (#22130) (#22170)
* Multiple improvements for comment edit diff (#21990) (#22007)
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21928)
* Fix flex layout for repo list icons (#21896) (#21920)
* Fix vertical align of committer avatar rendered by email address (#21884) (#21918)
* Fix setting HTTP headers after write (#21833) (#21877)
* Color and Style enhancements (#21784, #21799) (#21868)
* Ignore line anchor links with leading zeroes (#21728) (#21776)
* Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
* Use CSS color-scheme instead of invert (#21616) (#21623)
* Respect user's locale when rendering the date range in the repo activity page (#21410)
* Change `commits-table` column width (#21564)
* Refactor git command arguments and make all arguments to be safe to be used (#21535)
* CSS color enhancements (#21534)
* Add link to user profile in markdown mention only if user exists (#21533, #21554)
* Add option to skip index dirs (#21501)
* Diff file tree tweaks (#21446)
* Localize all timestamps (#21440)
* Add `code` highlighting in issue titles (#21432)
* Use Name instead of DisplayName in LFS Lock (#21415)
* Consolidate more CSS colors into variables (#21402)
* Redirect to new repository owner (#21398)
* Use ISO date format instead of hard-coded English date format for date range in repo activity page (#21396)
* Use weighted algorithm for string matching when finding files in repo (#21370)
* Show private data in feeds (#21369)
* Refactor parseTreeEntries, speed up tree list (#21368)
* Add GET and DELETE endpoints for Docker blob uploads (#21367)
* Add nicer error handling on template compile errors (#21350)
* Add `stat` to `ToCommit` function for speed (#21337)
* Support instance-wide OAuth2 applications (#21335)
* Record OAuth client type at registration (#21316)
* Add new CSS variables --color-accent and --color-small-accent (#21305)
* Improve error descriptions for unauthorized_client (#21292)
* Case-insensitive "find files in repo" (#21269)
* Consolidate more CSS rules, fix inline code on arc-green (#21260)
* Log real ip of requests from ssh (#21216)
* Save files in local storage as group readable (#21198)
* Enable fluid page layout on medium size viewports (#21178)
* File header tweaks (#21175)
* Added missing headers on user packages page (#21172)
* Display image digest for container packages (#21170)
* Skip dirty check for team forms (#21154)
* Keep path when creating a new branch (#21153)
* Remove fomantic image module (#21145)
* Make labels clickable in the comments section. (#21137)
* Sort branches and tags by date descending (#21136)
* Better repo API unit checks (#21130)
* Improve commit status icons (#21124)
* Limit length of repo description and repo url input fields (#21119)
* Show .editorconfig errors in frontend (#21088)
* Allow poster to choose reviewers (#21084)
* Remove black labels and CSS cleanup (#21003)
* Make e-mail sanity check more precise (#20991)
* Use native inputs in whitespace dropdown (#20980)
* Enhance package date display (#20928)
* Display total blob size of a package version (#20927)
* Show language name on hover (#20923)
* Show instructions for all generic package files (#20917)
* Refactor AssertExistsAndLoadBean to use generics (#20797)
* Move the official website link at the footer of gitea (#20777)
* Add support for full name in reverse proxy auth (#20776)
* Remove useless JS operation for relative time tooltips (#20756)
* Replace some icons with SVG (#20741)
* Change commit status icons to SVG (#20736)
* Improve single repo action for issue and pull requests (#20730)
* Allow multiple files in generic packages (#20661)
* Add option to create new issue from /issues page (#20650)
* Background color of private list-items updated (#20630)
* Added search input field to issue filter (#20623)
* Increase default item listing size `ISSUE_PAGING_NUM` to 20 (#20547)
* Modify milestone search keywords to be case insensitive again (#20513)
* Show hint to link package to repo when viewing empty repo package list (#20504)
* Add Tar ZSTD support (#20493)
* Make code review checkboxes clickable (#20481)
* Add "X-Gitea-Object-Type" header for GET `/raw/` & `/media/` API (#20438)
* Display project in issue list (#20434)
* Prepend commit message to template content when opening a new PR (#20429)
* Replace fomantic popup module with tippy.js (#20428)
* Allow to specify colors for text in markup (#20363)
* Allow access to the Public Organization Member lists with minimal permissions (#20330)
* Use default values when provided values are empty (#20318)
* Vertical align navbar avatar at middle (#20302)
* Delete cancel button in repo creation page (#21381)
* Include login_name in adminCreateUser response (#20283)
* fix: icon margin in user/settings/repos (#20281)
* Remove blue text on migrate page (#20273)
* Modify milestone search keywords to be case insensitive (#20266)
* Move some files into models' sub packages (#20262)
* Add tooltip to repo icons in explore page (#20241)
* Remove deprecated licenses (#20222)
* Webhook for Wiki changes (#20219)
* Share HTML template renderers and create a watcher framework (#20218)
* Allow enable LDAP source and disable user sync via CLI (#20206)
* Adds a checkbox to select all issues/PRs (#20177)
* Refactor `i18n` to `locale` (#20153)
* Disable status checks in template if none found (#20088)
* Allow manager logging to set SQL (#20064)
* Add order by for assignee no sort issue (#20053)
* Take a stab at porting existing components to Vue3 (#20044)
* Add doctor command to write commit-graphs (#20007)
* Add support for authentication based on reverse proxy email (#19949)
* Enable spellcheck for EasyMDE, use contenteditable mode (#19776)
* Allow specifying SECRET_KEY_URI, similar to INTERNAL_TOKEN_URI (#19663)
* Rework mailer settings (#18982)
* Add option to purge users (#18064)
* Add author search input (#21246)
* Make rss/atom identifier globally unique (#21550)
* BUGFIXES
* Auth interface return error when verify failure (#22119) (#22259)
* Use complete SHA to create and query commit status (#22244) (#22257)
* Update bleve and zapx to fix unaligned atomic (#22031) (#22218)
* Prevent panic in doctor command when running default checks (#21791) (#21807)
* Load GitRepo in API before deleting issue (#21720) (#21796)
* Ignore line anchor links with leading zeroes (#21728) (#21776)
* Set last login when activating account (#21731) (#21755)
* Fix UI language switching bug (#21597) (#21749)
* Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
* Allow local package identifiers for PyPI packages (#21690) (#21727)
* Deal with markdown template without metadata (#21639) (#21654)
* Fix opaque background on mermaid diagrams (#21642) (#21652)
* Fix repository adoption on Windows (#21646) (#21650)
* Sync git hooks when config file path changed (#21619) (#21626)
* Fix 500 on PR files API (#21602) (#21607)
* Fix `Timestamp.IsZero` (#21593) (#21603)
* Fix viewing user subscriptions (#21482)
* Fix mermaid-related bugs (#21431)
* Fix branch dropdown shifting on page load (#21428)
* Fix default theme-auto selector when nologin (#21346)
* Fix and improve incorrect error messages (#21342)
* Fix formatted link for PR review notifications to matrix (#21319)
* Center-aligning content of WebAuthN page (#21127)
* Remove follow from commits by file (#20765)
* Fix commit status popup (#20737)
* Fix init mail render logic (#20704)
* Use correct page size for link header pagination (#20546)
* Preserve unix socket file (#20499)
* Use tippy.js for context popup (#20393)
* Add missing parameter for error in log message (#20144)
* Do not allow organisation owners add themselves as collaborator (#20043)
* Rework file highlight rendering and fix yaml copy-paste (#19967)
* Improve code diff highlight, fix incorrect rendered diff result (#19958)
* TESTING
* Improve OAuth integration tests (#21390)
* Add playwright tests (#20123)
* BUILD
* Switch to building with go1.19 (#20695)
* Update JS dependencies, adjust eslint (#20659)
* Add more linters to improve code readability (#19989)
## [1.17.4](https://github.com/go-gitea/gitea/releases/tag/1.17.4) - 2022-12-21
* SECURITY
* Do not allow Ghost access to limited visible user/org (#21849) (#21875)
* Fix package access for admins and inactive users (#21580) (#21592)
* ENHANCEMENTS
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21927)
* Fix vertical align of committer avatar rendered by email address (#21884) (#21919)
* Fix setting HTTP headers after write (#21833) (#21874)
* Ignore line anchor links with leading zeroes (#21728) (#21777)
* Enable Monaco automaticLayout (#21516)
* BUGFIXES
* Do not list active repositories as unadopted (#22034) (#22167)
* Correctly handle moved files in apply patch (#22118) (#22136)
* Fix condition for is_internal (#22095) (#22131)
* Fix permission check on issue/pull lock (#22114)
* Fix sorting admin user list by last login (#22081) (#22106)
* Workaround for container registry push/pull errors (#21862) (#22069)
* Fix issue/PR numbers (#22037) (#22045)
* Handle empty author names (#21902) (#22028)
* Fix ListBranches to handle empty case (#21921) (#22025)
* Fix enabling partial clones on 1.17 (#21809)
* Prevent panic in doctor command when running default checks (#21791) (#21808)
* Upgrade golang.org/x/crypto (#21792) (#21794)
* Init git module before database migration (#21764) (#21766)
* Set last login when activating account (#21731) (#21754)
* Add HEAD fix to gitea doctor (#21352) (#21751)
* Fix UI language switching bug (#21597) (#21748)
* Remove semver compatible flag and change pypi to an array of test cases (#21708) (#21729)
* Allow local package identifiers for PyPI packages (#21690) (#21726)
* Fix repository adoption on Windows (#21646) (#21651)
* Sync git hooks when config file path changed (#21619) (#21625)
* Added check for disabled Packages (#21540) (#21614)
* Fix `Timestamp.IsZero` (#21593) (#21604)
* Fix issues count bug (#21600)
* Support binary deploy in npm packages (#21589)
* Update milestone counters when issue is deleted (#21459) (#21586)
* SessionUser protection against nil pointer dereference (#21581)
* Case-insensitive NuGet symbol file GUID (#21409) (#21575)
* Suppress `ExternalLoginUserNotExist` error (#21504) (#21572)
* Prevent Authorization header for presigned LFS urls (#21531) (#21569)
* Update binding to fix bugs (#21560)
* Fix generating compare link (#21519) (#21530)
* Ignore error when retrieving changed PR review files (#21487) (#21524)
* Fix incorrect notification commit url (#21479) (#21483)
* Display total commit count in hook message (#21400) (#21481)
* Enforce grouped NuGet search results (#21442) (#21480)
* Return 404 when user is not found on avatar (#21476) (#21477)
* Normalize NuGet package version on upload (#22186) (#22201)
* MISC
* Check for zero time instant in TimeStamp.IsZero() (#22171) (#22173)
* Fix warn in database structs sync (#22111)
* Allow for resolution of NPM registry paths that match upstream (#21568) (#21723)
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15 ## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
* SECURITY * SECURITY

View File

@@ -31,7 +31,6 @@ 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 \
@@ -69,6 +68,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/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD [] CMD []

View File

@@ -358,7 +358,7 @@ watch-frontend: node-check node_modules
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check watch-backend: go-check
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml $(GO) run $(AIR_PACKAGE) -c .air.toml
.PHONY: test .PHONY: test
test: test-frontend test-backend test: test-frontend test-backend
@@ -733,16 +733,16 @@ $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-docs release-check release: frontend generate release-windows release-linux release-darwin release-copy release-compress vendor release-sources release-docs release-check
$(DIST_DIRS): $(DIST_DIRS):
mkdir -p $(DIST_DIRS) mkdir -p $(DIST_DIRS)
.PHONY: release-windows .PHONY: release-windows
release-windows: | $(DIST_DIRS) release-windows: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq (,$(findstring gogit,$(TAGS))) ifeq (,$(findstring gogit,$(TAGS)))
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
endif endif
ifeq ($(CI),true) ifeq ($(CI),true)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
@@ -762,13 +762,6 @@ ifeq ($(CI),true)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-freebsd
release-freebsd: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
ifeq ($(CI),true)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-copy .PHONY: release-copy
release-copy: | $(DIST_DIRS) release-copy: | $(DIST_DIRS)
cd $(DIST); for file in `find . -type f -name "*"`; do cp $${file} ./release/; done; cd $(DIST); for file in `find . -type f -name "*"`; do cp $${file} ./release/; done;

2
assets/emoji.json generated

File diff suppressed because one or more lines are too long

View File

@@ -26,7 +26,7 @@ import (
const ( const (
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
maxUnicodeVersion = 14 maxUnicodeVersion = 12
) )
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")

View File

@@ -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: "host", Name: "addr",
Value: "", Value: "",
Usage: "SMTP Host", Usage: "SMTP Addr",
}, },
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("host") { if c.IsSet("addr") {
conf.Host = c.String("host") conf.Addr = c.String("addr")
} }
if c.IsSet("port") { if c.IsSet("port") {
conf.Port = c.Int("port") conf.Port = c.Int("port")

View File

@@ -996,9 +996,6 @@ 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1553,7 +1550,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+starttls", "smtp+unix", "sendmail", "dummy". ;; Mail server protocol. One of "smtp", "smtps", "smtp+startls", "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,

View File

@@ -101,7 +101,6 @@ 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`)
@@ -673,7 +672,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+starttls", "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+startls", "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.
@@ -735,9 +734,9 @@ and
- `GRAVATAR_SOURCE`: **gravatar**: Can be `gravatar`, `duoshuo` or anything like - `GRAVATAR_SOURCE`: **gravatar**: Can be `gravatar`, `duoshuo` or anything like
`http://cn.gravatar.com/avatar/`. `http://cn.gravatar.com/avatar/`.
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure. - `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only.
- `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see - `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see
[http://www.libravatar.org](http://www.libravatar.org)). **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure. [http://www.libravatar.org](http://www.libravatar.org)).
- `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`. - `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`.
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files. - `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.

32
go.mod
View File

@@ -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.4.0 github.com/alecthomas/chroma/v2 v2.3.0
github.com/blevesearch/bleve/v2 v2.3.5 github.com/blevesearch/bleve/v2 v2.3.4
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.2.1-0.20221112162523-6fad3dfc1891 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.2.0 golang.org/x/net v0.0.0-20220927171203-f486391704dc
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/sys v0.2.0 golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/text v0.4.0 golang.org/x/text v0.3.8
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.4 // indirect github.com/blevesearch/bleve_index_api v1.0.3 // indirect
github.com/blevesearch/geo v0.1.15 // indirect github.com/blevesearch/geo v0.1.14 // 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.3 // indirect github.com/blevesearch/scorch_segment_api/v2 v2.1.2 // 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.9 // indirect github.com/blevesearch/vellum v1.0.8 // indirect
github.com/blevesearch/zapx/v11 v11.3.6 // indirect github.com/blevesearch/zapx/v11 v11.3.5 // indirect
github.com/blevesearch/zapx/v12 v12.3.6 // indirect github.com/blevesearch/zapx/v12 v12.3.5 // indirect
github.com/blevesearch/zapx/v13 v13.3.6 // indirect github.com/blevesearch/zapx/v13 v13.3.5 // indirect
github.com/blevesearch/zapx/v14 v14.3.6 // indirect github.com/blevesearch/zapx/v14 v14.3.5 // indirect
github.com/blevesearch/zapx/v15 v15.3.6 // indirect github.com/blevesearch/zapx/v15 v15.3.5 // 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,8 +302,6 @@ 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-2
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
View File

@@ -149,6 +149,7 @@ 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=
@@ -159,10 +160,9 @@ 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.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4= github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ= github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
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,47 +225,52 @@ 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.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8= github.com/blevesearch/bleve/v2 v2.3.4 h1:SSb7/cwGzo85LWX1jchIsXM8ZiNNMX3shT5lROM63ew=
github.com/blevesearch/bleve/v2 v2.3.5/go.mod h1:FneKGHMRrCLrp4X9+iy3wlBqgM2ALucg7bp8jUuAi/s= github.com/blevesearch/bleve/v2 v2.3.4/go.mod h1:Ot0zYum8XQRfPcwhae8bZmNyYubynsoMjVvl1jPqL30=
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/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU= github.com/blevesearch/geo v0.1.13/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms= github.com/blevesearch/geo v0.1.14 h1:TTDpJN6l9ck/cUYbXSn4aCElNls0Whe44rcQKsB7EfU=
github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4= github.com/blevesearch/geo v0.1.14/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
github.com/blevesearch/geo v0.1.15/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc= github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
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.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ= github.com/blevesearch/scorch_segment_api/v2 v2.1.2 h1:TAte9VZLWda5WAVlZTTZ+GCzEHqGJb4iB2aiZSA6Iv8=
github.com/blevesearch/scorch_segment_api/v2 v2.1.3/go.mod h1:eZrfp1y+lUh+DzFjUcTBUSnKGuunyFIpBIvqYVzJfvc= github.com/blevesearch/scorch_segment_api/v2 v2.1.2/go.mod h1:rvoQXZGq8drq7vXbNeyiRzdEOwZkjkiYGf1822i6CRA=
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.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ= github.com/blevesearch/vellum v1.0.8 h1:iMGh4lfxza4BnWO/UJTMPlI3HsK9YawjPv+TteVa9ck=
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= github.com/blevesearch/vellum v1.0.8/go.mod h1:+cpRi/tqq49xUYSQN2P7A5zNSNrS+MscLeeaZ3J46UA=
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.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc= github.com/blevesearch/zapx/v11 v11.3.5 h1:eBQWQ7huA+mzm0sAGnZDwgGGli7S45EO+N+ObFWssbI=
github.com/blevesearch/zapx/v11 v11.3.6/go.mod h1:B0CzJRj/pS7hJIroflRtFsa9mRHpMSucSgre0FVINns= github.com/blevesearch/zapx/v11 v11.3.5/go.mod h1:5UdIa/HRMdeRCiLQOyFESsnqBGiip7vQmYReA9toevU=
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.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4= github.com/blevesearch/zapx/v12 v12.3.5 h1:5pX2hU+R1aZihT7ac1dNWh1n4wqkIM9pZzWp0ANED9s=
github.com/blevesearch/zapx/v12 v12.3.6/go.mod h1:iYi7tIKpauwU5os5wTxJITixr5Km21Hl365otMwdaP0= github.com/blevesearch/zapx/v12 v12.3.5/go.mod h1:ANcthYRZQycpbRut/6ArF5gP5HxQyJqiFcuJCBju/ss=
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.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig= github.com/blevesearch/zapx/v13 v13.3.5 h1:eJ3gbD+Nu8p36/O6lhfdvWQ4pxsGYSuTOBrLLPVWJ74=
github.com/blevesearch/zapx/v13 v13.3.6/go.mod h1:X+FsTwCU8qOHtK0d/ArvbOH7qiIgViSQ1GQvcR6LSkI= github.com/blevesearch/zapx/v13 v13.3.5/go.mod h1:FV+dRnScFgKnRDIp08RQL4JhVXt1x2HE3AOzqYa6fjo=
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.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE= github.com/blevesearch/zapx/v14 v14.3.5 h1:hEvVjZaagFCvOUJrlFQ6/Z6Jjy0opM3g7TMEo58TwP4=
github.com/blevesearch/zapx/v14 v14.3.6/go.mod h1:9X8W3XoikagU0rwcTqwZho7p9cC7m7zhPZO94S4wUvM= github.com/blevesearch/zapx/v14 v14.3.5/go.mod h1:954A/eKFb+pg/ncIYWLWCKY+mIjReM9FGTGIO2Wu1cU=
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=
@@ -356,6 +361,7 @@ 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=
@@ -824,7 +830,6 @@ 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=
@@ -1482,8 +1487,6 @@ 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-2 h1:IRB+69BV7fTT5ccw35ca7TCBe2b7dm5Q5y5tUMQmCvU=
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/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=
@@ -1605,8 +1608,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.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@@ -1718,8 +1721,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.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-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=
@@ -1873,13 +1876,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.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/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=
@@ -1889,8 +1892,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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-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=

View File

@@ -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) == git.SHAFullLength && git.IsValidSHAPattern(a.RefName): case len(a.RefName) == 40 && 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.

View File

@@ -68,16 +68,8 @@ func (key *GPGKey) PaddedKeyID() string {
if len(key.KeyID) > 15 { if len(key.KeyID) > 15 {
return key.KeyID return key.KeyID
} }
return PaddedKeyID(key.KeyID)
}
// PaddedKeyID show KeyID padded to 16 characters
func PaddedKeyID(keyID string) string {
if len(keyID) > 15 {
return keyID
}
zeros := "0000000000000000" zeros := "0000000000000000"
return zeros[0:16-len(keyID)] + keyID return zeros[0:16-len(key.KeyID)] + key.KeyID
} }
// ListGPGKeys returns a list of public keys belongs to given user. // ListGPGKeys returns a list of public keys belongs to given user.

View File

@@ -20,12 +20,8 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
const ( // DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
// DefaultAvatarClass is the default class of a rendered avatar const DefaultAvatarPixelSize = 28
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 {
@@ -154,10 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink() return DefaultAvatarLink()
} }
enableFederatedAvatar := system_model.GetSettingBool(system_model.KeyPictureEnableFederatedAvatar) enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
var err error var err error
if enableFederatedAvatar && system_model.LibravatarService != nil { if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && 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
@@ -175,8 +171,8 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return urlStr return urlStr
} }
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar) disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if !disableGravatar { if disableGravatar != nil && !disableGravatar.GetValueBool() {
// 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))

View File

@@ -8,9 +8,6 @@ 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
@@ -27,6 +24,11 @@ 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)
@@ -59,25 +61,8 @@ 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

View File

@@ -24,7 +24,7 @@
fork_id: 0 fork_id: 0
is_template: false is_template: false
template_id: 0 template_id: 0
size: 6708 size: 0
is_fsck_enabled: true is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false close_issues_via_commit_in_any_branch: false

View File

@@ -7,10 +7,8 @@ package git
import ( import (
"context" "context"
"crypto/sha1" "crypto/sha1"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@@ -51,67 +49,79 @@ func init() {
db.RegisterModel(new(CommitStatusIndex)) db.RegisterModel(new(CommitStatusIndex))
} }
func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) { // upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
res, err := db.GetEngine(ctx).Query("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+ func upsertCommitStatusIndex(ctx context.Context, repoID int64, sha string) (err error) {
"VALUES (?,?,1) ON CONFLICT (repo_id, sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1 RETURNING max_index", // An atomic UPSERT operation (INSERT/UPDATE) is the only operation
repoID, sha) // that ensures that the key is actually locked.
if err != nil { switch {
return 0, err case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
"VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1",
repoID, sha)
case setting.Database.UseMySQL:
_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
"VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
repoID, sha)
case setting.Database.UseMSSQL:
// https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
_, err = db.Exec(ctx, "MERGE `commit_status_index` WITH (HOLDLOCK) as target "+
"USING (SELECT ? AS repo_id, ? AS sha) AS src "+
"ON src.repo_id = target.repo_id AND src.sha = target.sha "+
"WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
"WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) "+
"VALUES (src.repo_id, src.sha, 1);",
repoID, sha)
default:
return fmt.Errorf("database type not supported")
} }
if len(res) == 0 { return err
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(ctx context.Context, repoID int64, sha string) (int64, error) { func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
if setting.Database.UsePostgreSQL { for i := 0; i < db.MaxDupIndexAttempts; i++ {
return postgresGetCommitStatusIndex(ctx, repoID, sha) idx, err := getNextCommitStatusIndex(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
}
affected, err = res.RowsAffected() // getNextCommitStatusIndex return the next index
if err != nil { func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
return 0, err ctx, commiter, err := db.TxContext()
} if err != nil {
// if the update still can not update any records, the record must not exist and there must be some errors (insert error) return 0, err
if affected == 0 { }
if errIns == nil { defer commiter.Close()
return 0, errors.New("impossible error when GetNextCommitStatusIndex, insert and update both succeeded but no record is updated")
} var preIdx int64
return 0, errIns _, err = db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx)
} if err != nil {
return 0, err
} }
// now, the new index is in database (protected by the transaction and write-lock) if err := upsertCommitStatusIndex(ctx, repoID, sha); err != nil {
var newIdx int64 return 0, err
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, errors.New("impossible error when GetNextCommitStatusIndex, upsert succeeded but no record can be selected") return 0, db.ErrResouceOutdated
} }
return newIdx, nil if err := commiter.Commit(); err != 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) {
@@ -281,8 +291,10 @@ 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)
} }
if _, err := git.NewIDFromString(opts.SHA); err != nil { // Get the next Status Index
return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err) idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
if err != nil {
return fmt.Errorf("generate commit status index failed: %w", err)
} }
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()
@@ -291,12 +303,6 @@ 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)
@@ -310,7 +316,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()

View File

@@ -1010,7 +1010,12 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
} }
} }
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil { if opts.IsPull {
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
} else {
_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID)
}
if err != nil {
return err return err
} }

View File

@@ -742,9 +742,17 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
if err != nil { if err != nil {
return nil, err return nil, err
} else if official { } else if official {
if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil { // recalculate the latest official review for reviewer
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
if err != nil && !IsErrReviewNotExist(err) {
return nil, err return nil, err
} }
if review != nil {
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
return nil, err
}
}
} }
comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{ comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
@@ -762,22 +770,6 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
return comment, committer.Commit() return comment, committer.Commit()
} }
// Recalculate the latest official review for reviewer
func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64) error {
review, err := GetReviewByIssueIDAndUserID(ctx, issueID, reviewerID)
if err != nil && !IsErrReviewNotExist(err) {
return err
}
if review != nil {
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
return err
}
}
return nil
}
// AddTeamReviewRequest add a review request from one team // AddTeamReviewRequest add a review request from one team
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) { func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()
@@ -996,12 +988,6 @@ func DeleteReview(r *Review) error {
return err return err
} }
if r.Official {
if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
return err
}
}
return committer.Commit() return committer.Commit()
} }

View File

@@ -201,38 +201,3 @@ func TestDismissReview(t *testing.T) {
assert.False(t, requestReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed)
assert.True(t, approveReviewExample.Dismissed) assert.True(t, approveReviewExample.Dismissed)
} }
func TestDeleteReview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
review1, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Content: "Official rejection",
Type: issues_model.ReviewTypeReject,
Official: false,
Issue: issue,
Reviewer: user,
})
assert.NoError(t, err)
review2, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Content: "Official approval",
Type: issues_model.ReviewTypeApprove,
Official: true,
Issue: issue,
Reviewer: user,
})
assert.NoError(t, err)
assert.NoError(t, issues_model.DeleteReview(review2))
_, err = issues_model.GetReviewByID(db.DefaultContext, review2.ID)
assert.Error(t, err)
assert.True(t, issues_model.IsErrReviewNotExist(err), "IsErrReviewNotExist")
review1, err = issues_model.GetReviewByID(db.DefaultContext, review1.ID)
assert.NoError(t, err)
assert.True(t, review1.Official)
}

View File

@@ -6,7 +6,6 @@ package issues
import ( import (
"context" "context"
"errors"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@@ -48,42 +47,33 @@ 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 {
if err != nil && !errors.Is(err, util.ErrNotExist) { return
return err
} }
}
// Now load the repo for the issue (which we may have just loaded)
if t.Issue != nil {
err = t.Issue.LoadRepo(ctx) err = t.Issue.LoadRepo(ctx)
if err != nil && !errors.Is(err, util.ErrNotExist) { if err != nil {
return err return
} }
} }
// Load the user
if t.User == nil { if t.User == nil {
t.User, err = user_model.GetUserByIDCtx(ctx, t.UserID) t.User, err = user_model.GetUserByIDCtx(ctx, t.UserID)
if err != nil { if err != nil {
if !errors.Is(err, util.ErrNotExist) { return
return err
}
t.User = user_model.NewGhostUser()
} }
} }
return nil return err
} }
// LoadAttributes load Issue, User // LoadAttributes load Issue, User
func (tl TrackedTimeList) LoadAttributes() error { func (tl TrackedTimeList) LoadAttributes() (err error) {
for _, t := range tl { for _, t := range tl {
if err := t.LoadAttributes(); err != nil { if err = t.LoadAttributes(); err != nil {
return err return err
} }
} }
return nil return err
} }
// 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.

View File

@@ -14,7 +14,6 @@ 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"
@@ -514,13 +513,6 @@ 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())

View File

@@ -458,9 +458,8 @@ 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 {
// If user is nil, it's an anonymous user/request. // Not SignedUser
// The Ghost user is handled like an anonymous user. if user == nil {
if user == nil || user.IsGhost() {
return orgOrUser.Visibility == structs.VisibleTypePublic return orgOrUser.Visibility == structs.VisibleTypePublic
} }

View File

@@ -26,7 +26,6 @@ type BlobSearchOptions struct {
Digest string Digest string
Tag string Tag string
IsManifest bool IsManifest bool
Repository string
} }
func (opts *BlobSearchOptions) toConds() builder.Cond { func (opts *BlobSearchOptions) toConds() builder.Cond {
@@ -55,15 +54,6 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property"))) cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
} }
if opts.Repository != "" {
var propsCond builder.Cond = builder.Eq{
"package_property.ref_type": packages.PropertyTypePackage,
"package_property.name": container_module.PropertyRepository,
"package_property.value": opts.Repository,
}
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
}
return cond return cond
} }

View File

@@ -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 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("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("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Where(cond) Where(cond)

View File

@@ -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_pull=?)", false), statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, false),
repoStatsCorrectNumIssues, 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_pull=?)", true), statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, true),
repoStatsCorrectNumPulls, repoStatsCorrectNumPulls,
"repository count 'num_pulls'", "repository count 'num_pulls'",
}, },

View File

@@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -499,12 +498,8 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
// Only show a repo that either has a topic or description. // Only show a repo that either has a topic or description.
subQueryCond := builder.NewCond() subQueryCond := builder.NewCond()
// Topic checking. Topics are present. // Topic checking. Topics is non-null.
if setting.Database.UsePostgreSQL { // postgres stores the topics as json and not as text subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
} else {
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
}
// Description checking. Description not empty. // Description checking. Description not empty.
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""}) subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})

View File

@@ -185,7 +185,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
return committer.Commit() return committer.Commit()
} }
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize // UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
func UpdateRepoSize(ctx context.Context, repoID, size int64) error { func UpdateRepoSize(ctx context.Context, repoID, size int64) error {
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{ _, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{
Size: size, Size: size,

View File

@@ -12,8 +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"
setting_module "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"strk.kbt.io/projects/go/libravatar" "strk.kbt.io/projects/go/libravatar"
@@ -36,10 +35,6 @@ 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
} }
@@ -80,8 +75,8 @@ func IsErrDataExpired(err error) bool {
return ok return ok
} }
// GetSettingNoCache returns specific setting without using the cache // GetSetting returns specific setting
func GetSettingNoCache(key string) (*Setting, error) { func GetSetting(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
@@ -89,26 +84,7 @@ func GetSettingNoCache(key string) (*Setting, error) {
if len(v) == 0 { if len(v) == 0 {
return nil, ErrSettingIsNotExist{key} return nil, ErrSettingIsNotExist{key}
} }
return v[strings.ToLower(key)], nil return v[key], nil
}
// GetSetting returns the setting value via the key
func GetSetting(key string) (string, error) {
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
res, err := GetSettingNoCache(key)
if err != nil {
return "", err
}
return res.SettingValue, nil
})
}
// GetSettingBool return bool value of setting,
// none existing keys and errors are ignored and result in false
func GetSettingBool(key string) bool {
s, _ := GetSetting(key)
v, _ := strconv.ParseBool(s)
return v
} }
// GetSettings returns specific settings // GetSettings returns specific settings
@@ -132,7 +108,7 @@ func GetSettings(keys []string) (map[string]*Setting, error) {
type AllSettings map[string]*Setting type AllSettings map[string]*Setting
func (settings AllSettings) Get(key string) Setting { func (settings AllSettings) Get(key string) Setting {
if v, ok := settings[strings.ToLower(key)]; ok { if v, ok := settings[key]; ok {
return *v return *v
} }
return Setting{} return Setting{}
@@ -163,13 +139,12 @@ 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 := GetSettingNoCache(key) s, err := GetSetting(key)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
return SetSetting(&Setting{ return SetSetting(&Setting{
SettingKey: key, SettingKey: key,
@@ -188,14 +163,7 @@ func SetSetting(setting *Setting) error {
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil { if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
return err return err
} }
setting.Version++ setting.Version++
cc := cache.GetCache()
if cc != nil {
return cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds())
}
return nil return nil
} }
@@ -245,9 +213,9 @@ var (
func Init() error { func Init() error {
var disableGravatar bool var disableGravatar bool
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar) disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
disableGravatar = setting_module.GetDefaultDisableGravatar() disableGravatar = setting.GetDefaultDisableGravatar()
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)} disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
} else if err != nil { } else if err != nil {
return err return err
@@ -256,9 +224,9 @@ func Init() error {
} }
var enableFederatedAvatar bool var enableFederatedAvatar bool
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar) enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar) enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)} enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
} else if err != nil { } else if err != nil {
return err return err
@@ -266,30 +234,20 @@ func Init() error {
enableFederatedAvatar = disableGravatarSetting.GetValueBool() enableFederatedAvatar = disableGravatarSetting.GetValueBool()
} }
if setting_module.OfflineMode { if setting.OfflineMode {
disableGravatar = true disableGravatar = true
enableFederatedAvatar = false enableFederatedAvatar = false
if !GetSettingBool(KeyPictureDisableGravatar) {
if err := SetSettingNoVersion(KeyPictureDisableGravatar, "true"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
}
}
if GetSettingBool(KeyPictureEnableFederatedAvatar) {
if err := SetSettingNoVersion(KeyPictureEnableFederatedAvatar, "false"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
}
}
} }
if enableFederatedAvatar || !disableGravatar { if disableGravatar || !enableFederatedAvatar {
var err error var err error
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource) GravatarSourceURL, err = url.Parse(setting.GravatarSource)
if err != nil { if err != nil {
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err) return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting.GravatarSource, err)
} }
} }
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() { if enableFederatedAvatarSetting.GetValueBool() {
LibravatarService = libravatar.New() LibravatarService = libravatar.New()
if GravatarSourceURL.Scheme == "https" { if GravatarSourceURL.Scheme == "https" {
LibravatarService.SetUseHTTPS(true) LibravatarService.SetUseHTTPS(true)

View File

@@ -9,8 +9,3 @@ 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
}

View File

@@ -34,14 +34,10 @@ func TestSettings(t *testing.T) {
assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue) assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
// updated setting // updated setting
updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: settings[strings.ToLower(keyName)].Version} updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.Version}
err = system.SetSetting(updatedSetting) err = system.SetSetting(updatedSetting)
assert.NoError(t, err) assert.NoError(t, err)
value, err := system.GetSetting(keyName)
assert.NoError(t, err)
assert.EqualValues(t, updatedSetting.SettingValue, value)
// get all settings // get all settings
settings, err = system.GetAllSettings() settings, err = system.GetAllSettings()
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -89,7 +89,6 @@ 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)
} }

View File

@@ -68,7 +68,11 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false useLocalAvatar := false
autoGenerateAvatar := false autoGenerateAvatar := false
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar) var disableGravatar bool
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatarSetting != nil {
disableGravatar = disableGravatarSetting.GetValueBool()
}
switch { switch {
case u.UseCustomAvatar: case u.UseCustomAvatar:

View File

@@ -10,7 +10,6 @@ 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"
) )
@@ -48,25 +47,9 @@ func IsErrUserSettingIsNotExist(err error) bool {
return ok return ok
} }
// genSettingCacheKey returns the cache key for some configuration // GetSetting returns specific setting
func genSettingCacheKey(userID int64, key string) string { func GetSetting(uid int64, key string) (*Setting, error) {
return fmt.Sprintf("user_%d.setting.%s", userID, key) v, err := GetUserSettings(uid, []string{key})
}
// GetSetting returns the setting value via the key
func GetSetting(uid int64, key string) (string, error) {
return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) {
res, err := GetSettingNoCache(uid, key)
if err != nil {
return "", err
}
return res.SettingValue, nil
})
}
// GetSettingNoCache returns specific setting without using the cache
func GetSettingNoCache(uid int64, key string) (*Setting, error) {
v, err := GetSettings(uid, []string{key})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -76,8 +59,8 @@ func GetSettingNoCache(uid int64, key string) (*Setting, error) {
return v[key], nil return v[key], nil
} }
// GetSettings returns specific settings from user // GetUserSettings returns specific settings from user
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) { func GetUserSettings(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).
@@ -122,7 +105,6 @@ 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 {
@@ -142,10 +124,7 @@ 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
} }
@@ -154,12 +133,7 @@ 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.GetString(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 {

View File

@@ -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.GetSettings(99, []string{keyName}) settings, err := user_model.GetUserSettings(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)

View File

@@ -1227,10 +1227,7 @@ func GetUserByOpenID(uri string) (*User, error) {
// GetAdminUser returns the first administrator // GetAdminUser returns the first administrator
func GetAdminUser() (*User, error) { func GetAdminUser() (*User, error) {
var admin User var admin User
has, err := db.GetEngine(db.DefaultContext). has, err := db.GetEngine(db.DefaultContext).Where("is_admin=?", true).Get(&admin)
Where("is_admin=?", true).
Asc("id"). // Reliably get the admin with the lowest ID.
Get(&admin)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {

View File

@@ -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.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) settings, err = user_model.GetUserSettings(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 {

102
modules/cache/cache.go vendored
View File

@@ -51,26 +51,27 @@ 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) {
cached := conn.Get(key) var (
value string
if cached == nil { err error
value, err := getFunc() )
if err != nil { if value, err = getFunc(); err != nil {
return value, err return value, err
} }
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return "", err
}
} }
value := conn.Get(key)
if value, ok := cached.(string); ok { if v, ok := value.(string); ok {
return value, nil return v, nil
} }
if v, ok := value.(fmt.Stringer); ok {
if stringer, ok := cached.(fmt.Stringer); ok { return v.String(), nil
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
@@ -78,33 +79,30 @@ 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) {
cached := conn.Get(key) var (
value int
if cached == nil { err error
value, err := getFunc() )
if err != nil { if value, err = getFunc(); 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())
}
switch v := cached.(type) {
case int:
return v, nil
case string:
value, err := strconv.Atoi(v)
if err != nil { if err != nil {
return 0, err return 0, err
} }
}
switch value := conn.Get(key).(type) {
case int:
return value, nil return value, nil
default: case string:
value, err := getFunc() v, err := strconv.Atoi(value)
if err != nil { if err != nil {
return value, err return 0, err
} }
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) return v, nil
default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
} }
} }
@@ -113,34 +111,30 @@ 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) {
cached := conn.Get(key) var (
value int64
if cached == nil { err error
value, err := getFunc() )
if err != nil { if value, err = getFunc(); 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())
}
switch v := conn.Get(key).(type) {
case int64:
return v, nil
case string:
value, err := strconv.ParseInt(v, 10, 64)
if err != nil { if err != nil {
return 0, err return 0, err
} }
}
switch value := conn.Get(key).(type) {
case int64:
return value, nil return value, nil
default: case string:
value, err := getFunc() v, err := strconv.ParseInt(value, 10, 64)
if err != nil { if err != nil {
return value, err return 0, err
} }
return v, nil
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
} }
} }

View File

@@ -29,12 +29,6 @@ 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"]
} }

View File

@@ -9,7 +9,6 @@
package charset package charset
import ( import (
"bufio"
"io" "io"
"strings" "strings"
@@ -33,7 +32,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 provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte // 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
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)
@@ -45,35 +44,6 @@ 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{}

View File

@@ -220,13 +220,7 @@ 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.
var err error ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
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)
@@ -394,7 +388,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) == git.SHAFullLength { } else if len(refName) == 40 {
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 {

View File

@@ -34,7 +34,6 @@ 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"
@@ -323,9 +322,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)
} }
@@ -346,61 +345,34 @@ 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(opts *ServeHeaderOptions) { func (ctx *Context) SetServeHeaders(filename string) {
header := ctx.Resp.Header() ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
contentType := typesniffer.ApplicationOctetStream ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
if opts.ContentType != "" { ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
if opts.ContentTypeCharset != "" { ctx.Resp.Header().Set("Expires", "0")
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset) ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
} else { ctx.Resp.Header().Set("Pragma", "public")
contentType = opts.ContentType ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
}
}
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(r io.ReadSeeker, opts *ServeHeaderOptions) { func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
ctx.SetServeHeaders(opts) ctx.SetServeHeaders(name)
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r) http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
}
// ServeFile serves given file to response.
func (ctx *Context) ServeFile(file string, names ...string) {
var name string
if len(names) > 0 {
name = names[0]
} else {
name = path.Base(file)
}
ctx.SetServeHeaders(name)
http.ServeFile(ctx.Resp, ctx.Req, file)
} }
// UploadStream returns the request body or the first form file // UploadStream returns the request body or the first form file
@@ -663,13 +635,7 @@ 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) {
var err error ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
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)

View File

@@ -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]) == git.SHAFullLength { if len(parts) > 0 && len(parts[0]) == 40 {
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]) <= git.SHAFullLength { if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 {
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) <= git.SHAFullLength { } else if len(refName) >= 7 && len(refName) <= 40 {
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) < git.SHAFullLength { if len(refName) < 40 {
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))))
} }
@@ -1087,9 +1087,6 @@ func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplat
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil { if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
invalidFiles[fullName] = err invalidFiles[fullName] = err
} else { } else {
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
it.Ref = git.BranchPrefix + it.Ref
}
issueTemplates = append(issueTemplates, it) issueTemplates = append(issueTemplates, it)
} }
} }

View File

@@ -110,11 +110,12 @@ 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,
Time: t.Time, UserName: t.User.Name,
Created: t.Created, Time: t.Time,
Created: t.Created,
} }
if t.Issue != nil { if t.Issue != nil {
apiT.Issue = ToAPIIssue(t.Issue) apiT.Issue = ToAPIIssue(t.Issue)

View File

@@ -89,10 +89,6 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
}, },
} }
if pr.Issue.ClosedUnix != 0 {
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
}
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil { if err != nil {
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err) log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)

View File

@@ -205,9 +205,6 @@ 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 {

View File

@@ -19,9 +19,11 @@ 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++
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) runOpts := &git.RunOpts{Dir: repo.RepoPath()}
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) _, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(runOpts)
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(runOpts)
// 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 {
@@ -47,7 +49,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", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()}) err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", repo.DefaultBranch).Run(runOpts)
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
@@ -63,7 +65,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", numRepos) logger.Info("All %d repos have their HEADs in the correct state")
} 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)

File diff suppressed because it is too large Load Diff

View File

@@ -202,11 +202,8 @@ func (c *Command) Run(opts *RunOpts) error {
if opts == nil { if opts == nil {
opts = &RunOpts{} opts = &RunOpts{}
} }
if opts.Timeout <= 0 {
// We must not change the provided options opts.Timeout = defaultCommandExecutionTimeout
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultCommandExecutionTimeout
} }
if len(opts.Dir) == 0 { if len(opts.Dir) == 0 {
@@ -241,7 +238,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, timeout, desc) ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
} }
defer finished() defer finished()
@@ -342,20 +339,9 @@ 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
// We must not change the provided options as it could break future calls - therefore make a copy. opts.Stderr = stderrBuf
newOpts := &RunOpts{ err := c.Run(opts)
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)}

View File

@@ -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 repo == nil || name == "" { if name == "" {
return false return false
} }

View File

@@ -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) == SHAFullLength { if len(commitID) == 40 {
sha1, err := NewIDFromString(commitID) sha1, err := NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return sha1, nil

View File

@@ -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) == SHAFullLength && IsValidSHAPattern(commitID) { if len(commitID) == 40 && IsValidSHAPattern(commitID) {
sha1, err := NewIDFromString(commitID) sha1, err := NewIDFromString(commitID)
if err == nil { if err == nil {
return sha1, nil return sha1, nil

View File

@@ -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) != SHAFullLength { if len(treeish) != 40 {
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

View File

@@ -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 repo == nil || name == "" { if name == "" {
return false return false
} }

View File

@@ -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) != SHAFullLength { if len(idStr) != 40 {
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

View File

@@ -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) != SHAFullLength { if len(idStr) != 40 {
res, err := repo.GetRefCommitID(idStr) res, err := repo.GetRefCommitID(idStr)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -18,9 +18,6 @@ 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}$`)
@@ -54,7 +51,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) != SHAFullLength { if len(s) != 40 {
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)

View File

@@ -10,7 +10,6 @@ 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"
@@ -31,9 +30,7 @@ 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, '<')
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't sig.Name = string(line[:emailStart-1])
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])

View File

@@ -11,7 +11,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -52,9 +51,7 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
return return
} }
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't sig.Name = string(line[:emailStart-1])
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)

View File

@@ -100,9 +100,6 @@ func RefURL(repoURL, ref string) string {
return repoURL + "/src/branch/" + refName return repoURL + "/src/branch/" + refName
case strings.HasPrefix(ref, TagPrefix): case strings.HasPrefix(ref, TagPrefix):
return repoURL + "/src/tag/" + refName return repoURL + "/src/tag/" + refName
case !IsValidSHAPattern(ref):
// assume they mean a branch
return repoURL + "/src/branch/" + refName
default: default:
return repoURL + "/src/commit/" + refName return repoURL + "/src/commit/" + refName
} }

View File

@@ -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[string]interface{}) opt, ok := option.(map[interface{}]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[string]interface{}); ok { if vs, ok := o.data.(map[interface{}]interface{}); ok {
if v, ok := vs["label"].(string); ok { if v, ok := vs["label"].(string); ok {
return v return v
} }

View File

@@ -6,21 +6,18 @@ 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
filename string content string
content string wantErr string
want *api.IssueTemplate
wantErr string
}{ }{
{ {
name: "miss name", name: "miss name",
@@ -319,9 +316,21 @@ body:
`, `,
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool", wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
}, },
{ }
name: "valid", for _, tt := range tests {
content: ` t.Run(tt.name, func(t *testing.T) {
tmpl, err := unmarshal("test.yaml", []byte(tt.content))
if err != nil {
t.Fatal(err)
}
if err := Validate(tmpl); (err == nil) != (tt.wantErr == "") || err != nil && err.Error() != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %q", err, tt.wantErr)
}
})
}
t.Run("valid", func(t *testing.T) {
content := `
name: Name name: Name
title: Title title: Title
about: About about: About
@@ -377,227 +386,96 @@ 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{}{
Type: "textarea", "required": true,
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", Type: "checkboxes",
Attributes: map[string]interface{}{ ID: "id5",
"label": "Label of input", Attributes: map[string]interface{}{
"description": "Description of input", "label": "Label of checkboxes",
"placeholder": "Placeholder of input", "description": "Description of checkboxes",
"value": "Value of input", "options": []interface{}{
}, map[interface{}]interface{}{"label": "Option 1 of checkboxes", "required": true},
Validations: map[string]interface{}{ map[interface{}]interface{}{"label": "Option 2 of checkboxes", "required": false},
"required": true, map[interface{}]interface{}{"label": "Option 3 of checkboxes", "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",
}, },
wantErr: "", FileName: "test.yaml",
}, }
{ got, err := unmarshal("test.yaml", []byte(content))
name: "single label", if err != nil {
content: ` t.Fatal(err)
name: Name }
title: Title if err := Validate(got); err != nil {
about: About t.Errorf("Validate() error = %v", err)
labels: label1 }
ref: Ref if !reflect.DeepEqual(want, got) {
body: jsonWant, _ := json.Marshal(want)
- type: markdown jsonGot, _ := json.Marshal(got)
id: id1 t.Errorf("want:\n%s\ngot:\n%s", jsonWant, jsonGot)
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) {

View File

@@ -7,16 +7,15 @@ package template
import ( import (
"fmt" "fmt"
"io" "io"
"path" "path/filepath"
"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.v3" "gopkg.in/yaml.v2"
) )
// CouldBe indicates a file with the filename could be a template, // CouldBe indicates a file with the filename could be a template,
@@ -44,7 +43,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, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here return unmarshalFromEntry(entry, filepath.Join(dir, entry.Name()))
} }
// UnmarshalFromCommit parses out a valid template from the commit // UnmarshalFromCommit parses out a valid template from the commit
@@ -96,27 +95,14 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
}{} }{}
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown { if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil { templateBody, err := markdown.ExtractMetadata(string(content), it)
// The only thing we know here is that we can't extract metadata from the content, if err != nil {
// it's hard to tell if metadata doesn't exist or metadata isn't valid. return nil, err
// There's an example template: }
// it.Content = templateBody
// --- if it.About == "" {
// # Title if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
// --- 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 {

View File

@@ -5,7 +5,6 @@
package external package external
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@@ -134,13 +133,11 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
if !p.IsInputFile { if !p.IsInputFile {
cmd.Stdin = input cmd.Stdin = input
} }
var stderr bytes.Buffer
cmd.Stdout = output cmd.Stdout = output
cmd.Stderr = &stderr
process.SetSysProcAttribute(cmd) process.SetSysProcAttribute(cmd)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) return fmt.Errorf("%s render run command %s %v failed: %w", p.Name(), commands[0], args, err)
} }
return nil return nil
} }

View File

@@ -9,86 +9,82 @@ 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. /*
Copied from structs.IssueTemplate, the original type has been changed a lot to support yaml template. A legacy to keep the unit tests working.
*/ Copied from the method "func (it IssueTemplate) Valid() bool", the original method has been removed.
type IssueTemplate struct { Because it becomes quite complicated to validate an issue template which is support yaml form now.
Name string `json:"name" yaml:"name"` The new way to validate an issue template is to call the Validate in modules/issue/template,
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 IssueTemplate var meta structs.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, meta.Valid()) assert.True(t, validateMetadata(meta))
}) })
t.Run("NoFirstSeparator", func(t *testing.T) { t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate var meta structs.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 IssueTemplate var meta structs.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 IssueTemplate var meta structs.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, meta.Valid()) assert.True(t, validateMetadata(meta))
}) })
} }
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 IssueTemplate var meta structs.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, meta.Valid()) assert.True(t, validateMetadata(meta))
}) })
t.Run("NoFirstSeparator", func(t *testing.T) { t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate var meta structs.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 IssueTemplate var meta structs.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 IssueTemplate var meta structs.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, meta.Valid()) assert.True(t, validateMetadata(meta))
}) })
} }
@@ -101,7 +97,7 @@ labels:
- bug - bug
- "test label"` - "test label"`
bodyTest = "This is the body" bodyTest = "This is the body"
metaTest = IssueTemplate{ metaTest = structs.IssueTemplate{
Name: "Test", Name: "Test",
About: "A Test", About: "A Test",
Title: "Test Title", Title: "Test Title",

View File

@@ -30,13 +30,6 @@ 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)

View File

@@ -6,10 +6,8 @@ package nuget
import ( import (
"archive/zip" "archive/zip"
"bytes"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt"
"io" "io"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -185,23 +183,7 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
return &Package{ return &Package{
PackageType: packageType, PackageType: packageType,
ID: p.Metadata.ID, ID: p.Metadata.ID,
Version: toNormalizedVersion(v), Version: v.String(),
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()
}

View File

@@ -147,19 +147,6 @@ 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) {

View File

@@ -110,6 +110,32 @@ func (q *ChannelQueue) Flush(timeout time.Duration) error {
return q.FlushWithContext(ctx) return q.FlushWithContext(ctx)
} }
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
func (q *ChannelQueue) FlushWithContext(ctx context.Context) error {
log.Trace("ChannelQueue: %d Flush", q.qid)
paused, _ := q.IsPausedIsResumed()
for {
select {
case <-paused:
return nil
case data, ok := <-q.dataChan:
if !ok {
return nil
}
if unhandled := q.handle(data); unhandled != nil {
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
}
atomic.AddInt64(&q.numInQueue, -1)
case <-q.baseCtx.Done():
return q.baseCtx.Err()
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}
}
// Shutdown processing from this queue // Shutdown processing from this queue
func (q *ChannelQueue) Shutdown() { func (q *ChannelQueue) Shutdown() {
q.lock.Lock() q.lock.Lock()

View File

@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"runtime/pprof" "runtime/pprof"
"sync" "sync"
"sync/atomic"
"time" "time"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
@@ -167,6 +168,35 @@ func (q *ChannelUniqueQueue) Flush(timeout time.Duration) error {
return q.FlushWithContext(ctx) return q.FlushWithContext(ctx)
} }
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
func (q *ChannelUniqueQueue) FlushWithContext(ctx context.Context) error {
log.Trace("ChannelUniqueQueue: %d Flush", q.qid)
paused, _ := q.IsPausedIsResumed()
for {
select {
case <-paused:
return nil
default:
}
select {
case data, ok := <-q.dataChan:
if !ok {
return nil
}
if unhandled := q.handle(data); unhandled != nil {
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
}
atomic.AddInt64(&q.numInQueue, -1)
case <-q.baseCtx.Done():
return q.baseCtx.Err()
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}
}
// Shutdown processing from this queue // Shutdown processing from this queue
func (q *ChannelUniqueQueue) Shutdown() { func (q *ChannelUniqueQueue) Shutdown() {
log.Trace("ChannelUniqueQueue: %s Shutting down", q.name) log.Trace("ChannelUniqueQueue: %s Shutting down", q.name)

View File

@@ -464,43 +464,13 @@ func (p *WorkerPool) IsEmpty() bool {
return atomic.LoadInt64(&p.numInQueue) == 0 return atomic.LoadInt64(&p.numInQueue) == 0
} }
// contextError returns either ctx.Done(), the base context's error or nil
func (p *WorkerPool) contextError(ctx context.Context) error {
select {
case <-p.baseCtx.Done():
return p.baseCtx.Err()
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty // FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
// NB: The worker will not be registered with the manager. // NB: The worker will not be registered with the manager.
func (p *WorkerPool) FlushWithContext(ctx context.Context) error { func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
log.Trace("WorkerPool: %d Flush", p.qid) log.Trace("WorkerPool: %d Flush", p.qid)
paused, _ := p.IsPausedIsResumed()
for { for {
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
select { select {
case <-paused: case data := <-p.dataChan:
// Ensure that even if paused that the cancelled error is still sent
return p.contextError(ctx)
case <-p.baseCtx.Done():
return p.baseCtx.Err()
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case <-paused:
return p.contextError(ctx)
case data, ok := <-p.dataChan:
if !ok {
return nil
}
if unhandled := p.handle(data); unhandled != nil { if unhandled := p.handle(data); unhandled != nil {
log.Error("Unhandled Data whilst flushing queue %d", p.qid) log.Error("Unhandled Data whilst flushing queue %d", p.qid)
} }
@@ -526,7 +496,6 @@ func (p *WorkerPool) doWork(ctx context.Context) {
paused, _ := p.IsPausedIsResumed() paused, _ := p.IsPausedIsResumed()
data := make([]Data, 0, p.batchLength) data := make([]Data, 0, p.batchLength)
for { for {
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
select { select {
case <-paused: case <-paused:
log.Trace("Worker for Queue %d Pausing", p.qid) log.Trace("Worker for Queue %d Pausing", p.qid)
@@ -547,19 +516,8 @@ func (p *WorkerPool) doWork(ctx context.Context) {
log.Trace("Worker shutting down") log.Trace("Worker shutting down")
return return
} }
case <-ctx.Done():
if len(data) > 0 {
log.Trace("Handling: %d data, %v", len(data), data)
if unhandled := p.handle(data...); unhandled != nil {
log.Error("Unhandled Data in queue %d", p.qid)
}
atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
}
log.Trace("Worker shutting down")
return
default: default:
} }
select { select {
case <-paused: case <-paused:
// go back around // go back around

View File

@@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@@ -287,36 +286,9 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
return repo, nil return repo, nil
} }
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular // UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
// getDirectorySize returns the disk consumption for a given path
func getDirectorySize(path string) (int64, error) {
var size int64
err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error {
if err != nil {
if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing.
return nil
}
return err
}
if info.IsDir() {
return nil
}
f, err := info.Info()
if err != nil {
return err
}
if (f.Mode() & notRegularFileMode) == 0 {
size += f.Size()
}
return err
})
return size, err
}
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
size, err := getDirectorySize(repo.RepoPath()) size, err := util.GetDirectorySize(repo.RepoPath())
if err != nil { if err != nil {
return fmt.Errorf("updateSize: %w", err) return fmt.Errorf("updateSize: %w", err)
} }

View File

@@ -169,13 +169,3 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, act.IsPrivate) assert.True(t, act.IsPrivate)
} }
func TestGetDirectorySize(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo, err := repo_model.GetRepositoryByID(1)
assert.NoError(t, err)
size, err := getDirectorySize(repo.RepoPath())
assert.NoError(t, err)
assert.EqualValues(t, size, repo.Size)
}

View File

@@ -13,57 +13,78 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
shellquote "github.com/kballard/go-shellquote" shellquote "github.com/kballard/go-shellquote"
ini "gopkg.in/ini.v1"
) )
// Mailer represents mail service. // Mailer represents mail service.
type Mailer struct { type Mailer struct {
// Mailer // Mailer
Name string `ini:"NAME"` Name string
From string `ini:"FROM"` From string
EnvelopeFrom string `ini:"ENVELOPE_FROM"` EnvelopeFrom string
OverrideEnvelopeFrom bool `ini:"-"` OverrideEnvelopeFrom bool `ini:"-"`
FromName string `ini:"-"` FromName string
FromEmail string `ini:"-"` FromEmail string
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` SendAsPlainText bool
SubjectPrefix string `ini:"SUBJECT_PREFIX"` SubjectPrefix string
// SMTP sender // SMTP sender
Protocol string `ini:"PROTOCOL"` Protocol string
SMTPAddr string `ini:"SMTP_ADDR"` SMTPAddr string
SMTPPort string `ini:"SMTP_PORT"` SMTPPort string
User string `ini:"USER"` User, Passwd string
Passwd string `ini:"PASSWD"` EnableHelo bool
EnableHelo bool `ini:"ENABLE_HELO"` HeloHostname string
HeloHostname string `ini:"HELO_HOSTNAME"` ForceTrustServerCert bool
ForceTrustServerCert bool `ini:"FORCE_TRUST_SERVER_CERT"` UseClientCert bool
UseClientCert bool `ini:"USE_CLIENT_CERT"` ClientCertFile string
ClientCertFile string `ini:"CLIENT_CERT_FILE"` ClientKeyFile string
ClientKeyFile string `ini:"CLIENT_KEY_FILE"`
// Sendmail sender // Sendmail sender
SendmailPath string `ini:"SENDMAIL_PATH"` SendmailPath string
SendmailArgs []string `ini:"-"` SendmailArgs []string
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"` SendmailTimeout time.Duration
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"` SendmailConvertCRLF bool
} }
// MailService the global mailer // MailService the global mailer
var MailService *Mailer var MailService *Mailer
func parseMailerConfig(rootCfg *ini.File) { func newMailService() {
sec := rootCfg.Section("mailer") sec := Cfg.Section("mailer")
// Check mailer setting. // Check mailer setting.
if !sec.Key("ENABLED").MustBool() { if !sec.Key("ENABLED").MustBool() {
return return
} }
// Handle Deprecations and map on to new configuration MailService = &Mailer{
Name: sec.Key("NAME").MustString(AppName),
SendAsPlainText: sec.Key("SEND_AS_PLAIN_TEXT").MustBool(false),
Protocol: sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy"}),
SMTPAddr: sec.Key("SMTP_ADDR").String(),
SMTPPort: sec.Key("SMTP_PORT").String(),
User: sec.Key("USER").String(),
Passwd: sec.Key("PASSWD").String(),
EnableHelo: sec.Key("ENABLE_HELO").MustBool(true),
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
ForceTrustServerCert: sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false),
UseClientCert: sec.Key("USE_CLIENT_CERT").MustBool(false),
ClientCertFile: sec.Key("CLIENT_CERT_FILE").String(),
ClientKeyFile: sec.Key("CLIENT_KEY_FILE").String(),
SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString(""),
SendmailPath: sec.Key("SENDMAIL_PATH").MustString("sendmail"),
SendmailTimeout: sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
SendmailConvertCRLF: sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true),
}
MailService.From = sec.Key("FROM").MustString(MailService.User)
MailService.EnvelopeFrom = sec.Key("ENVELOPE_FROM").MustString("")
// 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" {
sec.Key("PROTOCOL").MustString("sendmail") MailService.Protocol = "sendmail"
} }
} }
@@ -72,99 +93,34 @@ func parseMailerConfig(rootCfg *ini.File) {
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") { if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
givenHost := sec.Key("HOST").String() givenHost := sec.Key("HOST").String()
addr, port, err := net.SplitHostPort(givenHost) addr, port, err := net.SplitHostPort(givenHost)
if err != nil && strings.Contains(err.Error(), "missing port in address") { if err != nil {
addr = givenHost
} else if err != nil {
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err) log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
} }
if addr == "" { MailService.SMTPAddr = addr
addr = "127.0.0.1" MailService.SMTPPort = port
}
sec.Key("SMTP_ADDR").MustString(addr)
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() {
sec.Key("PROTOCOL").MustString("smtps") MailService.Protocol = "smtps"
} else { } else {
sec.Key("PROTOCOL").MustString("smtp+starttls") MailService.Protocol = "smtp+startls"
} }
} }
// 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+starttls": case "smtp+startls":
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"
@@ -175,38 +131,60 @@ func parseMailerConfig(rootCfg *ini.File) {
case "465": case "465":
MailService.Protocol = "smtps" MailService.Protocol = "smtps"
case "587": case "587":
MailService.Protocol = "smtp+starttls" MailService.Protocol = "smtp+startls"
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"
if MailService.SMTPPort == "" {
MailService.SMTPPort = "465"
}
} }
} }
} }
// we want to warn if users use SMTP on a non-local IP; // we want to warn if users use SMTP on a non-local IP;
// we might as well take the opportunity to check that it has an IP at all // we might as well take the opportunity to check that it has an IP at all
// This check is not needed for sendmail ips := tryResolveAddr(MailService.SMTPAddr)
switch MailService.Protocol { if MailService.Protocol == "smtp" {
case "sendmail": for _, ip := range ips {
var err error if !ip.IsLoopback() {
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String()) log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
if err != nil { break
log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
}
case "smtp", "smtps", "smtp+starttls", "smtp+unix":
ips := tryResolveAddr(MailService.SMTPAddr)
if MailService.Protocol == "smtp" {
for _, ip := range ips {
if !ip.IsLoopback() {
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
break
}
} }
} }
case "dummy": // just mention and do nothing }
// 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 != "" {
@@ -235,6 +213,14 @@ func parseMailerConfig(rootCfg *ini.File) {
MailService.EnvelopeFrom = parsed.Address MailService.EnvelopeFrom = parsed.Address
} }
if MailService.Protocol == "sendmail" {
var err error
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
if err != nil {
log.Error("Failed to parse Sendmail args: %s with error %v", CustomConf, err)
}
}
log.Info("Mail Service Enabled") log.Info("Mail Service Enabled")
} }

View File

@@ -1,43 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"testing"
"github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
)
func TestParseMailerConfig(t *testing.T) {
iniFile := ini.Empty()
kases := map[string]*Mailer{
"smtp.mydomain.com": {
SMTPAddr: "smtp.mydomain.com",
SMTPPort: "465",
},
"smtp.mydomain.com:123": {
SMTPAddr: "smtp.mydomain.com",
SMTPPort: "123",
},
":123": {
SMTPAddr: "127.0.0.1",
SMTPPort: "123",
},
}
for host, kase := range kases {
t.Run(host, func(t *testing.T) {
iniFile.DeleteSection("mailer")
sec := iniFile.Section("mailer")
sec.NewKey("ENABLED", "true")
sec.NewKey("HOST", host)
// Check mailer setting
parseMailerConfig(iniFile)
assert.EqualValues(t, kase.SMTPAddr, MailService.SMTPAddr)
assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort)
})
}
}

View File

@@ -69,7 +69,7 @@ func newPictureService() {
} }
func GetDefaultDisableGravatar() bool { func GetDefaultDisableGravatar() bool {
return OfflineMode return !OfflineMode
} }
func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool { func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool {

View File

@@ -83,7 +83,6 @@ 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
@@ -206,7 +205,6 @@ 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
@@ -221,7 +219,6 @@ var (
DefaultMergeMessageOfficialApproversOnly: true, DefaultMergeMessageOfficialApproversOnly: true,
PopulateSquashCommentWithCommitMessages: false, PopulateSquashCommentWithCommitMessages: false,
AddCoCommitterTrailers: true, AddCoCommitterTrailers: true,
TestConflictingPatchesWithGitApply: true,
}, },
// Issue settings // Issue settings

View File

@@ -22,7 +22,6 @@ 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"
@@ -464,13 +463,6 @@ 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
} }
@@ -610,7 +602,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.19.0", oldSection, oldKey, newSection, newKey) log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be removed in v1.18.0", oldSection, oldKey, newSection, newKey)
} }
} }
@@ -970,11 +962,6 @@ 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 {
@@ -1039,10 +1026,7 @@ 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 = os.Getenv("GITEA_RUN_MODE") RunMode = Cfg.Section("").Key("RUN_MODE").MustString("prod")
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 {
@@ -1166,8 +1150,6 @@ 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()
@@ -1191,15 +1173,7 @@ 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)
} }
val := strings.TrimSpace(string(buf)) return 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:
@@ -1208,19 +1182,6 @@ 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, "/"))
@@ -1334,7 +1295,7 @@ func NewServices() {
newCacheService() newCacheService()
newSessionService() newSessionService()
newCORSService() newCORSService()
parseMailerConfig(Cfg) newMailService()
newRegisterMailService() newRegisterMailService()
newNotifyMailService() newNotifyMailService()
newProxyService() newProxyService()
@@ -1351,5 +1312,5 @@ func NewServices() {
// NewServicesForInstall initializes the services for install // NewServicesForInstall initializes the services for install
func NewServicesForInstall() { func NewServicesForInstall() {
newService() newService()
parseMailerConfig(Cfg) newMailService()
} }

View File

@@ -12,62 +12,48 @@ import (
"time" "time"
) )
const ( // sitemapFileLimit contains the maximum size of a sitemap file
sitemapFileLimit = 50 * 1024 * 1024 // the maximum size of a sitemap file const sitemapFileLimit = 50 * 1024 * 1024
urlsLimit = 50000
schemaURL = "http://www.sitemaps.org/schemas/sitemap/0.9" // Url represents a single sitemap entry
urlsetName = "urlset"
sitemapindexName = "sitemapindex"
)
// URL represents a single sitemap entry
type URL struct { type URL struct {
URL string `xml:"loc"` URL string `xml:"loc"`
LastMod *time.Time `xml:"lastmod,omitempty"` LastMod *time.Time `xml:"lastmod,omitempty"`
} }
// Sitemap represents a sitemap // SitemapUrl represents a sitemap
type Sitemap struct { type Sitemap struct {
XMLName xml.Name XMLName xml.Name
Namespace string `xml:"xmlns,attr"` Namespace string `xml:"xmlns,attr"`
URLs []URL `xml:"url"` URLs []URL `xml:"url"`
Sitemaps []URL `xml:"sitemap"`
} }
// NewSitemap creates a sitemap // NewSitemap creates a sitemap
func NewSitemap() *Sitemap { func NewSitemap() *Sitemap {
return &Sitemap{ return &Sitemap{
XMLName: xml.Name{Local: urlsetName}, XMLName: xml.Name{Local: "urlset"},
Namespace: schemaURL, Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
} }
} }
// NewSitemapIndex creates a sitemap index. // NewSitemap creates a sitemap index.
func NewSitemapIndex() *Sitemap { func NewSitemapIndex() *Sitemap {
return &Sitemap{ return &Sitemap{
XMLName: xml.Name{Local: sitemapindexName}, XMLName: xml.Name{Local: "sitemapindex"},
Namespace: schemaURL, Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
} }
} }
// Add adds a URL to the sitemap // Add adds a URL to the sitemap
func (s *Sitemap) Add(u URL) { func (s *Sitemap) Add(u URL) {
if s.XMLName.Local == sitemapindexName { s.URLs = append(s.URLs, u)
s.Sitemaps = append(s.Sitemaps, u)
} else {
s.URLs = append(s.URLs, u)
}
} }
// WriteTo writes the sitemap to a response // Write writes the sitemap to a response
func (s *Sitemap) WriteTo(w io.Writer) (int64, error) { func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
if l := len(s.URLs); l > urlsLimit { if len(s.URLs) > 50000 {
return 0, fmt.Errorf("The sitemap contains %d URLs, but only %d are allowed", l, urlsLimit) return 0, fmt.Errorf("The sitemap contains too many URLs: %d", len(s.URLs))
}
if l := len(s.Sitemaps); l > urlsLimit {
return 0, fmt.Errorf("The sitemap contains %d sub-sitemaps, but only %d are allowed", l, urlsLimit)
} }
buf := bytes.NewBufferString(xml.Header) buf := bytes.NewBufferString(xml.Header)
if err := xml.NewEncoder(buf).Encode(s); err != nil { if err := xml.NewEncoder(buf).Encode(s); err != nil {
@@ -77,7 +63,7 @@ func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
return 0, err return 0, err
} }
if buf.Len() > sitemapFileLimit { if buf.Len() > sitemapFileLimit {
return 0, fmt.Errorf("The sitemap has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit) return 0, fmt.Errorf("The sitemap is too big: %d", buf.Len())
} }
return buf.WriteTo(w) return buf.WriteTo(w)
} }

View File

@@ -7,6 +7,7 @@ package sitemap
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -14,154 +15,63 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNewSitemap(t *testing.T) { func TestOk(t *testing.T) {
testReal := func(s *Sitemap, name string, urls []URL, expected string) {
for _, url := range urls {
s.Add(url)
}
buf := &bytes.Buffer{}
_, err := s.WriteTo(buf)
assert.NoError(t, nil, err)
assert.Equal(t, xml.Header+"<"+name+" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"+expected+"</"+name+">\n", buf.String())
}
test := func(urls []URL, expected string) {
testReal(NewSitemap(), "urlset", urls, expected)
testReal(NewSitemapIndex(), "sitemapindex", urls, expected)
}
ts := time.Unix(1651322008, 0).UTC() ts := time.Unix(1651322008, 0).UTC()
tests := []struct { test(
name string []URL{},
urls []URL "",
want string )
wantErr string test(
}{ []URL{
{ {URL: "https://gitea.io/test1", LastMod: &ts},
name: "empty",
urls: []URL{},
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"" +
"</urlset>\n",
}, },
{ "<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>",
name: "regular", )
urls: []URL{ test(
{URL: "https://gitea.io/test1", LastMod: &ts}, []URL{
}, {URL: "https://gitea.io/test2", LastMod: nil},
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
"</urlset>\n",
}, },
{ "<url><loc>https://gitea.io/test2</loc></url>",
name: "without lastmod", )
urls: []URL{ test(
{URL: "https://gitea.io/test1"}, []URL{
}, {URL: "https://gitea.io/test1", LastMod: &ts},
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` + {URL: "https://gitea.io/test2", LastMod: nil},
"<url><loc>https://gitea.io/test1</loc></url>" +
"</urlset>\n",
}, },
{ "<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>"+
name: "multiple", "<url><loc>https://gitea.io/test2</loc></url>",
urls: []URL{ )
{URL: "https://gitea.io/test1", LastMod: &ts},
{URL: "https://gitea.io/test2", LastMod: nil},
},
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
"<url><loc>https://gitea.io/test2</loc></url>" +
"</urlset>\n",
},
{
name: "too many urls",
urls: make([]URL, 50001),
wantErr: "The sitemap contains 50001 URLs, but only 50000 are allowed",
},
{
name: "too big file",
urls: []URL{
{URL: strings.Repeat("b", 50*1024*1024+1)},
},
wantErr: "The sitemap has 52428932 bytes, but only 52428800 are allowed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSitemap()
for _, url := range tt.urls {
s.Add(url)
}
buf := &bytes.Buffer{}
_, err := s.WriteTo(buf)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equalf(t, tt.want, buf.String(), "NewSitemap()")
}
})
}
} }
func TestNewSitemapIndex(t *testing.T) { func TestTooManyURLs(t *testing.T) {
ts := time.Unix(1651322008, 0).UTC() s := NewSitemap()
for i := 0; i < 50001; i++ {
tests := []struct { s.Add(URL{URL: fmt.Sprintf("https://gitea.io/test%d", i)})
name string
urls []URL
want string
wantErr string
}{
{
name: "empty",
urls: []URL{},
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"" +
"</sitemapindex>\n",
},
{
name: "regular",
urls: []URL{
{URL: "https://gitea.io/test1", LastMod: &ts},
},
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
"</sitemapindex>\n",
},
{
name: "without lastmod",
urls: []URL{
{URL: "https://gitea.io/test1"},
},
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"<sitemap><loc>https://gitea.io/test1</loc></sitemap>" +
"</sitemapindex>\n",
},
{
name: "multiple",
urls: []URL{
{URL: "https://gitea.io/test1", LastMod: &ts},
{URL: "https://gitea.io/test2", LastMod: nil},
},
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
"<sitemap><loc>https://gitea.io/test2</loc></sitemap>" +
"</sitemapindex>\n",
},
{
name: "too many sitemaps",
urls: make([]URL, 50001),
wantErr: "The sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
},
{
name: "too big file",
urls: []URL{
{URL: strings.Repeat("b", 50*1024*1024+1)},
},
wantErr: "The sitemap has 52428952 bytes, but only 52428800 are allowed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSitemapIndex()
for _, url := range tt.urls {
s.Add(url)
}
buf := &bytes.Buffer{}
_, err := s.WriteTo(buf)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()")
}
})
} }
buf := &bytes.Buffer{}
_, err := s.WriteTo(buf)
assert.EqualError(t, err, "The sitemap contains too many URLs: 50001")
}
func TestSitemapTooBig(t *testing.T) {
s := NewSitemap()
s.Add(URL{URL: strings.Repeat("b", sitemapFileLimit)})
buf := &bytes.Buffer{}
_, err := s.WriteTo(buf)
assert.EqualError(t, err, "The sitemap is too big: 52428931")
} }

View File

@@ -103,8 +103,7 @@ 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)
// 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); err != nil {
if err := util.ApplyUmask(p, os.ModePerm&0o666); err != nil {
return 0, err return 0, err
} }

View File

@@ -5,12 +5,8 @@
package structs package structs
import ( import (
"fmt" "path/filepath"
"path"
"strings"
"time" "time"
"gopkg.in/yaml.v3"
) )
// StateType issue state type // StateType issue state type
@@ -147,47 +143,14 @@ 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 IssueTemplateLabels `json:"labels" yaml:"labels"` Labels []string `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
@@ -200,14 +163,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 base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" { if it.Name == "config.yaml" || it.Name == "config.yml" {
// ignore config.yaml which is a special configuration file // ignore config.yaml which is a special configuration file
return "" return ""
} }
if ext := path.Ext(it.FileName); ext == ".md" { if ext := filepath.Ext(it.FileName); ext == ".md" {
return IssueTemplateTypeMarkdown return IssueTemplateTypeMarkdown
} else if ext == ".yaml" || ext == ".yml" { } else if ext == ".yaml" || ext == ".yml" {
return IssueTemplateTypeYaml return "yaml"
} }
return "" return IssueTemplateTypeYaml
} }

View File

@@ -1,106 +0,0 @@
// 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)
}
})
}
}

View File

@@ -10,7 +10,6 @@ type CreatePushMirrorOption struct {
RemoteUsername string `json:"remote_username"` RemoteUsername string `json:"remote_username"`
RemotePassword string `json:"remote_password"` RemotePassword string `json:"remote_password"`
Interval string `json:"interval"` Interval string `json:"interval"`
SyncOnCommit bool `json:"sync_on_commit"`
} }
// PushMirror represents information of a push mirror // PushMirror represents information of a push mirror
@@ -23,5 +22,4 @@ type PushMirror struct {
LastUpdateUnix string `json:"last_update"` LastUpdateUnix string `json:"last_update"`
LastError string `json:"last_error"` LastError string `json:"last_error"`
Interval string `json:"interval"` Interval string `json:"interval"`
SyncOnCommit bool `json:"sync_on_commit"`
} }

View File

@@ -6,8 +6,7 @@ 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

46
modules/system/setting.go Normal file
View File

@@ -0,0 +1,46 @@
// 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,
})
}

View File

@@ -0,0 +1,34 @@
// 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)
}

View File

@@ -42,6 +42,7 @@ 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"
@@ -86,7 +87,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion return setting.AssetVersion
}, },
"DisableGravatar": func() bool { "DisableGravatar": func() bool {
return system_model.GetSettingBool(system_model.KeyPictureDisableGravatar) return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
}, },
"DefaultShowFullName": func() bool { "DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName return setting.UI.DefaultShowFullName
@@ -646,7 +647,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, avatars.DefaultAvatarClass, others...) size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar vm", others...)
switch t := item.(type) { switch t := item.(type) {
case *user_model.User: case *user_model.User:
@@ -677,7 +678,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, avatars.DefaultAvatarClass, others...) size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
src := repo.RelAvatarLink() src := repo.RelAvatarLink()
if src != "" { if src != "" {
@@ -688,7 +689,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, avatars.DefaultAvatarClass, others...) size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor) src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
if src != "" { if src != "" {

View File

@@ -76,15 +76,8 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
compilingTemplates = false compilingTemplates = false
if !setting.IsProd { if !setting.IsProd {
watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{ watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{
PathsCallback: walkTemplateFiles, PathsCallback: walkTemplateFiles,
BetweenCallback: func() { BetweenCallback: renderer.CompileTemplates,
defer func() {
if err := recover(); err != nil {
log.Error("PANIC: %v\n%s", err, log.Stack(2))
}
}()
renderer.CompileTemplates()
},
}) })
} }
return context.WithValue(ctx, rendererKey, renderer), renderer return context.WithValue(ctx, rendererKey, renderer), renderer

View File

@@ -13,13 +13,8 @@ import (
// TimeStamp defines a timestamp // TimeStamp defines a timestamp
type TimeStamp int64 type TimeStamp int64
var ( // mock is NOT concurrency-safe!!
// mock is NOT concurrency-safe!! var mock time.Time
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) {
@@ -108,5 +103,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 int64(ts) == 0 || int64(ts) == timeZeroUnix return ts.AsTimeInLocation(time.Local).IsZero()
} }

View File

@@ -23,6 +23,20 @@ func EnsureAbsolutePath(path, absoluteBase string) string {
return filepath.Join(absoluteBase, path) return filepath.Join(absoluteBase, path)
} }
const notRegularFileMode os.FileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
// GetDirectorySize returns the disk consumption for a given path
func GetDirectorySize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if info != nil && (info.Mode()&notRegularFileMode) == 0 {
size += info.Size()
}
return err
})
return size, err
}
// IsDir returns true if given path is a directory, // IsDir returns true if given path is a directory,
// or returns false when it's a file or does not exist. // or returns false when it's a file or does not exist.
func IsDir(dir string) (bool, error) { func IsDir(dir string) (bool, error) {

View File

@@ -742,9 +742,6 @@ create_oauth2_application_button=Vytvořit aplikaci
create_oauth2_application_success=Úspěšně jste vytvořili novou OAuth2 aplikaci. create_oauth2_application_success=Úspěšně jste vytvořili novou OAuth2 aplikaci.
update_oauth2_application_success=Úspěšně jste aktualizovali OAuth2 aplikaci. update_oauth2_application_success=Úspěšně jste aktualizovali OAuth2 aplikaci.
oauth2_application_name=Název aplikace oauth2_application_name=Název aplikace
oauth2_select_type=Jaký typ aplikace se hodí?
oauth2_type_web=Webová (např. Node.JS, Tomcat, Go)
oauth2_type_native=Nativní (např. Mobil, Desktop, Prohlížeč)
oauth2_redirect_uri=URI přesměrování oauth2_redirect_uri=URI přesměrování
save_application=Uložit save_application=Uložit
oauth2_client_id=ID klienta oauth2_client_id=ID klienta

View File

@@ -736,9 +736,6 @@ create_oauth2_application_button=Anwendung erstellen
create_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung erstellt. create_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung erstellt.
update_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung bearbeitet. update_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung bearbeitet.
oauth2_application_name=Name der Anwendung oauth2_application_name=Name der Anwendung
oauth2_select_type=Welcher Anwendungstyp passt?
oauth2_type_web=Web (z.B. Node.JS, Tomcat, Go)
oauth2_type_native=Native (z.B. Mobile, Desktop, Browser)
oauth2_redirect_uri=Weiterleitungs-URI oauth2_redirect_uri=Weiterleitungs-URI
save_application=Speichern save_application=Speichern
oauth2_client_id=Client-ID oauth2_client_id=Client-ID

View File

@@ -738,9 +738,6 @@ create_oauth2_application_button=Δημιουργία Εφαρμογής
create_oauth2_application_success=Δημιουργήσατε επιτυχώς μια νέα εφαρμογή OAuth2. create_oauth2_application_success=Δημιουργήσατε επιτυχώς μια νέα εφαρμογή OAuth2.
update_oauth2_application_success=Ενημερώσατε επιτυχώς την εφαρμογή OAuth2. update_oauth2_application_success=Ενημερώσατε επιτυχώς την εφαρμογή OAuth2.
oauth2_application_name=Όνομα Εφαρμογής oauth2_application_name=Όνομα Εφαρμογής
oauth2_select_type=Ποιος τύπος εφαρμογής ταιριάζει;
oauth2_type_web=Web (πχ Node.JS, Tomcat, Go)
oauth2_type_native=Εγγενές (π.χ. Κινητό, Επιφάνεια Εργασίας, Πρόγραμμα Περιήγησης)
oauth2_redirect_uri=URI Ανακατεύθυνσης oauth2_redirect_uri=URI Ανακατεύθυνσης
save_application=Αποθήκευση save_application=Αποθήκευση
oauth2_client_id=Ταυτότητα Πελάτη oauth2_client_id=Ταυτότητα Πελάτη

View File

@@ -497,7 +497,6 @@ team_not_exist = The team does not exist.
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization. last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
cannot_add_org_to_team = An organization cannot be added as a team member. cannot_add_org_to_team = An organization cannot be added as a team member.
duplicate_invite_to_team = The user was already invited as a team member. duplicate_invite_to_team = The user was already invited as a team member.
organization_leave_success = You have successfully left the organization %s.
invalid_ssh_key = Can not verify your SSH key: %s invalid_ssh_key = Can not verify your SSH key: %s
invalid_gpg_key = Can not verify your GPG key: %s invalid_gpg_key = Can not verify your GPG key: %s

View File

@@ -749,9 +749,6 @@ create_oauth2_application_button=Crear Aplicación
create_oauth2_application_success=Ha creado una nueva aplicación OAuth2 con éxito. create_oauth2_application_success=Ha creado una nueva aplicación OAuth2 con éxito.
update_oauth2_application_success=Ha actualizado correctamente la aplicación OAuth2. update_oauth2_application_success=Ha actualizado correctamente la aplicación OAuth2.
oauth2_application_name=Nombre de la Aplicación oauth2_application_name=Nombre de la Aplicación
oauth2_select_type=¿Qué tipo de aplicación es?
oauth2_type_web=Web (por ejemplo: Node.JS, Tomcat, Go)
oauth2_type_native=Nativa (por ejemplo, móvil, escritorio, navegador)
oauth2_redirect_uri=URI de redireccionado oauth2_redirect_uri=URI de redireccionado
save_application=Guardar save_application=Guardar
oauth2_client_id=ID de cliente oauth2_client_id=ID de cliente

View File

@@ -675,9 +675,6 @@ create_oauth2_application_button=ایجاد برنامه
create_oauth2_application_success=برنامه OAuth2 جدید شما با موفقیت ساخته شد. create_oauth2_application_success=برنامه OAuth2 جدید شما با موفقیت ساخته شد.
update_oauth2_application_success=برنامه OAuth2 با موفقیت به‎روزرسانی شد. update_oauth2_application_success=برنامه OAuth2 با موفقیت به‎روزرسانی شد.
oauth2_application_name=نام برنامه oauth2_application_name=نام برنامه
oauth2_select_type=کدام نوع برنامه متناسب است؟
oauth2_type_web=وب (مثلا Node.JS, Tomcat, Go)
oauth2_type_native=بومی (مثلا، Mobile, Desktop, Browser)
oauth2_redirect_uri=تغییر مسیر به نشانی اینترنتی oauth2_redirect_uri=تغییر مسیر به نشانی اینترنتی
save_application=ذخيره save_application=ذخيره
oauth2_client_id=شناسه کلاینت oauth2_client_id=شناسه کلاینت

View File

@@ -742,9 +742,6 @@ create_oauth2_application_button=Créer une application
create_oauth2_application_success=Vous avez créé avec succès une nouvelle application OAuth2. create_oauth2_application_success=Vous avez créé avec succès une nouvelle application OAuth2.
update_oauth2_application_success=Vous avez mis à jour l'application OAuth2 avec succès. update_oauth2_application_success=Vous avez mis à jour l'application OAuth2 avec succès.
oauth2_application_name=Nom de l'Application oauth2_application_name=Nom de l'Application
oauth2_select_type=De quel type d'application s'agit-il ?
oauth2_type_web=Web (par exemple Node.JS, Tomcat, Go)
oauth2_type_native=Native (par exemple Mobile, Bureau, Navigateur)
oauth2_redirect_uri=URL de redirection oauth2_redirect_uri=URL de redirection
save_application=Enregistrer save_application=Enregistrer
oauth2_client_id=ID du client oauth2_client_id=ID du client

View File

@@ -526,9 +526,6 @@ create_oauth2_application_button=Alkalmazás létrehozása
create_oauth2_application_success=Sikerült létrehozni egy új OAuth2 alkalmazást. create_oauth2_application_success=Sikerült létrehozni egy új OAuth2 alkalmazást.
update_oauth2_application_success=Sikerült módosítani az OAuth2 alkalmazást. update_oauth2_application_success=Sikerült módosítani az OAuth2 alkalmazást.
oauth2_application_name=Alkalmazásnév oauth2_application_name=Alkalmazásnév
oauth2_select_type=Melyik típus felelne meg?
oauth2_type_web=Web (pl. Node.JS, Tomcat, Go)
oauth2_type_native=Natív (pl. Mobil, PC, Böngésző)
oauth2_redirect_uri=Átirányítási URI oauth2_redirect_uri=Átirányítási URI
save_application=Mentés save_application=Mentés
oauth2_client_id=Ügyfélazonosító oauth2_client_id=Ügyfélazonosító

Some files were not shown because too many files have changed in this diff Show More