Compare commits

..

1 Commits

Author SHA1 Message Date
techknowlogick
49a4e4555a [skip ci] Updated translations via Crowdin 2022-10-26 00:20:58 +00:00
332 changed files with 3206 additions and 6171 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

@@ -769,16 +769,10 @@ steps:
image: woodpeckerci/plugin-s3:latest image: woodpeckerci/plugin-s3:latest
pull: always pull: always
settings: settings:
acl: acl: public-read
from_secret: aws_s3_acl bucket: gitea-artifacts
region: endpoint: https://ams3.digitaloceanspaces.com
from_secret: aws_s3_region path_style: true
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
target: "/gitea/${DRONE_BRANCH##release/v}" target: "/gitea/${DRONE_BRANCH##release/v}"
@@ -796,16 +790,10 @@ steps:
- name: release-main - name: release-main
image: woodpeckerci/plugin-s3:latest image: woodpeckerci/plugin-s3:latest
settings: settings:
acl: acl: public-read
from_secret: aws_s3_acl bucket: gitea-artifacts
region: endpoint: https://ams3.digitaloceanspaces.com
from_secret: aws_s3_region path_style: true
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
target: /gitea/main target: /gitea/main
@@ -904,16 +892,10 @@ steps:
image: woodpeckerci/plugin-s3:latest image: woodpeckerci/plugin-s3:latest
pull: always pull: always
settings: settings:
acl: acl: public-read
from_secret: aws_s3_acl bucket: gitea-artifacts
region: endpoint: https://ams3.digitaloceanspaces.com
from_secret: aws_s3_region path_style: true
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
target: "/gitea/${DRONE_TAG##v}" target: "/gitea/${DRONE_TAG##v}"
@@ -959,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

@@ -173,6 +173,3 @@ issues:
linters: linters:
- revive - revive
text: "exported: type name will be used as user.UserBadge by other packages, and that stutters; consider calling this Badge" text: "exported: type name will be used as user.UserBadge by other packages, and that stutters; consider calling this Badge"
- path: models/db/sql_postgres_with_schema.go
linters:
- nolintlint

View File

@@ -4,354 +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.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
* SECURITY
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
* Add command to bulk set must-change-password (#22823) (#22928)
* ENHANCEMENTS
* Use import of OCI structs (#22765) (#22805)
* Fix color of tertiary button on dark theme (#22739) (#22744)
* Link issue and pull requests status change in UI notifications directly to their event in the timelined view. (#22627) (#22642)
* BUGFIXES
* Notify on container image create (#22806) (#22965)
* Fix blame view missing lines (#22826) (#22929)
* Fix incorrect role labels for migrated issues and comments (#22914) (#22923)
* Fix PR file tree folders no longer collapsing (#22864) (#22872)
* Escape filename when assemble URL (#22850) (#22871)
* Fix isAllowed of escapeStreamer (#22814) (#22837)
* Load issue before accessing index in merge message (#22822) (#22830)
* Improve trace logging for pulls and processes (#22633) (#22812)
* Fix restore repo bug, clarify the problem of ForeignIndex (#22776) (#22794)
* Add default user visibility to cli command "admin user create" (#22750) (#22760)
* Escape path for the file list (#22741) (#22757)
* Fix bugs with WebAuthn preventing sign in and registration. (#22651) (#22721)
* Add missing close bracket in imagediff (#22710) (#22712)
* Move code comments to a standalone file and fix the bug when adding a reply to an outdated review appears to not post(#20821) (#22707)
* Fix line spacing for plaintext previews (#22699) (#22701)
* Fix wrong hint when deleting a branch successfully from pull request UI (#22673) (#22698)
* Fix README TOC links (#22577) (#22677)
* Fix missing message in git hook when pull requests disabled on fork (#22625) (#22658)
* Improve checkIfPRContentChanged (#22611) (#22644)
* Prevent duplicate labels when importing more than 99 (#22591) (#22598)
* Don't return duplicated users who can create org repo (#22560) (#22562)
* BUILD
* Upgrade golangcilint to v1.51.0 (#22764)
* MISC
* Use proxy for pull mirror (#22771) (#22772)
* Use `--index-url` in PyPi description (#22620) (#22636)
## [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

@@ -28,8 +28,8 @@ XGO_VERSION := go-1.19.x
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4 AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.5.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.5.0
ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.1 ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.1
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.3.1
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.0
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.0
@@ -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

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

@@ -6,6 +6,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -16,14 +17,20 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
pwd "code.gitea.io/gitea/modules/password"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
auth_service "code.gitea.io/gitea/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -42,6 +49,142 @@ var (
}, },
} }
subcmdUser = cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
microcmdUserGenerateAccessToken,
},
}
microcmdUserList = cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},
},
}
microcmdUserCreate = cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
cli.StringFlag{
Name: "username",
Usage: "Username",
},
cli.StringFlag{
Name: "password",
Usage: "User password",
},
cli.StringFlag{
Name: "email",
Usage: "User email address",
},
cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
},
cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
},
}
microcmdUserChangePassword = cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "The user to change password for",
},
cli.StringFlag{
Name: "password,p",
Value: "",
Usage: "New password to set for user",
},
},
}
microcmdUserDelete = cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
cli.StringFlag{
Name: "username,u",
Usage: "Username of the user to delete",
},
cli.StringFlag{
Name: "email,e",
Usage: "Email of the user to delete",
},
cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
}
microcmdUserGenerateAccessToken = cli.Command{
Name: "generate-access-token",
Usage: "Generate a access token for a specific user",
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Usage: "Username",
},
cli.StringFlag{
Name: "token-name,t",
Usage: "Token name",
Value: "gitea-admin",
},
cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
},
Action: runGenerateAccessToken,
}
subcmdRepoSyncReleases = cli.Command{ subcmdRepoSyncReleases = cli.Command{
Name: "repo-sync-releases", Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
@@ -270,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",
@@ -325,6 +468,255 @@ var (
} }
) )
func runChangePassword(c *cli.Context) error {
if err := argsSet(c, "username", "password"); err != nil {
return err
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if len(c.String("password")) < setting.MinPasswordLength {
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
}
if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("Password does not meet complexity requirements")
}
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
if err != nil {
return err
}
if pwned {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
}
uname := c.String("username")
user, err := user_model.GetUserByName(ctx, uname)
if err != nil {
return err
}
if err = user.SetPassword(c.String("password")); err != nil {
return err
}
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err
}
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
return nil
}
func runCreateUser(c *cli.Context) error {
if err := argsSet(c, "email"); err != nil {
return err
}
if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags")
}
if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("One of --name or --username flags must be set")
}
if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags")
}
var username string
if c.IsSet("username") {
username = c.String("username")
} else {
username = c.String("name")
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
var password string
if c.IsSet("password") {
password = c.String("password")
} else if c.IsSet("random-password") {
var err error
password, err = pwd.Generate(c.Int("random-password-length"))
if err != nil {
return err
}
fmt.Printf("generated random password is '%s'\n", password)
} else {
return errors.New("must set either password or random-password flag")
}
// always default to true
changePassword := true
// If this is the first user being created.
// Take it as the admin and don't force a password update.
if n := user_model.CountUsers(nil); n == 0 {
changePassword = false
}
if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
}
restricted := util.OptionalBoolNone
if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted"))
}
u := &user_model.User{
Name: username,
Email: c.String("email"),
Passwd: password,
IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue,
IsRestricted: restricted,
}
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}
if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
UID: u.ID,
}
if err := auth_model.NewAccessToken(t); err != nil {
return err
}
fmt.Printf("Access token was successfully created... %s\n", t.Token)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}
func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
users, err := user_model.GetAllUsers()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users {
if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
}
}
} else {
twofa := user_model.UserList(users).GetTwoFaStatus()
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
}
}
w.Flush()
return nil
}
func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return fmt.Errorf("You must provide the id, username or email of a user to delete")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if err := storage.Init(); err != nil {
return err
}
var err error
var user *user_model.User
if c.IsSet("email") {
user, err = user_model.GetUserByEmail(c.String("email"))
} else if c.IsSet("username") {
user, err = user_model.GetUserByName(ctx, c.String("username"))
} else {
user, err = user_model.GetUserByID(c.Int64("id"))
}
if err != nil {
return err
}
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
}
if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
}
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
}
func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") {
return fmt.Errorf("You must provide the username to generate a token for them")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
t := &auth_model.AccessToken{
Name: c.String("token-name"),
UID: user.ID,
}
if err := auth_model.NewAccessToken(t); err != nil {
return err
}
if c.Bool("raw") {
fmt.Printf("%s\n", t.Token)
} else {
fmt.Printf("Access token was successfully created: %s\n", t.Token)
}
return nil
}
func runRepoSyncReleases(_ *cli.Context) error { func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals() ctx, cancel := installSignals()
defer cancel() defer cancel()
@@ -563,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

@@ -1,21 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"github.com/urfave/cli"
)
var subcmdUser = cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword,
},
}

View File

@@ -1,76 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
)
var microcmdUserChangePassword = cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "The user to change password for",
},
cli.StringFlag{
Name: "password,p",
Value: "",
Usage: "New password to set for user",
},
},
}
func runChangePassword(c *cli.Context) error {
if err := argsSet(c, "username", "password"); err != nil {
return err
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if len(c.String("password")) < setting.MinPasswordLength {
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
}
if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("Password does not meet complexity requirements")
}
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
if err != nil {
return err
}
if pwned {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
}
uname := c.String("username")
user, err := user_model.GetUserByName(ctx, uname)
if err != nil {
return err
}
if err = user.SetPassword(c.String("password")); err != nil {
return err
}
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err
}
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
return nil
}

View File

@@ -1,169 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"errors"
"fmt"
"os"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
)
var microcmdUserCreate = cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
cli.StringFlag{
Name: "username",
Usage: "Username",
},
cli.StringFlag{
Name: "password",
Usage: "User password",
},
cli.StringFlag{
Name: "email",
Usage: "User email address",
},
cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
},
cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
},
}
func runCreateUser(c *cli.Context) error {
if err := argsSet(c, "email"); err != nil {
return err
}
if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags")
}
if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("One of --name or --username flags must be set")
}
if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags")
}
var username string
if c.IsSet("username") {
username = c.String("username")
} else {
username = c.String("name")
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
var password string
if c.IsSet("password") {
password = c.String("password")
} else if c.IsSet("random-password") {
var err error
password, err = pwd.Generate(c.Int("random-password-length"))
if err != nil {
return err
}
fmt.Printf("generated random password is '%s'\n", password)
} else {
return errors.New("must set either password or random-password flag")
}
// always default to true
changePassword := true
// If this is the first user being created.
// Take it as the admin and don't force a password update.
if n := user_model.CountUsers(nil); n == 0 {
changePassword = false
}
if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
}
restricted := util.OptionalBoolNone
if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted"))
}
// default user visibility in app.ini
visibility := setting.Service.DefaultUserVisibilityMode
u := &user_model.User{
Name: username,
Email: c.String("email"),
Passwd: password,
IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
Visibility: visibility,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue,
IsRestricted: restricted,
}
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}
if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
UID: u.ID,
}
if err := auth_model.NewAccessToken(t); err != nil {
return err
}
fmt.Printf("Access token was successfully created... %s\n", t.Token)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}

View File

@@ -1,78 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli"
)
var microcmdUserDelete = cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
cli.StringFlag{
Name: "username,u",
Usage: "Username of the user to delete",
},
cli.StringFlag{
Name: "email,e",
Usage: "Email of the user to delete",
},
cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
}
func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return fmt.Errorf("You must provide the id, username or email of a user to delete")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if err := storage.Init(); err != nil {
return err
}
var err error
var user *user_model.User
if c.IsSet("email") {
user, err = user_model.GetUserByEmail(c.String("email"))
} else if c.IsSet("username") {
user, err = user_model.GetUserByName(ctx, c.String("username"))
} else {
user, err = user_model.GetUserByID(c.Int64("id"))
}
if err != nil {
return err
}
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
}
if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
}
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
}

View File

@@ -1,69 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
)
var microcmdUserGenerateAccessToken = cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Usage: "Username",
},
cli.StringFlag{
Name: "token-name,t",
Usage: "Token name",
Value: "gitea-admin",
},
cli.BoolFlag{
Name: "raw",
Usage: "Display only the token value",
},
},
Action: runGenerateAccessToken,
}
func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") {
return fmt.Errorf("You must provide a username to generate a token for")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
t := &auth_model.AccessToken{
Name: c.String("token-name"),
UID: user.ID,
}
if err := auth_model.NewAccessToken(t); err != nil {
return err
}
if c.Bool("raw") {
fmt.Printf("%s\n", t.Token)
} else {
fmt.Printf("Access token was successfully created: %s\n", t.Token)
}
return nil
}

View File

@@ -1,60 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"text/tabwriter"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
)
var microcmdUserList = cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "admin",
Usage: "List only admin users",
},
},
}
func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
users, err := user_model.GetAllUsers()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
if c.IsSet("admin") {
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
for _, u := range users {
if u.IsAdmin {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
}
}
} else {
twofa := user_model.UserList(users).GetTwoFaStatus()
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
}
}
w.Flush()
return nil
}

View File

@@ -1,58 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"errors"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli"
)
var microcmdUserMustChangePassword = cli.Command{
Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "all,A",
Usage: "All users must change password, except those explicitly excluded with --exclude",
},
cli.StringSliceFlag{
Name: "exclude,e",
Usage: "Do not change the must-change-password flag for these users",
},
cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
},
}
func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if c.NArg() == 0 && !c.IsSet("all") {
return errors.New("either usernames or --all must be provided")
}
mustChangePassword := !c.Bool("unset")
all := c.Bool("all")
exclude := c.StringSlice("exclude")
if err := initDB(ctx); err != nil {
return err
}
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args(), exclude)
if err != nil {
return err
}
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
return nil
}

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`)
@@ -523,21 +522,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
- `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server.
- `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary. - `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary.
- `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining INTERNAL_TOKEN in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) - `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining INTERNAL_TOKEN in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`)
- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, pbkdf2_v1, scrypt, bcrypt\], argon2 and scrypt will spend significant amounts of memory. - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others.
- Note: The default parameters for `pbkdf2` hashing have changed - the previous settings are available as `pbkdf2_v1` but are not recommended.
- The hash functions may be tuned by using `$` after the algorithm:
- `argon2$<time>$<memory>$<threads>$<key-length>`
- `bcrypt$<cost>`
- `pbkdf2$<iterations>$<key-length>`
- `scrypt$<n>$<r>$<p>$<key-length>`
- The defaults are:
- `argon2`: `argon2$2$65536$8$50`
- `bcrypt`: `bcrypt$10`
- `pbkdf2`: `pbkdf2$320000$50`
- `pbkdf2_v1`: `pbkdf2$10000$50`
- `pbkdf2_v2`: `pbkdf2$320000$50`
- `scrypt`: `scrypt$65536$16$2$50`
- Adjusting the algorithm parameters using this functionality is done at your own risk.
- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie.
- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users. - `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users.
- `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off): - `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off):
@@ -687,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.
@@ -749,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.

View File

@@ -77,8 +77,6 @@ For example:
pip install --index-url https://testuser:password123@gitea.example.com/api/packages/testuser/pypi/simple --no-deps test_package pip install --index-url https://testuser:password123@gitea.example.com/api/packages/testuser/pypi/simple --no-deps test_package
``` ```
You can use `--extra-index-url` instead of `--index-url` but that makes you vulnerable to dependency confusion attacks because `pip` checks the official PyPi repository for the package before it checks the specified custom repository. Read the `pip` docs for more information.
## Supported commands ## Supported commands
``` ```

View File

@@ -99,13 +99,6 @@ Admin operations:
- `--password value`, `-p value`: New password. Required. - `--password value`, `-p value`: New password. Required.
- Examples: - Examples:
- `gitea admin user change-password --username myname --password asecurepassword` - `gitea admin user change-password --username myname --password asecurepassword`
- `must-change-password`:
- Args:
- `[username...]`: Users that must change their passwords
- Options:
- `--all`, `-A`: Force a password change for all users
- `--exclude username`, `-e username`: Exclude the given user. Can be set multiple times.
- `--unset`: Revoke forced password change for the given users
- `regenerate` - `regenerate`
- Options: - Options:
- `hooks`: Regenerate Git Hooks for all repositories - `hooks`: Regenerate Git Hooks for all repositories

35
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
@@ -75,8 +75,6 @@ require (
github.com/niklasfasching/go-org v1.6.5 github.com/niklasfasching/go-org v1.6.5
github.com/oliamb/cutter v0.2.2 github.com/oliamb/cutter v0.2.2
github.com/olivere/elastic/v7 v7.0.32 github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.3.0 github.com/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
@@ -96,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
@@ -131,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
@@ -287,7 +285,6 @@ require (
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect
@@ -305,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

76
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=
@@ -1174,10 +1179,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -1486,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=
@@ -1609,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=
@@ -1722,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=
@@ -1763,8 +1762,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1878,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=
@@ -1894,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

@@ -157,7 +157,7 @@ func CreateRepoTransferNotification(doer, newOwner *user_model.User, repo *repo_
} }
for i := range users { for i := range users {
notify = append(notify, &Notification{ notify = append(notify, &Notification{
UserID: i, UserID: users[i].ID,
RepoID: repo.ID, RepoID: repo.ID,
Status: NotificationStatusUnread, Status: NotificationStatusUnread,
UpdatedBy: doer.ID, UpdatedBy: doer.ID,

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

@@ -24,10 +24,8 @@ type contextKey struct {
} }
// enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context // enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
var ( var enginedContextKey = &contextKey{"engined"}
enginedContextKey = &contextKey{"engined"} var _ Engined = &Context{}
_ Engined = &Context{}
)
// Context represents a db context // Context represents a db context
type Context struct { type Context struct {

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

@@ -5,11 +5,8 @@
package db package db
import ( import (
"context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
) )
@@ -22,7 +19,6 @@ const (
type Paginator interface { type Paginator interface {
GetSkipTake() (skip, take int) GetSkipTake() (skip, take int)
GetStartEnd() (start, end int) GetStartEnd() (start, end int)
IsListAll() bool
} }
// GetPaginatedSession creates a paginated database session // GetPaginatedSession creates a paginated database session
@@ -49,12 +45,9 @@ func SetEnginePagination(e Engine, p Paginator) Engine {
// ListOptions options to paginate results // ListOptions options to paginate results
type ListOptions struct { type ListOptions struct {
PageSize int PageSize int
Page int // start from 1 Page int // start from 1
ListAll bool // if true, then PageSize and Page will not be taken
} }
var _ Paginator = &ListOptions{}
// GetSkipTake returns the skip and take values // GetSkipTake returns the skip and take values
func (opts *ListOptions) GetSkipTake() (skip, take int) { func (opts *ListOptions) GetSkipTake() (skip, take int) {
opts.SetDefaultValues() opts.SetDefaultValues()
@@ -68,11 +61,6 @@ func (opts *ListOptions) GetStartEnd() (start, end int) {
return start, end return start, end
} }
// IsListAll indicates PageSize and Page will be ignored
func (opts *ListOptions) IsListAll() bool {
return opts.ListAll
}
// SetDefaultValues sets default values // SetDefaultValues sets default values
func (opts *ListOptions) SetDefaultValues() { func (opts *ListOptions) SetDefaultValues() {
if opts.PageSize <= 0 { if opts.PageSize <= 0 {
@@ -92,8 +80,6 @@ type AbsoluteListOptions struct {
take int take int
} }
var _ Paginator = &AbsoluteListOptions{}
// NewAbsoluteListOptions creates a list option with applied limits // NewAbsoluteListOptions creates a list option with applied limits
func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions { func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions {
if skip < 0 { if skip < 0 {
@@ -108,11 +94,6 @@ func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions {
return &AbsoluteListOptions{skip, take} return &AbsoluteListOptions{skip, take}
} }
// IsListAll will always return false
func (opts *AbsoluteListOptions) IsListAll() bool {
return false
}
// GetSkipTake returns the skip and take values // GetSkipTake returns the skip and take values
func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) { func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
return opts.skip, opts.take return opts.skip, opts.take
@@ -122,32 +103,3 @@ func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) { func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) {
return opts.skip, opts.skip + opts.take return opts.skip, opts.skip + opts.take
} }
// FindOptions represents a find options
type FindOptions interface {
Paginator
ToConds() builder.Cond
}
// Find represents a common find function which accept an options interface
func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
sess := GetEngine(ctx).Where(opts.ToConds())
if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake())
}
return sess.Find(&objects)
}
// Count represents a common count function which accept an options interface
func Count[T any](ctx context.Context, opts FindOptions, object T) (int64, error) {
return GetEngine(ctx).Where(opts.ToConds()).Count(object)
}
// FindAndCount represents a common findandcount function which accept an options interface
func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (int64, error) {
sess := GetEngine(ctx).Where(opts.ToConds())
if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake())
}
return sess.FindAndCount(&objects)
}

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

@@ -140,14 +140,3 @@
num_members: 1 num_members: 1
includes_all_repositories: false includes_all_repositories: false
can_create_org_repo: false can_create_org_repo: false
-
id: 14
org_id: 3
lower_name: teamcreaterepo
name: teamCreateRepo
authorize: 2 # write
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true

View File

@@ -93,9 +93,3 @@
org_id: 19 org_id: 19
team_id: 6 team_id: 6
uid: 31 uid: 31
-
id: 17
org_id: 3
team_id: 14
uid: 2

View File

@@ -8,8 +8,8 @@
email: user1@example.com email: user1@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user1 login_name: user1
@@ -45,8 +45,8 @@
email: user2@example.com email: user2@example.com
keep_email_private: true keep_email_private: true
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user2 login_name: user2
@@ -82,8 +82,8 @@
email: user3@example.com email: user3@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: onmention email_notifications_preference: onmention
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user3 login_name: user3
@@ -104,7 +104,7 @@
num_following: 0 num_following: 0
num_stars: 0 num_stars: 0
num_repos: 3 num_repos: 3
num_teams: 5 num_teams: 4
num_members: 3 num_members: 3
visibility: 0 visibility: 0
repo_admin_change_team_access: false repo_admin_change_team_access: false
@@ -119,8 +119,8 @@
email: user4@example.com email: user4@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: onmention email_notifications_preference: onmention
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user4 login_name: user4
@@ -156,8 +156,8 @@
email: user5@example.com email: user5@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user5 login_name: user5
@@ -193,8 +193,8 @@
email: user6@example.com email: user6@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user6 login_name: user6
@@ -230,8 +230,8 @@
email: user7@example.com email: user7@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: disabled email_notifications_preference: disabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user7 login_name: user7
@@ -267,8 +267,8 @@
email: user8@example.com email: user8@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user8 login_name: user8
@@ -304,8 +304,8 @@
email: user9@example.com email: user9@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: onmention email_notifications_preference: onmention
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user9 login_name: user9
@@ -341,8 +341,8 @@
email: user10@example.com email: user10@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user10 login_name: user10
@@ -378,8 +378,8 @@
email: user11@example.com email: user11@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user11 login_name: user11
@@ -415,8 +415,8 @@
email: user12@example.com email: user12@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user12 login_name: user12
@@ -452,8 +452,8 @@
email: user13@example.com email: user13@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user13 login_name: user13
@@ -489,8 +489,8 @@
email: user14@example.com email: user14@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user14 login_name: user14
@@ -526,8 +526,8 @@
email: user15@example.com email: user15@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user15 login_name: user15
@@ -563,8 +563,8 @@
email: user16@example.com email: user16@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user16 login_name: user16
@@ -600,8 +600,8 @@
email: user17@example.com email: user17@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user17 login_name: user17
@@ -637,8 +637,8 @@
email: user18@example.com email: user18@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user18 login_name: user18
@@ -674,8 +674,8 @@
email: user19@example.com email: user19@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user19 login_name: user19
@@ -711,8 +711,8 @@
email: user20@example.com email: user20@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user20 login_name: user20
@@ -748,8 +748,8 @@
email: user21@example.com email: user21@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user21 login_name: user21
@@ -785,8 +785,8 @@
email: limited_org@example.com email: limited_org@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: limited_org login_name: limited_org
@@ -822,8 +822,8 @@
email: privated_org@example.com email: privated_org@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: privated_org login_name: privated_org
@@ -859,8 +859,8 @@
email: user24@example.com email: user24@example.com
keep_email_private: true keep_email_private: true
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user24 login_name: user24
@@ -896,8 +896,8 @@
email: org25@example.com email: org25@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: org25 login_name: org25
@@ -933,8 +933,8 @@
email: org26@example.com email: org26@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: onmention email_notifications_preference: onmention
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: org26 login_name: org26
@@ -970,8 +970,8 @@
email: user27@example.com email: user27@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user27 login_name: user27
@@ -1007,8 +1007,8 @@
email: user28@example.com email: user28@example.com
keep_email_private: true keep_email_private: true
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user28 login_name: user28
@@ -1044,8 +1044,8 @@
email: user29@example.com email: user29@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user29 login_name: user29
@@ -1081,8 +1081,8 @@
email: user30@example.com email: user30@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user30 login_name: user30
@@ -1118,8 +1118,8 @@
email: user31@example.com email: user31@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user31 login_name: user31
@@ -1155,7 +1155,7 @@
email: user32@example.com email: user32@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f47017 passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a
passwd_hash_algo: argon2 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
@@ -1192,8 +1192,8 @@
email: user33@example.com email: user33@example.com
keep_email_private: false keep_email_private: false
email_notifications_preference: enabled email_notifications_preference: enabled
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889 passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
passwd_hash_algo: pbkdf2$50000$50 passwd_hash_algo: argon2
must_change_password: false must_change_password: false
login_source: 0 login_source: 0
login_name: user33 login_name: user33

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

@@ -9,7 +9,9 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings"
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@@ -21,6 +23,8 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"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/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@@ -693,6 +697,31 @@ func (c *Comment) LoadReview() error {
return c.loadReview(db.DefaultContext) return c.loadReview(db.DefaultContext)
} }
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
func (c *Comment) checkInvalidation(doer *user_model.User, repo *git.Repository, branch string) error {
// FIXME differentiate between previous and proposed line
commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
c.Invalidated = true
return UpdateComment(c, doer)
}
if err != nil {
return err
}
if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() {
c.Invalidated = true
return UpdateComment(c, doer)
}
return nil
}
// CheckInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated.
func (c *Comment) CheckInvalidation(repo *git.Repository, doer *user_model.User, branch string) error {
return c.checkInvalidation(doer, repo, branch)
}
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes. // DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
func (c *Comment) DiffSide() string { func (c *Comment) DiffSide() string {
if c.Line < 0 { if c.Line < 0 {
@@ -1036,28 +1065,23 @@ func GetCommentByID(ctx context.Context, id int64) (*Comment, error) {
// FindCommentsOptions describes the conditions to Find comments // FindCommentsOptions describes the conditions to Find comments
type FindCommentsOptions struct { type FindCommentsOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
IssueID int64 IssueID int64
ReviewID int64 ReviewID int64
Since int64 Since int64
Before int64 Before int64
Line int64 Line int64
TreePath string TreePath string
Type CommentType Type CommentType
IssueIDs []int64
Invalidated util.OptionalBool
} }
// ToConds implements FindOptions interface func (opts *FindCommentsOptions) toConds() builder.Cond {
func (opts *FindCommentsOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
} }
if opts.IssueID > 0 { if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID}) cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
} else if len(opts.IssueIDs) > 0 {
cond = cond.And(builder.In("comment.issue_id", opts.IssueIDs))
} }
if opts.ReviewID > 0 { if opts.ReviewID > 0 {
cond = cond.And(builder.Eq{"comment.review_id": opts.ReviewID}) cond = cond.And(builder.Eq{"comment.review_id": opts.ReviewID})
@@ -1077,16 +1101,13 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
if len(opts.TreePath) > 0 { if len(opts.TreePath) > 0 {
cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath}) cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
} }
if !opts.Invalidated.IsNone() {
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
}
return cond return cond
} }
// FindComments returns all comments according options // FindComments returns all comments according options
func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, error) { func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, error) {
comments := make([]*Comment, 0, 10) comments := make([]*Comment, 0, 10)
sess := db.GetEngine(ctx).Where(opts.ToConds()) sess := db.GetEngine(ctx).Where(opts.toConds())
if opts.RepoID > 0 { if opts.RepoID > 0 {
sess.Join("INNER", "issue", "issue.id = comment.issue_id") sess.Join("INNER", "issue", "issue.id = comment.issue_id")
} }
@@ -1105,19 +1126,13 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, e
// CountComments count all comments according options by ignoring pagination // CountComments count all comments according options by ignoring pagination
func CountComments(opts *FindCommentsOptions) (int64, error) { func CountComments(opts *FindCommentsOptions) (int64, error) {
sess := db.GetEngine(db.DefaultContext).Where(opts.ToConds()) sess := db.GetEngine(db.DefaultContext).Where(opts.toConds())
if opts.RepoID > 0 { if opts.RepoID > 0 {
sess.Join("INNER", "issue", "issue.id = comment.issue_id") sess.Join("INNER", "issue", "issue.id = comment.issue_id")
} }
return sess.Count(&Comment{}) return sess.Count(&Comment{})
} }
// UpdateCommentInvalidate updates comment invalidated column
func UpdateCommentInvalidate(ctx context.Context, c *Comment) error {
_, err := db.GetEngine(ctx).ID(c.ID).Cols("invalidated").Update(c)
return err
}
// UpdateComment updates information of comment. // UpdateComment updates information of comment.
func UpdateComment(c *Comment, doer *user_model.User) error { func UpdateComment(c *Comment, doer *user_model.User) error {
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()
@@ -1176,6 +1191,120 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID}) return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID})
} }
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map[string]map[int64][]*Comment
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
}
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (CodeComments, error) {
pathToLineToComment := make(CodeComments)
if review == nil {
review = &Review{ID: 0}
}
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
ReviewID: review.ID,
}
comments, err := findCodeComments(ctx, opts, issue, currentUser, review)
if err != nil {
return nil, err
}
for _, comment := range comments {
if pathToLineToComment[comment.TreePath] == nil {
pathToLineToComment[comment.TreePath] = make(map[int64][]*Comment)
}
pathToLineToComment[comment.TreePath][comment.Line] = append(pathToLineToComment[comment.TreePath][comment.Line], comment)
}
return pathToLineToComment, nil
}
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review) ([]*Comment, error) {
var comments []*Comment
if review == nil {
review = &Review{ID: 0}
}
conds := opts.toConds()
if review.ID == 0 {
conds = conds.And(builder.Eq{"invalidated": false})
}
e := db.GetEngine(ctx)
if err := e.Where(conds).
Asc("comment.created_unix").
Asc("comment.id").
Find(&comments); err != nil {
return nil, err
}
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
if err := CommentList(comments).loadPosters(ctx); err != nil {
return nil, err
}
// Find all reviews by ReviewID
reviews := make(map[int64]*Review)
ids := make([]int64, 0, len(comments))
for _, comment := range comments {
if comment.ReviewID != 0 {
ids = append(ids, comment.ReviewID)
}
}
if err := e.In("id", ids).Find(&reviews); err != nil {
return nil, err
}
n := 0
for _, comment := range comments {
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except if the review is set)
if review.ID == 0 && re.Type == ReviewTypePending &&
(currentUser == nil || currentUser.ID != re.ReviewerID) {
continue
}
comment.Review = re
}
comments[n] = comment
n++
if err := comment.LoadResolveDoer(); err != nil {
return nil, err
}
if err := comment.LoadReactions(issue.Repo); err != nil {
return nil, err
}
var err error
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: issue.Repo.Link(),
Metas: issue.Repo.ComposeMetas(),
}, comment.Content); err != nil {
return nil, err
}
}
return comments[:n], nil
}
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
TreePath: treePath,
Line: line,
}
return findCodeComments(ctx, opts, issue, currentUser, nil)
}
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
_, err := db.GetEngine(db.DefaultContext).Table("comment"). _, err := db.GetEngine(db.DefaultContext).Table("comment").
@@ -1420,8 +1549,3 @@ func FixCommentTypeLabelWithOutsideLabels() (int64, error) {
return res.RowsAffected() return res.RowsAffected()
} }
// HasOriginalAuthor returns if a comment was migrated and has an original author.
func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
}

View File

@@ -1,129 +0,0 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package issues
import (
"context"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"xorm.io/builder"
)
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map[string]map[int64][]*Comment
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
}
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (CodeComments, error) {
pathToLineToComment := make(CodeComments)
if review == nil {
review = &Review{ID: 0}
}
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
ReviewID: review.ID,
}
comments, err := findCodeComments(ctx, opts, issue, currentUser, review)
if err != nil {
return nil, err
}
for _, comment := range comments {
if pathToLineToComment[comment.TreePath] == nil {
pathToLineToComment[comment.TreePath] = make(map[int64][]*Comment)
}
pathToLineToComment[comment.TreePath][comment.Line] = append(pathToLineToComment[comment.TreePath][comment.Line], comment)
}
return pathToLineToComment, nil
}
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review) ([]*Comment, error) {
var comments []*Comment
if review == nil {
review = &Review{ID: 0}
}
conds := opts.ToConds()
if review.ID == 0 {
conds = conds.And(builder.Eq{"invalidated": false})
}
e := db.GetEngine(ctx)
if err := e.Where(conds).
Asc("comment.created_unix").
Asc("comment.id").
Find(&comments); err != nil {
return nil, err
}
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
if err := CommentList(comments).loadPosters(ctx); err != nil {
return nil, err
}
// Find all reviews by ReviewID
reviews := make(map[int64]*Review)
ids := make([]int64, 0, len(comments))
for _, comment := range comments {
if comment.ReviewID != 0 {
ids = append(ids, comment.ReviewID)
}
}
if err := e.In("id", ids).Find(&reviews); err != nil {
return nil, err
}
n := 0
for _, comment := range comments {
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except if the review is set)
if review.ID == 0 && re.Type == ReviewTypePending &&
(currentUser == nil || currentUser.ID != re.ReviewerID) {
continue
}
comment.Review = re
}
comments[n] = comment
n++
if err := comment.LoadResolveDoer(); err != nil {
return nil, err
}
if err := comment.LoadReactions(issue.Repo); err != nil {
return nil, err
}
var err error
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: issue.Repo.Link(),
Metas: issue.Repo.ComposeMetas(),
}, comment.Content); err != nil {
return nil, err
}
}
return comments[:n], nil
}
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
TreePath: treePath,
Line: line,
}
return findCodeComments(ctx, opts, issue, currentUser, nil)
}

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
} }
@@ -2466,8 +2471,3 @@ func DeleteOrphanedIssues() error {
} }
return nil return nil
} }
// HasOriginalAuthor returns if an issue was migrated and has an original author.
func (issue *Issue) HasOriginalAuthor() bool {
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0
}

View File

@@ -9,7 +9,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@@ -134,27 +133,6 @@ const (
PullRequestStatusAncestor PullRequestStatusAncestor
) )
func (status PullRequestStatus) String() string {
switch status {
case PullRequestStatusConflict:
return "CONFLICT"
case PullRequestStatusChecking:
return "CHECKING"
case PullRequestStatusMergeable:
return "MERGEABLE"
case PullRequestStatusManuallyMerged:
return "MANUALLY_MERGED"
case PullRequestStatusError:
return "ERROR"
case PullRequestStatusEmpty:
return "EMPTY"
case PullRequestStatusAncestor:
return "ANCESTOR"
default:
return strconv.Itoa(int(status))
}
}
// PullRequestFlow the flow of pull request // PullRequestFlow the flow of pull request
type PullRequestFlow int type PullRequestFlow int
@@ -226,42 +204,6 @@ func DeletePullsByBaseRepoID(ctx context.Context, repoID int64) error {
return err return err
} }
// ColorFormat writes a colored string to identify this struct
func (pr *PullRequest) ColorFormat(s fmt.State) {
if pr == nil {
log.ColorFprintf(s, "PR[%d]%s#%d[%s...%s:%s]",
log.NewColoredIDValue(0),
log.NewColoredValue("<nil>/<nil>"),
log.NewColoredIDValue(0),
log.NewColoredValue("<nil>"),
log.NewColoredValue("<nil>/<nil>"),
log.NewColoredValue("<nil>"),
)
return
}
log.ColorFprintf(s, "PR[%d]", log.NewColoredIDValue(pr.ID))
if pr.BaseRepo != nil {
log.ColorFprintf(s, "%s#%d[%s...", log.NewColoredValue(pr.BaseRepo.FullName()),
log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch))
} else {
log.ColorFprintf(s, "Repo[%d]#%d[%s...", log.NewColoredIDValue(pr.BaseRepoID),
log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch))
}
if pr.HeadRepoID == pr.BaseRepoID {
log.ColorFprintf(s, "%s]", log.NewColoredValue(pr.HeadBranch))
} else if pr.HeadRepo != nil {
log.ColorFprintf(s, "%s:%s]", log.NewColoredValue(pr.HeadRepo.FullName()), log.NewColoredValue(pr.HeadBranch))
} else {
log.ColorFprintf(s, "Repo[%d]:%s]", log.NewColoredIDValue(pr.HeadRepoID), log.NewColoredValue(pr.HeadBranch))
}
}
// String represents the pr as a simple string
func (pr *PullRequest) String() string {
return log.ColorFormatAsString(pr)
}
// MustHeadUserName returns the HeadRepo's username if failed return blank // MustHeadUserName returns the HeadRepo's username if failed return blank
func (pr *PullRequest) MustHeadUserName() string { func (pr *PullRequest) MustHeadUserName() string {
if err := pr.LoadHeadRepo(); err != nil { if err := pr.LoadHeadRepo(); err != nil {
@@ -313,7 +255,7 @@ func (pr *PullRequest) LoadHeadRepoCtx(ctx context.Context) (err error) {
pr.HeadRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.HeadRepoID) pr.HeadRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.HeadRepoID)
if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work
return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err) return fmt.Errorf("getRepositoryByID(head): %w", err)
} }
pr.isHeadRepoLoaded = true pr.isHeadRepoLoaded = true
} }
@@ -348,7 +290,7 @@ func (pr *PullRequest) LoadBaseRepoCtx(ctx context.Context) (err error) {
pr.BaseRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.BaseRepoID) pr.BaseRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.BaseRepoID)
if err != nil { if err != nil {
return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err) return fmt.Errorf("repo_model.GetRepositoryByID(base): %w", err)
} }
return nil return nil
} }

View File

@@ -13,6 +13,7 @@ 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/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"xorm.io/xorm" "xorm.io/xorm"
@@ -161,7 +162,7 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
} }
// Load issues. // Load issues.
issueIDs := prs.GetIssueIDs() issueIDs := prs.getIssueIDs()
issues := make([]*Issue, 0, len(issueIDs)) issues := make([]*Issue, 0, len(issueIDs))
if err := db.GetEngine(ctx). if err := db.GetEngine(ctx).
Where("id > 0"). Where("id > 0").
@@ -180,8 +181,7 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
return nil return nil
} }
// GetIssueIDs returns all issue ids func (prs PullRequestList) getIssueIDs() []int64 {
func (prs PullRequestList) GetIssueIDs() []int64 {
issueIDs := make([]int64, 0, len(prs)) issueIDs := make([]int64, 0, len(prs))
for i := range prs { for i := range prs {
issueIDs = append(issueIDs, prs[i].IssueID) issueIDs = append(issueIDs, prs[i].IssueID)
@@ -193,3 +193,24 @@ func (prs PullRequestList) GetIssueIDs() []int64 {
func (prs PullRequestList) LoadAttributes() error { func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(db.DefaultContext) return prs.loadAttributes(db.DefaultContext)
} }
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
func (prs PullRequestList) InvalidateCodeComments(ctx context.Context, doer *user_model.User, repo *git.Repository, branch string) error {
if len(prs) == 0 {
return nil
}
issueIDs := prs.getIssueIDs()
var codeComments []*Comment
if err := db.GetEngine(ctx).
Where("type = ? and invalidated = ?", CommentTypeCode, false).
In("issue_id", issueIDs).
Find(&codeComments); err != nil {
return fmt.Errorf("find code comments: %w", err)
}
for _, comment := range codeComments {
if err := comment.CheckInvalidation(repo, doer, branch); err != nil {
return err
}
}
return nil
}

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()
@@ -978,7 +970,7 @@ func DeleteReview(r *Review) error {
ReviewID: r.ID, ReviewID: r.ID,
} }
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil { if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
return err return err
} }
@@ -988,7 +980,7 @@ func DeleteReview(r *Review) error {
ReviewID: r.ID, ReviewID: r.ID,
} }
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil { if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
return err return err
} }
@@ -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()
} }
@@ -1012,7 +998,7 @@ func (r *Review) GetCodeCommentsCount() int {
IssueID: r.IssueID, IssueID: r.IssueID,
ReviewID: r.ID, ReviewID: r.ID,
} }
conds := opts.ToConds() conds := opts.toConds()
if r.ID == 0 { if r.ID == 0 {
conds = conds.And(builder.Eq{"invalidated": false}) conds = conds.And(builder.Eq{"invalidated": false})
} }
@@ -1032,7 +1018,7 @@ func (r *Review) HTMLURL() string {
ReviewID: r.ID, ReviewID: r.ID,
} }
comment := new(Comment) comment := new(Comment)
has, err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Get(comment) has, err := db.GetEngine(db.DefaultContext).Where(opts.toConds()).Get(comment)
if err != nil || !has { if err != nil || !has {
return "" return ""
} }

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

@@ -396,14 +396,13 @@ func (org *Organization) GetOrgUserMaxAuthorizeLevel(uid int64) (perm.AccessMode
} }
// GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization // GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization
func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*user_model.User, error) { func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) ([]*user_model.User, error) {
// Use a map, in order to de-duplicate users. users := make([]*user_model.User, 0, 10)
users := make(map[int64]*user_model.User)
return users, db.GetEngine(ctx). return users, db.GetEngine(ctx).
Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). Join("INNER", "`team_user`", "`team_user`.uid=`user`.id").
Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). Join("INNER", "`team`", "`team`.id=`team_user`.team_id").
Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})). Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})).
And("team_user.org_id = ?", orgID).Find(&users) And("team_user.org_id = ?", orgID).Asc("`user`.name").Find(&users)
} }
// SearchOrganizationsOptions options to filter organizations // SearchOrganizationsOptions options to filter organizations
@@ -459,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

@@ -92,12 +92,11 @@ func TestUser_GetTeams(t *testing.T) {
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
teams, err := org.LoadTeams() teams, err := org.LoadTeams()
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, teams, 5) { if assert.Len(t, teams, 4) {
assert.Equal(t, int64(1), teams[0].ID) assert.Equal(t, int64(1), teams[0].ID)
assert.Equal(t, int64(2), teams[1].ID) assert.Equal(t, int64(2), teams[1].ID)
assert.Equal(t, int64(12), teams[2].ID) assert.Equal(t, int64(12), teams[2].ID)
assert.Equal(t, int64(14), teams[3].ID) assert.Equal(t, int64(7), teams[3].ID)
assert.Equal(t, int64(7), teams[4].ID)
} }
} }
@@ -294,7 +293,7 @@ func TestUser_GetUserTeamIDs(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, teamIDs) assert.Equal(t, expected, teamIDs)
} }
testSuccess(2, []int64{1, 2, 14}) testSuccess(2, []int64{1, 2})
testSuccess(4, []int64{2}) testSuccess(4, []int64{2})
testSuccess(unittest.NonexistentID, []int64{}) testSuccess(unittest.NonexistentID, []int64{})
} }
@@ -449,7 +448,7 @@ func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) {
users, err = organization.GetUsersWhoCanCreateOrgRepo(db.DefaultContext, 7) users, err = organization.GetUsersWhoCanCreateOrgRepo(db.DefaultContext, 7)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 1) assert.Len(t, users, 1)
assert.NotNil(t, users[5]) assert.EqualValues(t, 5, users[0].ID)
} }
func TestUser_RemoveOrgRepo(t *testing.T) { func TestUser_RemoveOrgRepo(t *testing.T) {

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

@@ -1,49 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"xorm.io/builder"
)
func SetMustChangePassword(ctx context.Context, all, mustChangePassword bool, include, exclude []string) (int64, error) {
sliceTrimSpaceDropEmpty := func(input []string) []string {
output := make([]string, 0, len(input))
for _, in := range input {
in = strings.ToLower(strings.TrimSpace(in))
if in == "" {
continue
}
output = append(output, in)
}
return output
}
var cond builder.Cond
// Only include the users where something changes to get an accurate count
cond = builder.Neq{"must_change_password": mustChangePassword}
if !all {
include = sliceTrimSpaceDropEmpty(include)
if len(include) == 0 {
return 0, fmt.Errorf("no users to include provided")
}
cond = cond.And(builder.In("lower_name", include))
}
exclude = sliceTrimSpaceDropEmpty(exclude)
if len(exclude) > 0 {
cond = cond.And(builder.NotIn("lower_name", exclude))
}
return db.GetEngine(ctx).Where(cond).MustCols("must_change_password").Update(&User{MustChangePassword: mustChangePassword})
}

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

@@ -7,6 +7,8 @@ package user
import ( import (
"context" "context"
"crypto/sha256"
"crypto/subtle"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
@@ -20,7 +22,6 @@ import (
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@@ -29,6 +30,10 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -43,6 +48,21 @@ const (
UserTypeOrganization UserTypeOrganization
) )
const (
algoBcrypt = "bcrypt"
algoScrypt = "scrypt"
algoArgon2 = "argon2"
algoPbkdf2 = "pbkdf2"
)
// AvailableHashAlgorithms represents the available password hashing algorithms
var AvailableHashAlgorithms = []string{
algoPbkdf2,
algoArgon2,
algoScrypt,
algoBcrypt,
}
const ( const (
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own // EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
EmailNotificationsEnabled = "enabled" EmailNotificationsEnabled = "enabled"
@@ -348,6 +368,42 @@ func (u *User) NewGitSig() *git.Signature {
} }
} }
func hashPassword(passwd, salt, algo string) (string, error) {
var tempPasswd []byte
var saltBytes []byte
// There are two formats for the Salt value:
// * The new format is a (32+)-byte hex-encoded string
// * The old format was a 10-byte binary format
// We have to tolerate both here but Authenticate should
// regenerate the Salt following a successful validation.
if len(salt) == 10 {
saltBytes = []byte(salt)
} else {
var err error
saltBytes, err = hex.DecodeString(salt)
if err != nil {
return "", err
}
}
switch algo {
case algoBcrypt:
tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
return string(tempPasswd), nil
case algoScrypt:
tempPasswd, _ = scrypt.Key([]byte(passwd), saltBytes, 65536, 16, 2, 50)
case algoArgon2:
tempPasswd = argon2.IDKey([]byte(passwd), saltBytes, 2, 65536, 8, 50)
case algoPbkdf2:
fallthrough
default:
tempPasswd = pbkdf2.Key([]byte(passwd), saltBytes, 10000, 50, sha256.New)
}
return fmt.Sprintf("%x", tempPasswd), nil
}
// SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO
// change passwd, salt and passwd_hash_algo fields // change passwd, salt and passwd_hash_algo fields
func (u *User) SetPassword(passwd string) (err error) { func (u *User) SetPassword(passwd string) (err error) {
@@ -361,7 +417,7 @@ func (u *User) SetPassword(passwd string) (err error) {
if u.Salt, err = GetUserSalt(); err != nil { if u.Salt, err = GetUserSalt(); err != nil {
return err return err
} }
if u.Passwd, err = hash.Parse(setting.PasswordHashAlgo).Hash(passwd, u.Salt); err != nil { if u.Passwd, err = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo); err != nil {
return err return err
} }
u.PasswdHashAlgo = setting.PasswordHashAlgo u.PasswdHashAlgo = setting.PasswordHashAlgo
@@ -369,9 +425,20 @@ func (u *User) SetPassword(passwd string) (err error) {
return nil return nil
} }
// ValidatePassword checks if the given password matches the one belonging to the user. // ValidatePassword checks if given password matches the one belongs to the user.
func (u *User) ValidatePassword(passwd string) bool { func (u *User) ValidatePassword(passwd string) bool {
return hash.Parse(u.PasswdHashAlgo).VerifyPassword(passwd, u.Passwd, u.Salt) tempHash, err := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
if err != nil {
return false
}
if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 {
return true
}
if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
return true
}
return false
} }
// IsPasswordSet checks if the password is set or left empty // IsPasswordSet checks if the password is set or left empty
@@ -1160,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

@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/setting" "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"
@@ -163,7 +162,7 @@ func TestEmailNotificationPreferences(t *testing.T) {
func TestHashPasswordDeterministic(t *testing.T) { func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16) b := make([]byte, 16)
u := &user_model.User{} u := &user_model.User{}
algos := hash.RecommendedHashAlgorithms algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"}
for j := 0; j < len(algos); j++ { for j := 0; j < len(algos); j++ {
u.PasswdHashAlgo = algos[j] u.PasswdHashAlgo = algos[j]
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {

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 {

View File

@@ -1,77 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"encoding/hex"
"strings"
"code.gitea.io/gitea/modules/log"
"golang.org/x/crypto/argon2"
)
func init() {
Register("argon2", NewArgon2Hasher)
}
// Argon2Hasher implements PasswordHasher
// and uses the Argon2 key derivation function, hybrant variant
type Argon2Hasher struct {
time uint32
memory uint32
threads uint8
keyLen uint32
}
// HashWithSaltBytes a provided password and salt
func (hasher *Argon2Hasher) HashWithSaltBytes(password string, salt []byte) string {
if hasher == nil {
return ""
}
return hex.EncodeToString(argon2.IDKey([]byte(password), salt, hasher.time, hasher.memory, hasher.threads, hasher.keyLen))
}
// NewArgon2Hasher is a factory method to create an Argon2Hasher
// The provided config should be either empty or of the form:
// "<time>$<memory>$<threads>$<keyLen>", where <x> is the string representation
// of an integer
func NewArgon2Hasher(config string) *Argon2Hasher {
// This default configuration uses the following parameters:
// time=2, memory=64*1024, threads=8, keyLen=50.
// It will make two passes through the memory, using 64MiB in total.
hasher := &Argon2Hasher{
time: 2,
memory: 1 << 16,
threads: 8,
keyLen: 50,
}
if config == "" {
return hasher
}
vals := strings.SplitN(config, "$", 4)
if len(vals) != 4 {
log.Error("invalid argon2 hash spec %s", config)
return nil
}
parsed, err := parseUIntParam(vals[0], "time", "argon2", config, nil)
hasher.time = uint32(parsed)
parsed, err = parseUIntParam(vals[1], "memory", "argon2", config, err)
hasher.memory = uint32(parsed)
parsed, err = parseUIntParam(vals[2], "threads", "argon2", config, err)
hasher.threads = uint8(parsed)
parsed, err = parseUIntParam(vals[3], "keyLen", "argon2", config, err)
hasher.keyLen = uint32(parsed)
if err != nil {
return nil
}
return hasher
}

View File

@@ -1,51 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"golang.org/x/crypto/bcrypt"
)
func init() {
Register("bcrypt", NewBcryptHasher)
}
// BcryptHasher implements PasswordHasher
// and uses the bcrypt password hash function.
type BcryptHasher struct {
cost int
}
// HashWithSaltBytes a provided password and salt
func (hasher *BcryptHasher) HashWithSaltBytes(password string, salt []byte) string {
if hasher == nil {
return ""
}
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), hasher.cost)
return string(hashedPassword)
}
func (hasher *BcryptHasher) VerifyPassword(password, hashedPassword, salt string) bool {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
}
// NewBcryptHasher is a factory method to create an BcryptHasher
// The provided config should be either empty or the string representation of the "<cost>"
// as an integer
func NewBcryptHasher(config string) *BcryptHasher {
hasher := &BcryptHasher{
cost: 10, // cost=10. i.e. 2^10 rounds of key expansion.
}
if config == "" {
return hasher
}
var err error
hasher.cost, err = parseIntParam(config, "cost", "bcrypt", config, nil)
if err != nil {
return nil
}
return hasher
}

View File

@@ -1,28 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"strconv"
"code.gitea.io/gitea/modules/log"
)
func parseIntParam(value, param, algorithmName, config string, previousErr error) (int, error) {
parsed, err := strconv.Atoi(value)
if err != nil {
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
return 0, err
}
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
}
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) {
parsed, err := strconv.ParseUint(value, 10, 64)
if err != nil {
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
return 0, err
}
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
}

View File

@@ -1,147 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"crypto/subtle"
"encoding/hex"
"fmt"
"strings"
"sync/atomic"
"code.gitea.io/gitea/modules/log"
)
// This package takes care of hashing passwords, verifying passwords, defining
// available password algorithms, defining recommended password algorithms and
// choosing the default password algorithm.
// PasswordSaltHasher will hash a provided password with the provided saltBytes
type PasswordSaltHasher interface {
HashWithSaltBytes(password string, saltBytes []byte) string
}
// PasswordHasher will hash a provided password with the salt
type PasswordHasher interface {
Hash(password, salt string) (string, error)
}
// PasswordVerifier will ensure that a providedPassword matches the hashPassword when hashed with the salt
type PasswordVerifier interface {
VerifyPassword(providedPassword, hashedPassword, salt string) bool
}
// PasswordHashAlgorithms are named PasswordSaltHashers with a default verifier and hash function
type PasswordHashAlgorithm struct {
PasswordSaltHasher
Name string
}
// Hash the provided password with the salt and return the hash
func (algorithm *PasswordHashAlgorithm) Hash(password, salt string) (string, error) {
var saltBytes []byte
// There are two formats for the salt value:
// * The new format is a (32+)-byte hex-encoded string
// * The old format was a 10-byte binary format
// We have to tolerate both here.
if len(salt) == 10 {
saltBytes = []byte(salt)
} else {
var err error
saltBytes, err = hex.DecodeString(salt)
if err != nil {
return "", err
}
}
return algorithm.HashWithSaltBytes(password, saltBytes), nil
}
// Verify the provided password matches the hashPassword when hashed with the salt
func (algorithm *PasswordHashAlgorithm) VerifyPassword(providedPassword, hashedPassword, salt string) bool {
// The bcrypt package has its own specialized compare function that takes into
// account the stored password's bcrypt parameters.
if verifier, ok := algorithm.PasswordSaltHasher.(PasswordVerifier); ok {
return verifier.VerifyPassword(providedPassword, hashedPassword, salt)
}
// Compute the hash of the password.
providedPasswordHash, err := algorithm.Hash(providedPassword, salt)
if err != nil {
log.Error("passwordhash: %v.Hash(): %v", algorithm.Name, err)
return false
}
// Compare it against the hashed password in constant-time.
return subtle.ConstantTimeCompare([]byte(hashedPassword), []byte(providedPasswordHash)) == 1
}
var (
lastNonDefaultAlgorithm atomic.Value
availableHasherFactories = map[string]func(string) PasswordSaltHasher{}
)
// Register registers a PasswordSaltHasher with the availableHasherFactories
// This is not thread safe.
func Register[T PasswordSaltHasher](name string, newFn func(config string) T) {
if _, has := availableHasherFactories[name]; has {
panic(fmt.Errorf("duplicate registration of password salt hasher: %s", name))
}
availableHasherFactories[name] = func(config string) PasswordSaltHasher {
n := newFn(config)
return n
}
}
// In early versions of gitea the password hash algorithm field could be empty
// At that point the default was `pbkdf2` without configuration values
// Please note this is not the same as the DefaultAlgorithm
const defaultEmptyHashAlgorithmName = "pbkdf2"
func Parse(algorithm string) *PasswordHashAlgorithm {
if algorithm == "" {
algorithm = defaultEmptyHashAlgorithmName
}
if DefaultHashAlgorithm != nil && algorithm == DefaultHashAlgorithm.Name {
return DefaultHashAlgorithm
}
ptr := lastNonDefaultAlgorithm.Load()
if ptr != nil {
hashAlgorithm, ok := ptr.(*PasswordHashAlgorithm)
if ok && hashAlgorithm.Name == algorithm {
return hashAlgorithm
}
}
vals := strings.SplitN(algorithm, "$", 2)
var name string
var config string
if len(vals) == 0 {
return nil
}
name = vals[0]
if len(vals) > 1 {
config = vals[1]
}
newFn, has := availableHasherFactories[name]
if !has {
return nil
}
ph := newFn(config)
if ph == nil {
return nil
}
hashAlgorithm := &PasswordHashAlgorithm{
PasswordSaltHasher: ph,
Name: algorithm,
}
lastNonDefaultAlgorithm.Store(hashAlgorithm)
return hashAlgorithm
}

View File

@@ -1,186 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"encoding/hex"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
type testSaltHasher string
func (t testSaltHasher) HashWithSaltBytes(password string, salt []byte) string {
return password + "$" + string(salt) + "$" + string(t)
}
func Test_registerHasher(t *testing.T) {
Register("Test_registerHasher", func(config string) testSaltHasher {
return testSaltHasher(config)
})
assert.Panics(t, func() {
Register("Test_registerHasher", func(config string) testSaltHasher {
return testSaltHasher(config)
})
})
assert.Equal(t, "password$salt$",
Parse("Test_registerHasher").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
assert.Equal(t, "password$salt$config",
Parse("Test_registerHasher$config").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
delete(availableHasherFactories, "Test_registerHasher")
}
func TestParse(t *testing.T) {
hashAlgorithmsToTest := []string{}
for plainHashAlgorithmNames := range availableHasherFactories {
hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
}
for _, aliased := range aliasAlgorithmNames {
if strings.Contains(aliased, "$") {
hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
}
}
for _, algorithmName := range hashAlgorithmsToTest {
t.Run(algorithmName, func(t *testing.T) {
algo := Parse(algorithmName)
assert.NotNil(t, algo, "Algorithm %s resulted in an empty algorithm", algorithmName)
})
}
}
func TestHashing(t *testing.T) {
hashAlgorithmsToTest := []string{}
for plainHashAlgorithmNames := range availableHasherFactories {
hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
}
for _, aliased := range aliasAlgorithmNames {
if strings.Contains(aliased, "$") {
hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
}
}
runTests := func(password, salt string, shouldPass bool) {
for _, algorithmName := range hashAlgorithmsToTest {
t.Run(algorithmName, func(t *testing.T) {
output, err := Parse(algorithmName).Hash(password, salt)
if shouldPass {
assert.NoError(t, err)
assert.NotEmpty(t, output, "output for %s was empty", algorithmName)
} else {
assert.Error(t, err)
}
assert.Equal(t, Parse(algorithmName).VerifyPassword(password, output, salt), shouldPass)
})
}
}
// Test with new salt format.
runTests(strings.Repeat("a", 16), hex.EncodeToString([]byte{0x01, 0x02, 0x03}), true)
// Test with legacy salt format.
runTests(strings.Repeat("a", 16), strings.Repeat("b", 10), true)
// Test with invalid salt.
runTests(strings.Repeat("a", 16), "a", false)
}
// vectors were generated using the current codebase.
var vectors = []struct {
algorithms []string
password string
salt string
output string
shouldfail bool
}{
{
algorithms: []string{"bcrypt", "bcrypt$10"},
password: "abcdef",
salt: strings.Repeat("a", 10),
output: "$2a$10$fjtm8BsQ2crym01/piJroenO3oSVUBhSLKaGdTYJ4tG0ePVCrU0G2",
shouldfail: false,
},
{
algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
password: "abcdef",
salt: strings.Repeat("a", 10),
output: "3b571d0c07c62d42b7bad3dbf18fb0cd67d4d8cd4ad4c6928e1090e5b2a4a84437c6fd2627d897c0e7e65025ca62b67a0002",
shouldfail: false,
},
{
algorithms: []string{"argon2", "argon2$2$65536$8$50"},
password: "abcdef",
salt: strings.Repeat("a", 10),
output: "551f089f570f989975b6f7c6a8ff3cf89bc486dd7bbe87ed4d80ad4362f8ee599ec8dda78dac196301b98456402bcda775dc",
shouldfail: false,
},
{
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
password: "abcdef",
salt: strings.Repeat("a", 10),
output: "ab48d5471b7e6ed42d10001db88c852ff7303c788e49da5c3c7b63d5adf96360303724b74b679223a3dea8a242d10abb1913",
shouldfail: false,
},
{
algorithms: []string{"bcrypt", "bcrypt$10"},
password: "abcdef",
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
output: "$2a$10$qhgm32w9ZpqLygugWJsLjey8xRGcaq9iXAfmCeNBXxddgyoaOC3Gq",
shouldfail: false,
},
{
algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
password: "abcdef",
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
output: "25fe5f66b43fa4eb7b6717905317cd2223cf841092dc8e0a1e8c75720ad4846cb5d9387303e14bc3c69faa3b1c51ef4b7de1",
shouldfail: false,
},
{
algorithms: []string{"argon2", "argon2$2$65536$8$50"},
password: "abcdef",
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
output: "9c287db63a91d18bb1414b703216da4fc431387c1ae7c8acdb280222f11f0929831055dbfd5126a3b48566692e83ec750d2a",
shouldfail: false,
},
{
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
password: "abcdef",
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
output: "45d6cdc843d65cf0eda7b90ab41435762a282f7df013477a1c5b212ba81dbdca2edf1ecc4b5cb05956bb9e0c37ab29315d78",
shouldfail: false,
},
{
algorithms: []string{"pbkdf2$320000$50"},
password: "abcdef",
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
output: "84e233114499e8721da80e85568e5b7b5900b3e49a30845fcda9d1e1756da4547d70f8740ac2b4a5d82f88cebcd27f21bfe2",
shouldfail: false,
},
{
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
password: "abcdef",
salt: "",
output: "",
shouldfail: true,
},
}
// Ensure that the current code will correctly verify against the test vectors.
func TestVectors(t *testing.T) {
for i, vector := range vectors {
for _, algorithm := range vector.algorithms {
t.Run(strconv.Itoa(i)+": "+algorithm, func(t *testing.T) {
pa := Parse(algorithm)
assert.Equal(t, !vector.shouldfail, pa.VerifyPassword(vector.password, vector.output, vector.salt))
})
}
}
}

View File

@@ -1,62 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"crypto/sha256"
"encoding/hex"
"strings"
"code.gitea.io/gitea/modules/log"
"golang.org/x/crypto/pbkdf2"
)
func init() {
Register("pbkdf2", NewPBKDF2Hasher)
}
// PBKDF2Hasher implements PasswordHasher
// and uses the PBKDF2 key derivation function.
type PBKDF2Hasher struct {
iter, keyLen int
}
// HashWithSaltBytes a provided password and salt
func (hasher *PBKDF2Hasher) HashWithSaltBytes(password string, salt []byte) string {
if hasher == nil {
return ""
}
return hex.EncodeToString(pbkdf2.Key([]byte(password), salt, hasher.iter, hasher.keyLen, sha256.New))
}
// NewPBKDF2Hasher is a factory method to create an PBKDF2Hasher
// config should be either empty or of the form:
// "<iter>$<keyLen>", where <x> is the string representation
// of an integer
func NewPBKDF2Hasher(config string) *PBKDF2Hasher {
hasher := &PBKDF2Hasher{
iter: 10_000,
keyLen: 50,
}
if config == "" {
return hasher
}
vals := strings.SplitN(config, "$", 2)
if len(vals) != 2 {
log.Error("invalid pbkdf2 hash spec %s", config)
return nil
}
var err error
hasher.iter, err = parseIntParam(vals[0], "iter", "pbkdf2", config, nil)
hasher.keyLen, err = parseIntParam(vals[1], "keyLen", "pbkdf2", config, err)
if err != nil {
return nil
}
return hasher
}

View File

@@ -1,64 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"encoding/hex"
"strings"
"code.gitea.io/gitea/modules/log"
"golang.org/x/crypto/scrypt"
)
func init() {
Register("scrypt", NewScryptHasher)
}
// ScryptHasher implements PasswordHasher
// and uses the scrypt key derivation function.
type ScryptHasher struct {
n, r, p, keyLen int
}
// HashWithSaltBytes a provided password and salt
func (hasher *ScryptHasher) HashWithSaltBytes(password string, salt []byte) string {
if hasher == nil {
return ""
}
hashedPassword, _ := scrypt.Key([]byte(password), salt, hasher.n, hasher.r, hasher.p, hasher.keyLen)
return hex.EncodeToString(hashedPassword)
}
// NewScryptHasher is a factory method to create an ScryptHasher
// The provided config should be either empty or of the form:
// "<n>$<r>$<p>$<keyLen>", where <x> is the string representation
// of an integer
func NewScryptHasher(config string) *ScryptHasher {
hasher := &ScryptHasher{
n: 1 << 16,
r: 16,
p: 2, // 2 passes through memory - this default config will use 128MiB in total.
keyLen: 50,
}
if config == "" {
return hasher
}
vals := strings.SplitN(config, "$", 4)
if len(vals) != 4 {
log.Error("invalid scrypt hash spec %s", config)
return nil
}
var err error
hasher.n, err = parseIntParam(vals[0], "n", "scrypt", config, nil)
hasher.r, err = parseIntParam(vals[1], "r", "scrypt", config, err)
hasher.p, err = parseIntParam(vals[2], "p", "scrypt", config, err)
hasher.keyLen, err = parseIntParam(vals[3], "keyLen", "scrypt", config, err)
if err != nil {
return nil
}
return hasher
}

View File

@@ -1,44 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
const DefaultHashAlgorithmName = "pbkdf2"
var DefaultHashAlgorithm *PasswordHashAlgorithm
var aliasAlgorithmNames = map[string]string{
"argon2": "argon2$2$65536$8$50",
"bcrypt": "bcrypt$10",
"scrypt": "scrypt$65536$16$2$50",
"pbkdf2": "pbkdf2_v2", // pbkdf2 should default to pbkdf2_v2
"pbkdf2_v1": "pbkdf2$10000$50",
// The latest PBKDF2 password algorithm is used as the default since it doesn't
// use a lot of memory and is safer to use on less powerful devices.
"pbkdf2_v2": "pbkdf2$50000$50",
// The pbkdf2_hi password algorithm is offered as a stronger alternative to the
// slightly improved pbkdf2_v2 algorithm
"pbkdf2_hi": "pbkdf2$320000$50",
}
var RecommendedHashAlgorithms = []string{
"pbkdf2",
"argon2",
"bcrypt",
"scrypt",
"pbkdf2_hi",
}
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
if algorithmName == "" {
algorithmName = DefaultHashAlgorithmName
}
alias, has := aliasAlgorithmNames[algorithmName]
for has {
algorithmName = alias
alias, has = aliasAlgorithmNames[algorithmName]
}
DefaultHashAlgorithm = Parse(algorithmName)
return algorithmName, DefaultHashAlgorithm
}

View File

@@ -1,38 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package hash
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCheckSettingPasswordHashAlgorithm(t *testing.T) {
t.Run("pbkdf2 is pbkdf2_v2", func(t *testing.T) {
pbkdf2v2Config, pbkdf2v2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2_v2")
pbkdf2Config, pbkdf2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2")
assert.Equal(t, pbkdf2v2Config, pbkdf2Config)
assert.Equal(t, pbkdf2v2Algo.Name, pbkdf2Algo.Name)
})
for a, b := range aliasAlgorithmNames {
t.Run(a+"="+b, func(t *testing.T) {
aConfig, aAlgo := SetDefaultPasswordHashAlgorithm(a)
bConfig, bAlgo := SetDefaultPasswordHashAlgorithm(b)
assert.Equal(t, bConfig, aConfig)
assert.Equal(t, aAlgo.Name, bAlgo.Name)
})
}
t.Run("pbkdf2_v2 is the default when default password hash algorithm is empty", func(t *testing.T) {
emptyConfig, emptyAlgo := SetDefaultPasswordHashAlgorithm("")
pbkdf2v2Config, pbkdf2v2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2_v2")
assert.Equal(t, pbkdf2v2Config, emptyConfig)
assert.Equal(t, pbkdf2v2Algo.Name, emptyAlgo.Name)
})
}

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,31 +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. HTML line breaks are not inserted after every newline by this method.
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
}
}
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

@@ -7,6 +7,7 @@ package charset
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"sort"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@@ -20,16 +21,12 @@ import (
var defaultWordRegexp = regexp.MustCompile(`(-?\d*\.\d\w*)|([^\` + "`" + `\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s\x00-\x1f]+)`) var defaultWordRegexp = regexp.MustCompile(`(-?\d*\.\d\w*)|([^\` + "`" + `\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s\x00-\x1f]+)`)
func NewEscapeStreamer(locale translation.Locale, next HTMLStreamer, allowed ...rune) HTMLStreamer { func NewEscapeStreamer(locale translation.Locale, next HTMLStreamer, allowed ...rune) HTMLStreamer {
allowedM := make(map[rune]bool, len(allowed))
for _, v := range allowed {
allowedM[v] = true
}
return &escapeStreamer{ return &escapeStreamer{
escaped: &EscapeStatus{}, escaped: &EscapeStatus{},
PassthroughHTMLStreamer: *NewPassthroughStreamer(next), PassthroughHTMLStreamer: *NewPassthroughStreamer(next),
locale: locale, locale: locale,
ambiguousTables: AmbiguousTablesForLocale(locale), ambiguousTables: AmbiguousTablesForLocale(locale),
allowed: allowedM, allowed: allowed,
} }
} }
@@ -38,7 +35,7 @@ type escapeStreamer struct {
escaped *EscapeStatus escaped *EscapeStatus
locale translation.Locale locale translation.Locale
ambiguousTables []*AmbiguousTable ambiguousTables []*AmbiguousTable
allowed map[rune]bool allowed []rune
} }
func (e *escapeStreamer) EscapeStatus() *EscapeStatus { func (e *escapeStreamer) EscapeStatus() *EscapeStatus {
@@ -260,7 +257,7 @@ func (e *escapeStreamer) runeTypes(runes ...rune) (types []runeType, confusables
runeCounts.numBrokenRunes++ runeCounts.numBrokenRunes++
case r == ' ' || r == '\t' || r == '\n': case r == ' ' || r == '\t' || r == '\n':
runeCounts.numBasicRunes++ runeCounts.numBasicRunes++
case e.allowed[r]: case e.isAllowed(r):
if r > 0x7e || r < 0x20 { if r > 0x7e || r < 0x20 {
types[i] = nonBasicASCIIRuneType types[i] = nonBasicASCIIRuneType
runeCounts.numNonConfusingNonBasicRunes++ runeCounts.numNonConfusingNonBasicRunes++
@@ -286,3 +283,16 @@ func (e *escapeStreamer) runeTypes(runes ...rune) (types []runeType, confusables
} }
return types, confusables, runeCounts return types, confusables, runeCounts
} }
func (e *escapeStreamer) isAllowed(r rune) bool {
if len(e.allowed) == 0 {
return false
}
if len(e.allowed) == 1 {
return e.allowed[0] == r
}
return sort.Search(len(e.allowed), func(i int) bool {
return e.allowed[i] >= r
}) >= 0
}

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

@@ -24,12 +24,12 @@ type BlamePart struct {
// BlameReader returns part of file blame one by one // BlameReader returns part of file blame one by one
type BlameReader struct { type BlameReader struct {
cmd *exec.Cmd cmd *exec.Cmd
reader io.ReadCloser output io.ReadCloser
lastSha *string reader *bufio.Reader
cancel context.CancelFunc // Cancels the context that this reader runs in lastSha *string
finished process.FinishedFunc // Tells the process manager we're finished and it can remove the associated process from the process table cancel context.CancelFunc // Cancels the context that this reader runs in
bufferedReader *bufio.Reader finished process.FinishedFunc // Tells the process manager we're finished and it can remove the associated process from the process table
} }
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})") var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
@@ -38,6 +38,8 @@ var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
func (r *BlameReader) NextPart() (*BlamePart, error) { func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart var blamePart *BlamePart
reader := r.reader
if r.lastSha != nil { if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0)} blamePart = &BlamePart{*r.lastSha, make([]string, 0)}
} }
@@ -47,7 +49,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
var err error var err error
for err != io.EOF { for err != io.EOF {
line, isPrefix, err = r.bufferedReader.ReadLine() line, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return blamePart, err return blamePart, err
} }
@@ -69,7 +71,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
r.lastSha = &sha1 r.lastSha = &sha1
// need to munch to end of line... // need to munch to end of line...
for isPrefix { for isPrefix {
_, isPrefix, err = r.bufferedReader.ReadLine() _, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return blamePart, err return blamePart, err
} }
@@ -84,7 +86,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// need to munch to end of line... // need to munch to end of line...
for isPrefix { for isPrefix {
_, isPrefix, err = r.bufferedReader.ReadLine() _, isPrefix, err = reader.ReadLine()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return blamePart, err return blamePart, err
} }
@@ -100,9 +102,9 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
func (r *BlameReader) Close() error { func (r *BlameReader) Close() error {
defer r.finished() // Only remove the process from the process table when the underlying command is closed defer r.finished() // Only remove the process from the process table when the underlying command is closed
r.cancel() // However, first cancel our own context early r.cancel() // However, first cancel our own context early
r.bufferedReader = nil
_ = r.reader.Close() _ = r.output.Close()
if err := r.cmd.Wait(); err != nil { if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %w", err) return fmt.Errorf("Wait: %w", err)
} }
@@ -124,27 +126,25 @@ func createBlameReader(ctx context.Context, dir string, command ...string) (*Bla
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
process.SetSysProcAttribute(cmd) process.SetSysProcAttribute(cmd)
reader, stdout, err := os.Pipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
defer finished() defer finished()
return nil, fmt.Errorf("StdoutPipe: %w", err) return nil, fmt.Errorf("StdoutPipe: %w", err)
} }
cmd.Stdout = stdout
if err = cmd.Start(); err != nil { if err = cmd.Start(); err != nil {
defer finished() defer finished()
_ = stdout.Close() _ = stdout.Close()
return nil, fmt.Errorf("Start: %w", err) return nil, fmt.Errorf("Start: %w", err)
} }
_ = stdout.Close()
bufferedReader := bufio.NewReader(reader) reader := bufio.NewReader(stdout)
return &BlameReader{ return &BlameReader{
cmd: cmd, cmd: cmd,
reader: reader, output: stdout,
cancel: cancel, reader: reader,
finished: finished, cancel: cancel,
bufferedReader: bufferedReader, finished: finished,
}, nil }, nil
} }

View File

@@ -65,7 +65,7 @@ summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go filename gogs.go
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
` + `
e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2 e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2
author Lunny Xiao author Lunny Xiao
author-mail <xiaolunwen@gmail.com> author-mail <xiaolunwen@gmail.com>
@@ -112,7 +112,9 @@ func TestReadingBlameOutput(t *testing.T) {
}, },
{ {
"ce21ed6c3490cdfad797319cbb1145e2330a8fef", "ce21ed6c3490cdfad797319cbb1145e2330a8fef",
[]string{"// Copyright 2016 The Gitea Authors. All rights reserved."}, []string{
"// Copyright 2016 The Gitea Authors. All rights reserved.",
},
}, },
{ {
"4b92a6c2df28054ad766bc262f308db9f6066596", "4b92a6c2df28054ad766bc262f308db9f6066596",

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

@@ -164,8 +164,10 @@ func CloneWithArgs(ctx context.Context, args []CmdArg, from, to string, opts Clo
envs := os.Environ() envs := os.Environ()
u, err := url.Parse(from) u, err := url.Parse(from)
if err == nil { if err == nil && (strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https")) {
envs = proxy.EnvWithProxy(u) if proxy.Match(u.Host) {
envs = append(envs, fmt.Sprintf("https_proxy=%s", proxy.GetProxyURL()))
}
} }
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)

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) {

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