Merge branch 'main' of github.com:go-gitea/gitea into api-repo-actions

This commit is contained in:
chesterip 2024-04-18 22:20:40 -04:00
commit 0aabe97853
365 changed files with 6448 additions and 9683 deletions

View File

@ -2,9 +2,10 @@ root = "."
tmp_dir = ".air"
[build]
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
cmd = "make --no-print-directory backend"
bin = "gitea"
delay = 1000
delay = 2000
include_ext = ["go", "tmpl"]
include_file = ["main.go"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]

View File

@ -95,6 +95,9 @@ cpu.out
/.air
/.go-licenses
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft
snap/.snapcraft/
parts/

View File

@ -318,7 +318,7 @@ rules:
jquery/no-serialize: [2]
jquery/no-show: [2]
jquery/no-size: [2]
jquery/no-sizzle: [0]
jquery/no-sizzle: [2]
jquery/no-slide: [0]
jquery/no-submit: [0]
jquery/no-text: [0]
@ -470,7 +470,7 @@ rules:
no-jquery/no-selector-prop: [2]
no-jquery/no-serialize: [2]
no-jquery/no-size: [2]
no-jquery/no-sizzle: [0]
no-jquery/no-sizzle: [2]
no-jquery/no-slide: [2]
no-jquery/no-sub: [2]
no-jquery/no-support: [2]
@ -537,7 +537,7 @@ rules:
no-underscore-dangle: [0]
no-unexpected-multiline: [2]
no-unmodified-loop-condition: [2]
no-unneeded-ternary: [0]
no-unneeded-ternary: [2]
no-unreachable-loop: [2]
no-unreachable: [2]
no-unsafe-finally: [2]
@ -716,12 +716,14 @@ rules:
unicorn/import-style: [0]
unicorn/new-for-builtins: [2]
unicorn/no-abusive-eslint-disable: [0]
unicorn/no-anonymous-default-export: [0]
unicorn/no-array-callback-reference: [0]
unicorn/no-array-for-each: [2]
unicorn/no-array-method-this-argument: [2]
unicorn/no-array-push-push: [2]
unicorn/no-array-reduce: [2]
unicorn/no-await-expression-member: [0]
unicorn/no-await-in-promise-methods: [2]
unicorn/no-console-spaces: [0]
unicorn/no-document-cookie: [2]
unicorn/no-empty-file: [2]
@ -738,6 +740,7 @@ rules:
unicorn/no-null: [0]
unicorn/no-object-as-default-parameter: [0]
unicorn/no-process-exit: [0]
unicorn/no-single-promise-in-promise-methods: [2]
unicorn/no-static-only-class: [2]
unicorn/no-thenable: [2]
unicorn/no-this-assignment: [2]

1
.gitattributes vendored
View File

@ -1,5 +1,6 @@
* text=auto eol=lf
*.tmpl linguist-language=Handlebars
*.pb.go linguist-generated
/assets/*.json linguist-generated
/public/assets/img/svg/*.svg linguist-generated
/templates/swagger/v1_json.tmpl linguist-generated

3
.gitignore vendored
View File

@ -94,6 +94,9 @@ cpu.out
/.air
/.go-licenses
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft
/gitea_a*.txt
snap/.snapcraft/

View File

@ -30,10 +30,6 @@ linters:
run:
timeout: 10m
skip-dirs:
- node_modules
- public
- web_src
linters-settings:
stylecheck:
@ -90,10 +86,13 @@ linters-settings:
desc: do not use the internal package, use AddXxx function instead
- pkg: gopkg.in/ini.v1
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system
issues:
max-issues-per-linter: 0
max-same-issues: 0
exclude-dirs: [node_modules, public, web_src]
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go

View File

@ -159,11 +159,11 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix the issue ref rendering for wiki (#28556) (#28559)
* Fix duplicate ID when deleting repo (#28520) (#28528)
* Only check online runner when detecting matching runners in workflows (#28286) (#28512)
* Initalize stroage for orphaned repository doctor (#28487) (#28490)
* Initialize stroage for orphaned repository doctor (#28487) (#28490)
* Fix possible nil pointer access (#28428) (#28440)
* Don't show unnecessary citation JS error on UI (#28433) (#28437)
* DOCS
* Update actions document about comparsion as Github Actions (#28560) (#28564)
* Update actions document about comparison as Github Actions (#28560) (#28564)
* Fix documents for "custom/public/assets/" (#28465) (#28467)
* MISC
* Fix inperformant query on retrifing review from database. (#28552) (#28562)
@ -673,7 +673,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Docs: template variables (#26547)
* Update index doc (#26455)
* Update zh-cn documentation (#26406)
* Fix typos and grammer problems for actions documentation (#26328)
* Fix typos and grammar problems for actions documentation (#26328)
* Update documentation for 1.21 actions (#26317)
* Doc update swagger doc for POST /orgs/{org}/teams (#26155)
* Doc sync authentication.md to zh-cn (#26117)
@ -762,7 +762,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix incorrect color of selected assignees when create issue (#26324) (#26372)
* Display human-readable text instead of cryptic filemodes (#26352) (#26358)
* Hide `last indexed SHA` when a repo could not be indexed yet (#26340) (#26345)
* Fix the topic validation rule and suport dots (#26286) (#26303)
* Fix the topic validation rule and support dots (#26286) (#26303)
* Fix due date rendering the wrong date in issue (#26268) (#26274)
* Don't autosize textarea in diff view (#26233) (#26244)
* Fix commit compare style (#26209) (#26226)
@ -989,7 +989,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Add dark mode to API Docs (#24971)
* Display file mode for new file and file mode changes (#24966)
* Make the 500 page load themes (#24953)
* Show `bot` label next to username when rendering autor link if the user is a bot (#24943)
* Show `bot` label next to username when rendering author link if the user is a bot (#24943)
* Repo list improvements, fix bold helper classes (#24935)
* Improve queue and logger context (#24924)
* Improve RunMode / dev mode (#24886)
@ -1384,7 +1384,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Replace `drone exec` to `act_runner exec` in test README.md (#24791)
* Update packages overview page (#24730)
* Docs for creating a user to run Gitea on Fedora/RHEL/CentOS (#24725)
* Move actions as usage's subdirectory and update comparsion zh-cn version (#24719)
* Move actions as usage's subdirectory and update comparison zh-cn version (#24719)
* Document `redis-cluster` explicitly in config (#24717)
* Improve reverse-proxy document and fix nginx config bug (#24616)
* Fix broken `README` link (#24546)
@ -1462,7 +1462,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Show visibility status of email in own profile (#23900)
* Refactor authors dropdown (send get request from frontend to avoid long wait time) (#23890)
* Add self to maintainers (#23644)
* Upgrade to npm lockfile v3 and explicitely set it (#23561)
* Upgrade to npm lockfile v3 and explicitly set it (#23561)
* Improve indices for `action` table (#23532)
* Update JS dependencies, Require Node.js 16 (#23528)
* Add init file for Ubuntu (#23362)
@ -1503,7 +1503,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix issues list page multiple selection update milestones (#24660) (#24663)
* Fix: release page for empty or non-existing target (#24659)
* Fix close org projects (#24588) (#24591)
* Refresh the refernce of the closed PR when reopening (#24231) (#24587)
* Refresh the references of the closed PR when reopening (#24231) (#24587)
* Fix the permission of team's `Actions` unit issue (#24536) (#24545)
* Bump go.etcd.io/bbolt and blevesearch deps (#23062) (#24519)
* Fix new wiki page mirror (#24518)
@ -2644,7 +2644,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Feeds: render markdown to html (#19058)
* Allow users to self-request a PR review (#19030)
* Allow render HTML with css/js external links (#19017)
* Fix script compatiable with OpenWrt (#19000)
* Fix script compatible with OpenWrt (#19000)
* Support ignore all santize for external renderer (#18984)
* Add note to GPG key response if user has no keys (#18961)
* Improve Stopwatch behavior (#18930)
@ -2975,7 +2975,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Update go-org to v1.6.1 (#18932) (#18933)
* Fix `<strong>` html in translation (#18929) (#18931)
* Fix page and missing return on unadopted repos API (#18848) (#18927)
* Allow adminstrator teams members to see other teams (#18918) (#18919)
* Allow administrator teams members to see other teams (#18918) (#18919)
* Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
* Correctly link URLs to users/repos with dashes, dots or underscores (… (#18908)
* Fix redirect when using lowercase repo name (#18775) (#18902)
@ -3323,7 +3323,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fixed issue merged/closed wording (#17973)
* Return nicer error for ForcePrivate (#17971)
* Fix overflow in commit graph (#17947)
* Prevent services/mailer/mailer_test.go tests from deleteing data directory (#17941)
* Prevent services/mailer/mailer_test.go tests from deleting data directory (#17941)
* Use disable_form_autofill on Codebase and Gitbucket (#17936)
* Fix a panic in NotifyCreateIssueComment (caused by string truncation) (#17928)
* Fix markdown URL parsing (#17924)
@ -3362,7 +3362,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fixed emoji alias not parsed in links (#16221)
* Calculate label URL on API (#16186)
* TRANSLATION
* Fix mispelling of starred as stared (#17465)
* Fix misspelling of starred as stared (#17465)
* Re-separate the color translation strings (#17390)
* Enable Malayalam, Greek, Persian, Hungarian & Indonesian by default (#16998)
* BUILD
@ -3554,7 +3554,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Correctly return the number of Repositories for Organizations (#16807) (#16911)
* Test if LFS object is accessible (#16865) (#16904)
* Fix git.Blob.DataAsync(): close pipe since we return a NopCloser (#16899) (#16900)
* Fix dump and restore respository (#16698) (#16898)
* Fix dump and restore repository (#16698) (#16898)
* Repare and Improve GetDiffRangeWithWhitespaceBehavior (#16894) (#16895)
* Fix wiki raw commit diff/patch view (#16891) (#16892)
* Ensure wiki repos are all closed (#16886) (#16888)
@ -4164,7 +4164,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Improve Description in new/ edit Project template (#14429)
* Allow ssh-keygen on Windows to detect ssh key type (#14413)
* Display error if twofaSecret cannot be retrieved (#14372)
* Sort issue search results by revelance (#14353)
* Sort issue search results by relevance (#14353)
* Implement ghost comment mitigation (#14349)
* Upgrade blevesearch dependency to v2.0.1 (#14346)
* Add edit, delete and reaction support to code review comments on issue page (#14339)
@ -4337,7 +4337,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* BUGFIXES
* Fix race in LFS ContentStore.Put(...) (#14895) (#14913)
* Fix a couple of issues with a feeds (#14897) (#14903)
* When transfering repository and database transaction failed, rollback the renames (#14864) (#14902)
* When transferring repository and database transaction failed, rollback the renames (#14864) (#14902)
* Fix race in local storage (#14888) (#14901)
* Fix 500 on pull view page if user is not loged in (#14885) (#14886)
* DOCS
@ -4668,7 +4668,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Render the git graph on the server (#12333)
* Fix clone panel in wiki position not always align right (#12326)
* Rework 'make generate-images' (#12316)
* Refactor webhook payload convertion (#12310)
* Refactor webhook payload conversion (#12310)
* Move jquery-minicolors to npm/webpack (#12305)
* Support use nvarchar for all varchar columns when using mssql (#12269)
* Update Octicons to v10 (#12240)
@ -4744,7 +4744,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Add debug option to hooks (#11624)
* Log slow tests (#11487)
* TRANSLATION
* Translate two small lables on commit statuse list (#12821)
* Translate two small lables on commit statutes list (#12821)
* Make issues.force_push_codes message shorter (#11575)
* BUILD
* Bump min required golang to 1.13 (#12717)
@ -5123,7 +5123,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Add restricted user filter to LDAP authentication (#10600)
* Add Yandex OAuth2 provider (#8335) (#10564)
* Make avatar lookup occur at image request (#10540)
* Prevent accidential selection of language stats bar (#10537)
* Prevent accidental selection of language stats bar (#10537)
* Add fluid-icon (#10491)
* Inform participants on UI too (#10473)
* Build with go 1.14 (and raise minimum go version to 1.12) (#10467)
@ -5515,7 +5515,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Don't link wiki revision to commit (#9244)
* Change review content column to type text in db (#9229)
* Fixed topic regex pattern and added search by topic links after save (#9219)
* Add language to user API responce (#9215)
* Add language to user API response (#9215)
* Correct tooltip message blocked by dependencies (#9211)
* Add SimpleMDE and Fix Image Paste for Issue/Comment Editor (#9197)
* Fix panic when diff (#9187)
@ -6136,7 +6136,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Display original author and URL information when showing migrated issues/comments (#7352)
* Refactor filetype is not allowed errors (#7309)
* switch to use gliderlabs/ssh for builtin server (#7250)
* Remove settting dependency on modules/session (#7237)
* Remove setting dependency on modules/session (#7237)
* Move all mail related codes from models to services/mailer (#7200)
* Support git.PATH entry in app.ini (#6772)
* Support setting cookie domain (#6288)
@ -6311,7 +6311,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Fix markdown invoke sequence (#7513) (#7560)
* Reserve .well-known username (#7638)
* Do not leak secrets via timing side channel (#7364)
* Ensure that decryption of cookie actually suceeds (#7363)
* Ensure that decryption of cookie actually succeeds (#7363)
* FEATURES
* Content API for Creating, Updating, Deleting Files (#6314)
* Enable tls-alpn-01: Use certmanager provided TLSConfig for LetsEncrypt (#7229)
@ -6533,7 +6533,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Change UpdateRepoIndex api to include watchers (#7012)
* Move serv hook functionality & drop GitLogger (#6993)
* Add support of utf8mb4 for mysql (#6992)
* Make webhook http connections resuable (#6976)
* Make webhook http connections reusable (#6976)
* Move xorm logger bridge from log to models so that log module could be a standalone package (#6944)
* Refactor models.NewRepoContext to extract git related codes to modules/git (#6941)
* Remove macaron dependent on models (#6940)
@ -7104,7 +7104,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Fix data race on migrate repository (#5224)
* Fix sqlite and mssql lock (#5214)
* Fix sqlite lock (#5210)
* Fix: Accept web-command cli flags if web-command is commited (#5200)
* Fix: Accept web-command cli flags if web-command is committed (#5200)
* Fix: Add secret to all webhook's payload where it has been missing (#5199)
* Fix race on updatesize (#5190)
* Fix create team, update team missing units (#5188)
@ -7255,7 +7255,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Enforce token on api routes [fixed critical security issue #4357] (#4840)
* Update legacy branch and tag URLs in dashboard to new format (#4812)
* Slack webhook channel name cannot be empty or just contain an hashtag (#4786)
* Add whitespace handling to PR-comparsion (#4683)
* Add whitespace handling to PR-comparison (#4683)
* Make reverse proxy auth optional (#4643)
* MySQL TLS (#4642)
* Make sure to set PR split view when creating/previewing a pull request (#4617)
@ -7309,7 +7309,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Fix markdown image with link (#4675)
* Remove maxlines option for file logger (#5282)
* Fix wrong api request url for instances running in subfolders (#5261) (#5247)
* Accept web-command cli flags if web-command is commited (#5245) (#5200)
* Accept web-command cli flags if web-command is committed (#5245) (#5200)
* Reduce join star, repo_topic, topic tables on repo search, to resolve extra columns problem on MSSQL (#5136) (#5229)
* Fix data race on migrate repository (#5224) (#5230)
* Add secret to all webhook's payload where it has been missing (#5208) (#5199)
@ -7342,7 +7342,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Fix missing release title in webhook (#4783) (#4796)
* User shouldn't be able to approve or reject his/her own PR (#4729)
* Make sure to reset commit count in the cache on mirror syncing (#4720)
* Fixed bug where team with admin privelege type doesn't get any unit (#4719)
* Fixed bug where team with admin privilege type doesn't get any unit (#4719)
* Fix incorrect caption of webhook setting (#4701) (#4717)
* Allow WIP marker to contains < or > (#4709)
* Hide org/create menu item in Dashboard if user has no rights (#4678) (#4680)
@ -7408,7 +7408,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* BUGFIXES
* Fix missing release title in webhook (#4783) (#4800)
* Make sure to reset commit count in the cache on mirror syncing (#4770)
* Fixed bug where team with admin privelege type doesn't get any unit (#4759)
* Fixed bug where team with admin privilege type doesn't get any unit (#4759)
* Fix failure on creating pull request with assignees (#4583) (#4727)
* Hide org/create menu item in Dashboard if user has no rights (#4678) (#4686)
* TRANSLATION
@ -7759,7 +7759,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Fix inconsistencies in user settings UI (#2901)
* Fix attachments icon size on zoom in/out (#2853)
* Fix ignored errors in API route (#2850)
* Fix activity css conflit with semantic ui (#2758)
* Fix activity css conflict with semantic ui (#2758)
* Fix notifications tabs according to semantic-ui docs (#2733)
* Fix typos in app.ini (#2732)
* Fix duplicated rel attribute (#2549)
@ -7957,7 +7957,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* LFS: Return 404 for unimplemented endpoints (#1330)
* Show a link to password reset from user settings requiring a password (#862)
* Reserve the "explore" user/org name (#1222)
* Send notifications to partecipants in issue comments (#1217)
* Send notifications to participants in issue comments (#1217)
* Improve style of user OpenID setting page (#1324)
* Use font-awesome OpenID icon more (#1320)
* Use readonly input form to show the validated OpenID URI (#1308)
@ -8155,7 +8155,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* fix #1501 ssh hangs caused by #1461 (#1513)
* Fix empty file download (#1506)
* Fix broken v27 migration - change mirror interval from int to bigint (#1504)
* Do not allow commiting to protected branch from online editor (#1502)
* Do not allow committing to protected branch from online editor (#1502)
* Add internal routes for ssh hook comands (#1471)
* Fix races within code.gitea.io/git.(*Command).RunInDirTimeoutPipeline (#1465)
* Simple quick fix for #1418 (#1456)
@ -8183,7 +8183,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Make sure both scripts/ can live side by side (#1264)
* Fix nil-dereference bug (#1258)
* rewrite pre-commit, post-commit and options hooks (fixes #1250) (#1257)
* Commit search appearence fixes (#1254)
* Commit search appearance fixes (#1254)
* Fix forget migration for wiki hooks (#1227)
* Fix repo settings external tracker failed and check external urls (#1215)
* Fix 500 caused by branches settings introduced by #1198 (#1214)
@ -8267,7 +8267,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Only run coverage on merges/pushes to master (#1783)
* Remove stale rule from Makefile (#1782)
* feat: upgrade drone docker image to support multi-stage build. (#1732)
* Realy don't cache apk index (#1694)
* Really don't cache apk index (#1694)
* Limit clone depth when drone-building (#1644)
* Refactor Dockerfile (#1632)
* Check if missing/modified/unused deps in vendor and fix errors (#1468)
@ -8331,7 +8331,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Remove unused files (#2124)
* Improve org error handling (#2117)
* Absolute path for setting.CustomConf (#2085)
* remove deprecated code for Gogs compitable (#2041)
* remove deprecated code for Gogs compatible (#2041)
* Refactor session close as xorm already does everything needed internally (#2020)
* SQLite has a query timeout. Hopefully fixes most 'database locked' errors (#1961)
* Use monospace font in githook editor (#1958)
@ -8339,7 +8339,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Gracefully handle bare repositories on API operations. (#1932)
* Fix errors caused by force push (#1927)
* Display URLs in integration test logs (#1924)
* Set TMPDIR enviroment variable for dump command (#1915)
* Set TMPDIR environment variable for dump command (#1915)
* Cache ctx.User in retrieveFeeds (#1902)
* Make `LocalCopyPath` a setting instead of a hard-coded path (#1881)
* Add check misspelling (#1877)
@ -8348,7 +8348,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Rename misnamed migration (#1867)
* Support CRLF when splitting code lines for display (#1862)
* Add convert less css file step. (#1861)
* Prevent accidential selection of line numbers in code view (#1860)
* Prevent accidental selection of line numbers in code view (#1860)
* Delete Public SSH Key tmp file after calculating fingerprint (#1855)
* Remove annoying difference in button heights. (#1853)
* Only run test coverage on master branch. (#1838)
@ -8358,7 +8358,7 @@ WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should __not__ be
* Rename RepoCreationNum -> MaxCreationLimit (#1766)
* Add button to admin ui (#1738)
* Correct spelling mistakes (#1703)
* Make openid support default false for compitable with v1.1 (#1650)
* Make openid support default false for compatible with v1.1 (#1650)
* Send mails as HTML as default. Setting for send as plain text. (#1648)
* fix potential lock when sqlite (#1647)
* Optimize png images via Google zopflipng [ci skip] (#1639)

View File

@ -25,17 +25,17 @@ COMMA := ,
XGO_VERSION := go-1.22.x
AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0
AIR_PACKAGE ?= github.com/cosmtrek/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@db51e79a0e37c572d8b59ae0c58bf2bbbbe53285
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.26
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest
@ -110,7 +110,6 @@ LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(G
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
@ -144,9 +143,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.config.js tests/e2e
ESLINT_FILES := web_src/js tools *.js tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go)
@ -295,7 +294,7 @@ clean:
.PHONY: fmt
fmt:
GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@# whitespace before it
@ -423,7 +422,7 @@ lint-go-windows:
lint-go-vet:
@echo "Running go vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
@$(GO) vet -vettool=gitea-vet ./...
.PHONY: lint-editorconfig
lint-editorconfig:
@ -779,7 +778,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
.PHONY: generate-go
generate-go: $(TAGS_PREREQ)
@echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
.PHONY: security-check
security-check:

View File

@ -304,11 +304,6 @@
"path": "github.com/davecgh/go-spew/spew/LICENSE",
"licenseText": "ISC License\n\nCopyright (c) 2012-2016 Dave Collins \u003cdave@davec.name\u003e\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
},
{
"name": "github.com/denisenkom/go-mssqldb",
"path": "github.com/denisenkom/go-mssqldb/LICENSE.txt",
"licenseText": "Copyright (c) 2012 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/dgryski/go-rendezvous",
"path": "github.com/dgryski/go-rendezvous/LICENSE",
@ -759,6 +754,16 @@
"path": "github.com/microcosm-cc/bluemonday/LICENSE.md",
"licenseText": "SPDX short identifier: BSD-3-Clause\nhttps://opensource.org/licenses/BSD-3-Clause\n\nCopyright (c) 2014, David Kitchen \u003cdavid@buro9.com\u003e\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the organisation (Microcosm) nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/microsoft/go-mssqldb",
"path": "github.com/microsoft/go-mssqldb/LICENSE.txt",
"licenseText": "Copyright (c) 2012 The Go Authors. All rights reserved.\nCopyright (c) Microsoft Corporation.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/microsoft/go-mssqldb/internal/github.com/swisscom/mssql-always-encrypted/pkg",
"path": "github.com/microsoft/go-mssqldb/internal/github.com/swisscom/mssql-always-encrypted/pkg/LICENSE.txt",
"licenseText": "Copyright (c) 2021 Swisscom (Switzerland) Ltd\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n"
},
{
"name": "github.com/miekg/dns",
"path": "github.com/miekg/dns/LICENSE",

View File

@ -69,6 +69,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
@ -203,17 +204,6 @@ Example:
`, "file-batch-exec")
}
func getGoVersion() string {
goModFile, err := os.ReadFile("go.mod")
if err != nil {
log.Fatalf(`Faild to read "go.mod": %v`, err)
os.Exit(1)
}
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
goModVersionLine := goModVersionRegex.Find(goModFile)
return string(goModVersionLine[3:])
}
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
fileFilter := mainOptions["file-filter"]
if fileFilter == "" {
@ -278,7 +268,8 @@ func main() {
log.Print("the -d option is not supported by gitea-fmt")
}
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...)))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...)))
default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
}

View File

@ -36,6 +36,7 @@ var microcmdUserChangePassword = &cli.Command{
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password",
Value: true,
},
},
}
@ -57,23 +58,18 @@ func runChangePassword(c *cli.Context) error {
return err
}
var mustChangePassword optional.Option[bool]
if c.IsSet("must-change-password") {
mustChangePassword = optional.Some(c.Bool("must-change-password"))
}
opts := &user_service.UpdateAuthOptions{
Password: optional.Some(c.String("password")),
MustChangePassword: mustChangePassword,
MustChangePassword: optional.Some(c.Bool("must-change-password")),
}
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
switch {
case errors.Is(err, password.ErrMinLength):
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
case errors.Is(err, password.ErrComplexity):
return errors.New("Password does not meet complexity requirements")
return errors.New("password does not meet complexity requirements")
case errors.Is(err, password.ErrIsPwned):
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")
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords")
default:
return err
}

View File

@ -8,6 +8,7 @@ import (
"fmt"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
@ -46,8 +47,9 @@ var microcmdUserCreate = &cli.Command{
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)",
Name: "must-change-password",
Usage: "Set to false to prevent forcing the user to change their password after initial login",
DisableDefaultText: true,
},
&cli.IntFlag{
Name: "random-password-length",
@ -71,10 +73,10 @@ func runCreateUser(c *cli.Context) error {
}
if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags")
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")
return errors.New("one of --name or --username flags must be set")
}
if c.IsSet("password") && c.IsSet("random-password") {
@ -110,17 +112,21 @@ func runCreateUser(c *cli.Context) error {
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(ctx, nil); n == 0 {
changePassword = false
}
isAdmin := c.Bool("admin")
mustChangePassword := true // always default to true
if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
// if the flag is set, use the value provided by the user
mustChangePassword = c.Bool("must-change-password")
} else {
// check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil {
return fmt.Errorf("IsTableNotEmpty: %w", err)
}
if !hasUserRecord && isAdmin {
// if this is the first admin being created, don't force to change password (keep the old behavior)
mustChangePassword = false
}
}
restricted := optional.None[bool]()
@ -136,8 +142,8 @@ func runCreateUser(c *cli.Context) error {
Name: username,
Email: c.String("email"),
Passwd: password,
IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
IsAdmin: isAdmin,
MustChangePassword: mustChangePassword,
Visibility: visibility,
}

View File

@ -448,23 +448,26 @@ Gitea or set your environment appropriately.`, "")
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
for _, res := range results {
if !res.Message {
continue
}
fmt.Fprintln(os.Stderr, "")
if res.Create {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
hookPrintResult(res.Message, res.Create, res.Branch, res.URL)
}
}
func hookPrintResult(output, isCreate bool, branch, url string) {
if !output {
return
}
fmt.Fprintln(os.Stderr, "")
if isCreate {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
fmt.Fprintf(os.Stderr, " %s\n", url)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", url)
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
}
func pushOptions() map[string]string {
opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
@ -691,6 +694,12 @@ Gitea or set your environment appropriately.`, "")
}
err = writeFlushPktLine(ctx, os.Stdout)
if err == nil {
for _, res := range resp.Results {
hookPrintResult(res.ShouldShowMessage, res.IsCreatePR, res.HeadBranch, res.URL)
}
}
return err
}

View File

@ -114,7 +114,7 @@ func showWebStartupMessage(msg string) {
log.Info("* WorkPath: %s", setting.AppWorkPath)
log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg)
log.Info("%s", msg) // show startup message
}
func serveInstall(ctx *cli.Context) error {

View File

@ -1553,8 +1553,9 @@ LEVEL = Info
;; The source of the username for new oauth2 accounts:
;; userid = use the userid / sub attribute
;; nickname = use the nickname attribute
;; preferred_username = use the preferred_username attribute
;; email = use the username part of the email attribute
;; Note: `nickname` and `email` options will normalize input strings using the following criteria:
;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
;; - diacritics are removed
;; - the characters in the set `['´\x60]` are removed
;; - the characters in the set `[\s~+]` are replaced with `-`
@ -2376,22 +2377,6 @@ LEVEL = Info
;; Enable issue by repository metrics; default is false
;ENABLED_ISSUE_BY_REPOSITORY = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[task]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Task queue type, could be `channel` or `redis`.
;QUEUE_TYPE = channel
;;
;; Task queue length, available only when `QUEUE_TYPE` is `channel`.
;QUEUE_LENGTH = 1000
;;
;; Task queue connection string, available only when `QUEUE_TYPE` is `redis`.
;; If there is a password of redis, use `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for `redis-clsuter`.
;QUEUE_CONN_STR = "redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[migrations]

View File

@ -83,8 +83,7 @@ Admin operations:
- `--email value`: Email. Required.
- `--admin`: If provided, this makes the user an admin. Optional.
- `--access-token`: If provided, an access token will be created for the user. Optional. (default: false).
- `--must-change-password`: If provided, the created user will be required to choose a newer password after the
initial login. Optional. (default: true).
- `--must-change-password`: The created user will be required to set a new password after the initial login, default: true. It could be disabled by `--must-change-password=false`.
- `--random-password`: If provided, a randomly generated password will be used as the password of the created
user. The value of `--password` will be discarded. Optional.
- `--random-password-length`: If provided, it will be used to configure the length of the randomly generated
@ -95,7 +94,7 @@ Admin operations:
- Options:
- `--username value`, `-u value`: Username. Required.
- `--password value`, `-p value`: New password. Required.
- `--must-change-password`: If provided, the user is required to choose a new password after the login. Optional.
- `--must-change-password`: The user is required to set a new password after the login, default: true. It could be disabled by `--must-change-password=false`.
- Examples:
- `gitea admin user change-password --username myname --password asecurepassword`
- `must-change-password`:

View File

@ -608,9 +608,10 @@ And the following unique queues:
- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users.
- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts:
- `userid` - use the userid / sub attribute
- `nickname` - use the nickname attribute
- `nickname` - use the nickname
- `preferred_username` - use the preferred_username
- `email` - use the username part of the email attribute
- Note: `nickname` and `email` options will normalize input strings using the following criteria:
- Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria:
- diacritics are removed
- the characters in the set `['´\x60]` are removed
- the characters in the set `[\s~+]` are replaced with `-`
@ -1197,14 +1198,6 @@ in this mapping or the filetype using heuristics.
- `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Asia/Shanghai
## Task (`task`)
Task queue configuration has been moved to `queue.task`. However, the below configuration values are kept for backwards compatibility:
- `QUEUE_TYPE`: **channel**: Task queue type, could be `channel` or `redis`.
- `QUEUE_LENGTH`: **1000**: Task queue length, available only when `QUEUE_TYPE` is `channel`.
- `QUEUE_CONN_STR`: **redis://127.0.0.1:6379/0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `redis://123@127.0.0.1:6379/0` or `redis+cluster://123@127.0.0.1:6379/0`.
## Migrations (`migrations`)
- `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations.

View File

@ -1128,15 +1128,6 @@ ALLOW_DATA_URI_IMAGES = true
- `DEFAULT_UI_LOCATION`:在 UI 上的默认时间位置,以便我们可以在 UI 上显示正确的用户时间。例如Asia/Shanghai
## 任务 (`task`)
任务队列配置已移动到 `queue.task`。然而,以下配置值仍保留以确保向后兼容:
- `QUEUE_TYPE`**channel**:任务队列类型,可以是 `channel``redis`
- `QUEUE_LENGTH`**1000**:任务队列长度,仅在 `QUEUE_TYPE``channel` 时可用。
- `QUEUE_CONN_STR`**redis://127.0.0.1:6379/0**:任务队列连接字符串,仅在 `QUEUE_TYPE``redis` 时可用。
如果 redis 需要密码,使用 `redis://123@127.0.0.1:6379/0``redis+cluster://123@127.0.0.1:6379/0`
## 迁移 (`migrations`)
- `MAX_ATTEMPTS`**3**:每次 http/https 请求的最大尝试次数(用于迁移)。

View File

@ -303,34 +303,3 @@ sudo systemctl enable act_runner --now
```
If using Docker, the `act_runner` user should also be added to the `docker` group before starting the service. Keep in mind that this effectively gives `act_runner` root access to the system [[1]](https://docs.docker.com/engine/security/#docker-daemon-attack-surface).
## Configuration variable
You can create configuration variables on the user, organization and repository level.
The level of the variable depends on where you created it.
### Naming conventions
The following rules apply to variable names:
- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `GITHUB_` and `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not be `CI`.
### Using variable
After creating configuration variables, they will be automatically filled in the `vars` context.
They can be accessed through expressions like `{{ vars.VARIABLE_NAME }}` in the workflow.
### Precedence
If a variable with the same name exists at multiple levels, the variable at the lowest level takes precedence:
A repository variable will always be chosen over an organization/user variable.

View File

@ -258,32 +258,3 @@ Runner的标签用于确定Runner可以运行哪些Job以及如何运行它们
Runner将从Gitea实例获取Job并自动运行它们。
由于Act Runner仍处于开发中建议定期检查最新版本并进行升级。
## 变量
您可以创建用户、组织和仓库级别的变量。变量的级别取决于创建它的位置。
### 命名规则
以下规则适用于变量名:
- 变量名称只能包含字母数字字符 (`[a-z]`, `[A-Z]`, `[0-9]`) 或下划线 (`_`)。不允许使用空格。
- 变量名称不能以 `GITHUB_``GITEA_` 前缀开头。
- 变量名称不能以数字开头。
- 变量名称不区分大小写。
- 变量名称在创建它们的级别上必须是唯一的。
- 变量名称不能为 “CI”。
### 使用
创建配置变量后,它们将自动填充到 `vars` 上下文中。您可以在工作流中使用类似 `{{ vars.VARIABLE_NAME }}` 这样的表达式来使用它们。
### 优先级
如果同名变量存在于多个级别,则级别最低的变量优先。
仓库级别的变量总是比组织或者用户级别的变量优先被选中。

View File

@ -104,7 +104,7 @@ However, if a job container tries to fetch code from localhost, it will fail bec
### Connection 3, act runner to internet
When you use some actions like `actions/checkout@v4`, the act runner downloads the scripts, not the job containers.
By default, it downloads from [gitea.com](http://gitea.com/), so it requires access to the internet.
By default, it downloads from [github.com](http://github.com/), so it requires access to the internet. If you configure the `DEFAULT_ACTIONS_URL` to `self`, then it will download from your Gitea instance by default. Then it will not connect to internet when downloading the action itself.
It also downloads some docker images from Docker Hub by default, which also requires internet access.
However, internet access is not strictly necessary.

View File

@ -105,7 +105,8 @@ act runner 必须能够连接到Gitea以接收任务并发送执行结果回来
### 连接 3act runner到互联网
当您使用诸如 `actions/checkout@v4` 的一些Actions时act runner下载的是脚本而不是Job容器。
默认情况下,它从[gitea.com](http://gitea.com/)下载,因此需要访问互联网。
默认情况下,它从[github.com](http://github.com/)下载,因此需要访问互联网。如果您设置的是 self
那么默认将从您的当前Gitea实例下载那么此步骤不需要连接到互联网。
它还默认从Docker Hub下载一些Docker镜像这也需要互联网访问。
然而,互联网访问并不是绝对必需的。

View File

@ -0,0 +1,41 @@
---
date: "2024-04-10T22:21:00+08:00"
title: "Variables"
slug: "actions-variables"
sidebar_position: 25
draft: false
toc: false
menu:
sidebar:
parent: "actions"
name: "Variables"
sidebar_position: 25
identifier: "actions-variables"
---
## Variables
You can create configuration variables on the user, organization and repository level.
The level of the variable depends on where you created it. When creating a variable, the
key will be converted to uppercase. You need use uppercase on the yaml file.
### Naming conventions
The following rules apply to variable names:
- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `GITHUB_` and `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not be `CI`.
### Using variable
After creating configuration variables, they will be automatically filled in the `vars` context.
They can be accessed through expressions like `${{ vars.VARIABLE_NAME }}` in the workflow.
### Precedence
If a variable with the same name exists at multiple levels, the variable at the lowest level takes precedence:
A repository variable will always be chosen over an organization/user variable.

View File

@ -0,0 +1,39 @@
---
date: "2024-04-10T22:21:00+08:00"
title: "变量"
slug: "actions-variables"
sidebar_position: 25
draft: false
toc: false
menu:
sidebar:
parent: "actions"
name: "变量"
sidebar_position: 25
identifier: "actions-variables"
---
## 变量
您可以创建用户、组织和仓库级别的变量。变量的级别取决于创建它的位置。当创建变量时,变量的名称会被
转换为大写在yaml文件中引用时需要使用大写。
### 命名规则
以下规则适用于变量名:
- 变量名称只能包含字母数字字符 (`[a-z]`, `[A-Z]`, `[0-9]`) 或下划线 (`_`)。不允许使用空格。
- 变量名称不能以 `GITHUB_``GITEA_` 前缀开头。
- 变量名称不能以数字开头。
- 变量名称不区分大小写。
- 变量名称在创建它们的级别上必须是唯一的。
- 变量名称不能为 `CI`
### 使用
创建配置变量后,它们将自动填充到 `vars` 上下文中。您可以在工作流中使用类似 `${{ vars.VARIABLE_NAME }}` 这样的表达式来使用它们。
### 优先级
如果同名变量存在于多个级别,则级别最低的变量优先。
仓库级别的变量总是比组织或者用户级别的变量优先被选中。

8
go.mod
View File

@ -24,7 +24,6 @@ require (
github.com/buildkite/terminal-to-html/v3 v3.11.0
github.com/caddyserver/certmagic v0.20.0
github.com/chi-middleware/proxy v1.1.1
github.com/denisenkom/go-mssqldb v0.12.3
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
@ -77,6 +76,7 @@ require (
github.com/meilisearch/meilisearch-go v0.26.2
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/microsoft/go-mssqldb v1.7.0
github.com/minio/minio-go/v7 v7.0.69
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52
@ -105,11 +105,11 @@ require (
github.com/yuin/goldmark v1.7.0
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.21.0
golang.org/x/crypto v0.22.0
golang.org/x/image v0.15.0
golang.org/x/net v0.22.0
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.18.0
golang.org/x/sys v0.18.0
golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.19.0
google.golang.org/grpc v1.62.1

44
go.sum
View File

@ -38,11 +38,20 @@ github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 h1:r3qt8PCHnfjOv9PN3H
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121/go.mod h1:Ock8XgA7pvULhIaHGAk/cDnRfNrF9Jey81nPcc403iU=
github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U=
github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
@ -220,7 +229,6 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@ -355,7 +363,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -513,6 +520,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
@ -551,6 +560,8 @@ github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Cl
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
@ -574,7 +585,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
@ -627,7 +637,8 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -836,7 +847,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@ -846,8 +856,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
@ -871,7 +881,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -881,8 +890,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -932,8 +941,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -943,8 +952,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -1022,7 +1031,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=

View File

@ -16,14 +16,9 @@ import (
type ActionJobList []*ActionRunJob
func (jobs ActionJobList) GetRunIDs() []int64 {
ids := make(container.Set[int64], len(jobs))
for _, j := range jobs {
if j.RunID == 0 {
continue
}
ids.Add(j.RunID)
}
return ids.Values()
return container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
return j.RunID, j.RunID != 0
})
}
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {

View File

@ -19,19 +19,15 @@ type RunList []*ActionRun
// GetUserIDs returns a slice of user's id
func (runs RunList) GetUserIDs() []int64 {
ids := make(container.Set[int64], len(runs))
for _, run := range runs {
ids.Add(run.TriggerUserID)
}
return ids.Values()
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
return run.TriggerUserID, true
})
}
func (runs RunList) GetRepoIDs() []int64 {
ids := make(container.Set[int64], len(runs))
for _, run := range runs {
ids.Add(run.RepoID)
}
return ids.Values()
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
return run.RepoID, true
})
}
func (runs RunList) LoadTriggerUser(ctx context.Context) error {

View File

@ -16,14 +16,9 @@ type RunnerList []*ActionRunner
// GetUserIDs returns a slice of user's id
func (runners RunnerList) GetUserIDs() []int64 {
ids := make(container.Set[int64], len(runners))
for _, runner := range runners {
if runner.OwnerID == 0 {
continue
}
ids.Add(runner.OwnerID)
}
return ids.Values()
return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) {
return runner.OwnerID, runner.OwnerID != 0
})
}
func (runners RunnerList) LoadOwners(ctx context.Context) error {
@ -41,16 +36,9 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error {
}
func (runners RunnerList) getRepoIDs() []int64 {
repoIDs := make(container.Set[int64], len(runners))
for _, runner := range runners {
if runner.RepoID == 0 {
continue
}
if _, ok := repoIDs[runner.RepoID]; !ok {
repoIDs[runner.RepoID] = struct{}{}
}
}
return repoIDs.Values()
return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) {
return runner.RepoID, runner.RepoID > 0
})
}
func (runners RunnerList) LoadRepos(ctx context.Context) error {

View File

@ -18,19 +18,15 @@ type ScheduleList []*ActionSchedule
// GetUserIDs returns a slice of user's id
func (schedules ScheduleList) GetUserIDs() []int64 {
ids := make(container.Set[int64], len(schedules))
for _, schedule := range schedules {
ids.Add(schedule.TriggerUserID)
}
return ids.Values()
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
return schedule.TriggerUserID, true
})
}
func (schedules ScheduleList) GetRepoIDs() []int64 {
ids := make(container.Set[int64], len(schedules))
for _, schedule := range schedules {
ids.Add(schedule.RepoID)
}
return ids.Values()
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
return schedule.RepoID, true
})
}
func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
@ -44,6 +40,9 @@ func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
schedule.TriggerUser = user_model.NewActionsUser()
} else {
schedule.TriggerUser = users[schedule.TriggerUserID]
if schedule.TriggerUser == nil {
schedule.TriggerUser = user_model.NewGhostUser()
}
}
}
return nil

View File

@ -16,11 +16,9 @@ import (
type SpecList []*ActionScheduleSpec
func (specs SpecList) GetScheduleIDs() []int64 {
ids := make(container.Set[int64], len(specs))
for _, spec := range specs {
ids.Add(spec.ScheduleID)
}
return ids.Values()
return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) {
return spec.ScheduleID, true
})
}
func (specs SpecList) LoadSchedules(ctx context.Context) error {
@ -46,11 +44,9 @@ func (specs SpecList) LoadSchedules(ctx context.Context) error {
}
func (specs SpecList) GetRepoIDs() []int64 {
ids := make(container.Set[int64], len(specs))
for _, spec := range specs {
ids.Add(spec.RepoID)
}
return ids.Values()
return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) {
return spec.RepoID, true
})
}
func (specs SpecList) LoadRepos(ctx context.Context) error {

View File

@ -11,6 +11,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -227,7 +228,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if runner.RepoID != 0 {
jobCond = builder.Eq{"repo_id": runner.RepoID}
} else if runner.OwnerID != 0 {
jobCond = builder.In("repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": runner.OwnerID}))
jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository").
Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id").
Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions}))
}
if jobCond.IsValid() {
jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond))

View File

@ -16,14 +16,9 @@ import (
type TaskList []*ActionTask
func (tasks TaskList) GetJobIDs() []int64 {
ids := make(container.Set[int64], len(tasks))
for _, t := range tasks {
if t.JobID == 0 {
continue
}
ids.Add(t.JobID)
}
return ids.Values()
return container.FilterSlice(tasks, func(t *ActionTask) (int64, bool) {
return t.JobID, t.JobID != 0
})
}
func (tasks TaskList) LoadJobs(ctx context.Context) error {

View File

@ -22,11 +22,9 @@ import (
type ActionList []*Action
func (actions ActionList) getUserIDs() []int64 {
userIDs := make(container.Set[int64], len(actions))
for _, action := range actions {
userIDs.Add(action.ActUserID)
}
return userIDs.Values()
return container.FilterSlice(actions, func(action *Action) (int64, bool) {
return action.ActUserID, true
})
}
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
@ -50,11 +48,9 @@ func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_mod
}
func (actions ActionList) getRepoIDs() []int64 {
repoIDs := make(container.Set[int64], len(actions))
for _, action := range actions {
repoIDs.Add(action.RepoID)
}
return repoIDs.Values()
return container.FilterSlice(actions, func(action *Action) (int64, bool) {
return action.RepoID, true
})
}
func (actions ActionList) LoadRepositories(ctx context.Context) error {
@ -80,18 +76,19 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*
userMap = make(map[int64]*user_model.User)
}
userSet := make(container.Set[int64], len(actions))
for _, action := range actions {
missingUserIDs := container.FilterSlice(actions, func(action *Action) (int64, bool) {
if action.Repo == nil {
continue
}
if _, ok := userMap[action.Repo.OwnerID]; !ok {
userSet.Add(action.Repo.OwnerID)
return 0, false
}
_, alreadyLoaded := userMap[action.Repo.OwnerID]
return action.Repo.OwnerID, !alreadyLoaded
})
if len(missingUserIDs) == 0 {
return nil
}
if err := db.GetEngine(ctx).
In("id", userSet.Values()).
In("id", missingUserIDs).
Find(&userMap); err != nil {
return fmt.Errorf("find user: %w", err)
}
@ -135,6 +132,9 @@ func (actions ActionList) LoadComments(ctx context.Context) error {
commentIDs = append(commentIDs, action.CommentID)
}
}
if len(commentIDs) == 0 {
return nil
}
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {

View File

@ -190,14 +190,12 @@ func (nl NotificationList) LoadAttributes(ctx context.Context) error {
}
func (nl NotificationList) getPendingRepoIDs() []int64 {
ids := make(container.Set[int64], len(nl))
for _, notification := range nl {
if notification.Repository != nil {
continue
return container.FilterSlice(nl, func(n *Notification) (int64, bool) {
if n.Repository != nil {
return 0, false
}
ids.Add(notification.RepoID)
}
return ids.Values()
return n.RepoID, true
})
}
// LoadRepos loads repositories from database

View File

@ -76,23 +76,14 @@ func calcFingerprintNative(publicKeyContent string) (string, error) {
// CalcFingerprint calculate public key's fingerprint
func CalcFingerprint(publicKeyContent string) (string, error) {
// Call the method based on configuration
var (
fnName, fp string
err error
)
if len(setting.SSH.KeygenPath) == 0 {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
useNative := setting.SSH.KeygenPath == ""
calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen)
fp, err := calcFn(publicKeyContent)
if err != nil {
if IsErrKeyUnableVerify(err) {
log.Info("%s", publicKeyContent)
return "", err
}
return "", fmt.Errorf("%s: %w", fnName, err)
return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err)
}
return fp, nil
}

View File

@ -137,6 +137,11 @@ func (app *OAuth2Application) TableName() string {
// ContainsRedirectURI checks if redirectURI is allowed for app
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
// OAuth2 requires the redirect URI to be an exact match, no dynamic parts are allowed.
// https://stackoverflow.com/questions/55524480/should-dynamic-query-parameters-be-present-in-the-redirection-uri-for-an-oauth2
// https://www.rfc-editor.org/rfc/rfc6819#section-5.2.3.3
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
contains := func(s string) bool {
s = strings.TrimSuffix(strings.ToLower(s), "/")
for _, u := range app.RedirectURIs {

View File

@ -10,21 +10,21 @@ import (
)
// CountOrphanedObjects count subjects with have no existing refobject anymore
func CountOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) (int64, error) {
func CountOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) (int64, error) {
return GetEngine(ctx).
Table("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}).
Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refObject + "`.id"}).
Select("COUNT(`" + subject + "`.`id`)").
Count()
}
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
func DeleteOrphanedObjects(ctx context.Context, subject, refobject, joinCond string) error {
func DeleteOrphanedObjects(ctx context.Context, subject, refObject, joinCond string) error {
subQuery := builder.Select("`"+subject+"`.id").
From("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"})
Join("LEFT", "`"+refObject+"`", joinCond).
Where(builder.IsNull{"`" + refObject + "`.id"})
b := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`")
_, err := GetEngine(ctx).Exec(b)
return err

View File

@ -21,9 +21,9 @@ import (
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
_ "github.com/denisenkom/go-mssqldb" // Needed for the MSSQL driver
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver
_ "github.com/microsoft/go-mssqldb" // Needed for the MSSQL driver
)
var (
@ -284,8 +284,8 @@ func MaxBatchInsertSize(bean any) int {
}
// IsTableNotEmpty returns true if table has at least one record
func IsTableNotEmpty(tableName string) (bool, error) {
return x.Table(tableName).Exist()
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
return x.Table(beanOrTableName).Exist()
}
// DeleteAllRecords will delete all the records of this table

View File

@ -297,6 +297,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
sess := db.GetEngine(ctx)
// check whether from branch exist
var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil {
@ -308,6 +309,24 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
}
}
// check whether to branch exist or is_deleted
var dstBranch Branch
exist, err = db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, to).Get(&dstBranch)
if err != nil {
return err
}
if exist {
if !dstBranch.IsDeleted {
return ErrBranchAlreadyExists{
BranchName: to,
}
}
if _, err := db.GetEngine(ctx).ID(dstBranch.ID).NoAutoCondition().Delete(&dstBranch); err != nil {
return err
}
}
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to,
@ -362,12 +381,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
return err
}
// 5. do git action
if err = gitAction(ctx, isDefault); err != nil {
return err
}
// 6. insert renamed branch record
// 5. insert renamed branch record
renamedBranch := &RenamedBranch{
RepoID: repo.ID,
From: from,
@ -378,6 +392,11 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
return err
}
// 6. do git action
if err = gitAction(ctx, isDefault); err != nil {
return err
}
return committer.Commit()
}

View File

@ -17,15 +17,12 @@ import (
type BranchList []*Branch
func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
ids := container.Set[int64]{}
for _, branch := range branches {
if !branch.IsDeleted {
continue
}
ids.Add(branch.DeletedByID)
}
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
return branch.DeletedByID, branch.IsDeleted
})
usersMap := make(map[int64]*user_model.User, len(ids))
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil {
return err
}
for _, branch := range branches {
@ -41,14 +38,13 @@ func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
}
func (branches BranchList) LoadPusher(ctx context.Context) error {
ids := container.Set[int64]{}
for _, branch := range branches {
if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher
ids.Add(branch.PusherID)
}
}
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
// pusher_id maybe zero because some branches are sync by backend with no pusher
return branch.PusherID, branch.PusherID > 0
})
usersMap := make(map[int64]*user_model.User, len(ids))
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil {
return err
}
for _, branch := range branches {

View File

@ -292,30 +292,27 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
}
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map[int64][]*CommitStatus, error) {
type result struct {
Index int64
RepoID int64
SHA string
}
results := make([]result, 0, len(repoIDsToLatestCommitSHAs))
results := make([]result, 0, len(repoSHAs))
getBase := func() *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{})
}
// Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
for repoID, sha := range repoIDsToLatestCommitSHAs {
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
conds := make([]builder.Cond, 0, len(repoSHAs))
for _, repoSHA := range repoSHAs {
conds = append(conds, builder.Eq{"repo_id": repoSHA.RepoID, "sha": repoSHA.SHA})
}
sess := getBase().Where(builder.Or(conds...)).
Select("max( `index` ) as `index`, repo_id").
GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions)
}
Select("max( `index` ) as `index`, repo_id, sha").
GroupBy("context_hash, repo_id, sha").OrderBy("max( `index` ) desc")
err := sess.Find(&results)
if err != nil {
@ -332,7 +329,7 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA
cond := builder.Eq{
"`index`": result.Index,
"repo_id": result.RepoID,
"sha": repoIDsToLatestCommitSHAs[result.RepoID],
"sha": result.SHA,
}
conds = append(conds, cond)
}

View File

@ -0,0 +1,88 @@
// Copyright 2024 Gitea. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"xorm.io/builder"
)
// CommitStatusSummary holds the latest commit Status of a single Commit
type CommitStatusSummary struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
TargetURL string `xorm:"TEXT"`
}
func init() {
db.RegisterModel(new(CommitStatusSummary))
}
type RepoSHA struct {
RepoID int64
SHA string
}
func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA) ([]*CommitStatus, error) {
cond := builder.NewCond()
for _, rs := range repoSHAs {
cond = cond.Or(builder.Eq{"repo_id": rs.RepoID, "sha": rs.SHA})
}
var summaries []CommitStatusSummary
if err := db.GetEngine(ctx).Where(cond).Find(&summaries); err != nil {
return nil, err
}
commitStatuses := make([]*CommitStatus, 0, len(repoSHAs))
for _, summary := range summaries {
commitStatuses = append(commitStatuses, &CommitStatus{
RepoID: summary.RepoID,
SHA: summary.SHA,
State: summary.State,
TargetURL: summary.TargetURL,
})
}
return commitStatuses, nil
}
func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error {
commitStatuses, _, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
if err != nil {
return err
}
state := CalcCommitStatus(commitStatuses)
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
// so we need to use insert in on duplicate
if setting.Database.Type.IsMySQL() {
_, err := db.GetEngine(ctx).Exec("INSERT INTO commit_status_summary (repo_id,sha,state,target_url) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE state=?",
repoID, sha, state.State, state.TargetURL, state.State)
return err
}
if cnt, err := db.GetEngine(ctx).Where("repo_id=? AND sha=?", repoID, sha).
Cols("state, target_url").
Update(&CommitStatusSummary{
State: state.State,
TargetURL: state.TargetURL,
}); err != nil {
return err
} else if cnt == 0 {
_, err = db.GetEngine(ctx).Insert(&CommitStatusSummary{
RepoID: repoID,
SHA: sha,
State: state.State,
TargetURL: state.TargetURL,
})
return err
}
return nil
}

View File

@ -1272,10 +1272,9 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
return nil
}
issueIDs := make(container.Set[int64])
for _, comment := range comments {
issueIDs.Add(comment.IssueID)
}
issueIDs := container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.IssueID, true
})
ctx, committer, err := db.TxContext(ctx)
if err != nil {
@ -1298,7 +1297,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
}
}
for issueID := range issueIDs {
for _, issueID := range issueIDs {
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?",
issueID, CommentTypeComment, issueID); err != nil {
return err

View File

@ -17,13 +17,9 @@ import (
type CommentList []*Comment
func (comments CommentList) getPosterIDs() []int64 {
posterIDs := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.PosterID > 0 {
posterIDs.Add(comment.PosterID)
}
}
return posterIDs.Values()
return container.FilterSlice(comments, func(c *Comment) (int64, bool) {
return c.PosterID, c.PosterID > 0
})
}
// LoadPosters loads posters
@ -44,13 +40,9 @@ func (comments CommentList) LoadPosters(ctx context.Context) error {
}
func (comments CommentList) getLabelIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.LabelID > 0 {
ids.Add(comment.LabelID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.LabelID, comment.LabelID > 0
})
}
func (comments CommentList) loadLabels(ctx context.Context) error {
@ -94,13 +86,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
}
func (comments CommentList) getMilestoneIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.MilestoneID > 0 {
ids.Add(comment.MilestoneID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.MilestoneID, comment.MilestoneID > 0
})
}
func (comments CommentList) loadMilestones(ctx context.Context) error {
@ -137,13 +125,9 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
}
func (comments CommentList) getOldMilestoneIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.OldMilestoneID > 0 {
ids.Add(comment.OldMilestoneID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.OldMilestoneID, comment.OldMilestoneID > 0
})
}
func (comments CommentList) loadOldMilestones(ctx context.Context) error {
@ -180,13 +164,9 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
}
func (comments CommentList) getAssigneeIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.AssigneeID > 0 {
ids.Add(comment.AssigneeID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.AssigneeID, comment.AssigneeID > 0
})
}
func (comments CommentList) loadAssignees(ctx context.Context) error {
@ -237,14 +217,9 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
// getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
func (comments CommentList) getIssueIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.Issue != nil {
continue
}
ids.Add(comment.IssueID)
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.IssueID, comment.Issue == nil
})
}
// Issues returns all the issues of comments
@ -311,16 +286,12 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
}
func (comments CommentList) getDependentIssueIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
if comment.DependentIssue != nil {
continue
return 0, false
}
if comment.DependentIssueID > 0 {
ids.Add(comment.DependentIssueID)
}
}
return ids.Values()
return comment.DependentIssueID, comment.DependentIssueID > 0
})
}
func (comments CommentList) loadDependentIssues(ctx context.Context) error {
@ -375,15 +346,9 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
// getAttachmentCommentIDs only return the comment ids which possibly has attachments
func (comments CommentList) getAttachmentCommentIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.Type == CommentTypeComment ||
comment.Type == CommentTypeReview ||
comment.Type == CommentTypeCode {
ids.Add(comment.ID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.ID, comment.Type.HasAttachmentSupport()
})
}
// LoadAttachmentsByIssue loads attachments by issue id
@ -451,13 +416,9 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
}
func (comments CommentList) getReviewIDs() []int64 {
ids := make(container.Set[int64], len(comments))
for _, comment := range comments {
if comment.ReviewID > 0 {
ids.Add(comment.ReviewID)
}
}
return ids.Values()
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.ReviewID, comment.ReviewID > 0
})
}
func (comments CommentList) loadReviews(ctx context.Context) error {

View File

@ -21,16 +21,15 @@ type IssueList []*Issue
// get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo
func (issues IssueList) getRepoIDs() []int64 {
repoIDs := make(container.Set[int64], len(issues))
for _, issue := range issues {
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
if issue.Repo == nil {
repoIDs.Add(issue.RepoID)
return issue.RepoID, true
}
if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil {
repoIDs.Add(issue.PullRequest.HeadRepoID)
return issue.PullRequest.HeadRepoID, true
}
}
return repoIDs.Values()
return 0, false
})
}
// LoadRepositories loads issues' all repositories
@ -74,11 +73,9 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
}
func (issues IssueList) getPosterIDs() []int64 {
posterIDs := make(container.Set[int64], len(issues))
for _, issue := range issues {
posterIDs.Add(issue.PosterID)
}
return posterIDs.Values()
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
return issue.PosterID, true
})
}
func (issues IssueList) loadPosters(ctx context.Context) error {
@ -193,11 +190,9 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
}
func (issues IssueList) getMilestoneIDs() []int64 {
ids := make(container.Set[int64], len(issues))
for _, issue := range issues {
ids.Add(issue.MilestoneID)
}
return ids.Values()
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
return issue.MilestoneID, true
})
}
func (issues IssueList) loadMilestones(ctx context.Context) error {

View File

@ -62,11 +62,13 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission,
return true
}
if len(p.Units) < 1 {
// the code below depends on units to get the repository ID, not ideal but just keep it for now
firstUnitRepoID := p.GetFirstUnitRepoID()
if firstUnitRepoID == 0 {
return false
}
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, firstUnitRepoID, branch)
if err != nil {
return false
}

View File

@ -305,14 +305,12 @@ func (list ReactionList) GroupByType() map[string]ReactionList {
}
func (list ReactionList) getUserIDs() []int64 {
userIDs := make(container.Set[int64], len(list))
for _, reaction := range list {
return container.FilterSlice(list, func(reaction *Reaction) (int64, bool) {
if reaction.OriginalAuthor != "" {
continue
return 0, false
}
userIDs.Add(reaction.UserID)
}
return userIDs.Values()
return reaction.UserID, true
})
}
func valuesUser(m map[int64]*user_model.User) []*user_model.User {

View File

@ -38,12 +38,11 @@ func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
}
func (reviews ReviewList) LoadIssues(ctx context.Context) error {
issueIDs := container.Set[int64]{}
for i := 0; i < len(reviews); i++ {
issueIDs.Add(reviews[i].IssueID)
}
issueIDs := container.FilterSlice(reviews, func(review *Review) (int64, bool) {
return review.IssueID, true
})
issues, err := GetIssuesByIDs(ctx, issueIDs.Values())
issues, err := GetIssuesByIDs(ctx, issueIDs)
if err != nil {
return err
}

View File

@ -576,7 +576,14 @@ var migrations = []Migration{
// Gitea 1.22.0 ends at 294
// v294 -> v295
NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
// v295 -> v296
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
// v296 -> v297
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
// v297 -> v298
NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode),
}
// GetCurrentDBVersion returns the current db version

View File

@ -336,7 +336,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
if err != nil {
return false, err
}
if perm.UnitsMode == nil {
if len(perm.UnitsMode) == 0 {
for _, u := range perm.Units {
if u.Type == UnitTypeCode {
return AccessModeWrite <= perm.AccessMode, nil

View File

@ -0,0 +1,18 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import "xorm.io/xorm"
func AddCommitStatusSummary(x *xorm.Engine) error {
type CommitStatusSummary struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"`
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"`
State string `xorm:"VARCHAR(7) NOT NULL"`
}
// there is no migrations because if there is no data on this table, it will fall back to get data
// from commit status
return x.Sync2(new(CommitStatusSummary))
}

View File

@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import "xorm.io/xorm"
func AddCommitStatusSummary2(x *xorm.Engine) error {
type CommitStatusSummary struct {
ID int64 `xorm:"pk autoincr"`
TargetURL string `xorm:"TEXT"`
}
// there is no migrations because if there is no data on this table, it will fall back to get data
// from commit status
return x.Sync(new(CommitStatusSummary))
}

View File

@ -0,0 +1,17 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"code.gitea.io/gitea/models/perm"
"xorm.io/xorm"
)
func AddRepoUnitEveryoneAccessMode(x *xorm.Engine) error {
type RepoUnit struct { //revive:disable-line:exported
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
}
return x.Sync(&RepoUnit{})
}

View File

@ -9,6 +9,7 @@ import (
"fmt"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
@ -402,6 +403,8 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
&TeamInvite{OrgID: org.ID},
&secret_model.Secret{OwnerID: org.ID},
&user_model.Blocking{BlockerID: org.ID},
&actions_model.ActionRunner{OwnerID: org.ID},
&actions_model.ActionRunnerToken{OwnerID: org.ID},
); err != nil {
return fmt.Errorf("DeleteBeans: %w", err)
}

View File

@ -130,11 +130,11 @@ func (t *Team) GetUnitsMap() map[string]string {
m := make(map[string]string)
if t.AccessMode >= perm.AccessModeAdmin {
for _, u := range unit.Units {
m[u.NameKey] = t.AccessMode.String()
m[u.NameKey] = t.AccessMode.ToString()
}
} else {
for _, u := range t.Units {
m[u.Unit().NameKey] = u.AccessMode.String()
m[u.Unit().NameKey] = u.AccessMode.ToString()
}
}
return m
@ -174,23 +174,27 @@ func (t *Team) LoadMembers(ctx context.Context) (err error) {
return err
}
// UnitEnabled returns if the team has the given unit type enabled
// UnitEnabled returns true if the team has the given unit type enabled
func (t *Team) UnitEnabled(ctx context.Context, tp unit.Type) bool {
return t.UnitAccessMode(ctx, tp) > perm.AccessModeNone
}
// UnitAccessMode returns if the team has the given unit type enabled
// UnitAccessMode returns the access mode for the given unit type, "none" for non-existent units
func (t *Team) UnitAccessMode(ctx context.Context, tp unit.Type) perm.AccessMode {
accessMode, _ := t.UnitAccessModeEx(ctx, tp)
return accessMode
}
func (t *Team) UnitAccessModeEx(ctx context.Context, tp unit.Type) (accessMode perm.AccessMode, exist bool) {
if err := t.LoadUnits(ctx); err != nil {
log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
}
for _, unit := range t.Units {
if unit.Type == tp {
return unit.AccessMode
for _, u := range t.Units {
if u.Type == tp {
return u.AccessMode, true
}
}
return perm.AccessModeNone
return perm.AccessModeNone, false
}
// IsUsableTeamName tests if a name could be as team name

View File

@ -63,13 +63,11 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
}
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
max := perm.AccessModeNone
maxMode := perm.AccessModeNone
for _, mode := range modes {
if mode > max {
max = mode
}
maxMode = max(maxMode, mode)
}
return max
return maxMode
}
type userAccess struct {

View File

@ -6,6 +6,7 @@ package access
import (
"context"
"fmt"
"slices"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
@ -14,13 +15,15 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
// Permission contains all the permissions related variables to a repository for a user
type Permission struct {
AccessMode perm_model.AccessMode
Units []*repo_model.RepoUnit
UnitsMode map[unit.Type]perm_model.AccessMode
units []*repo_model.RepoUnit
unitsMode map[unit.Type]perm_model.AccessMode
}
// IsOwner returns true if current user is the owner of repository.
@ -33,25 +36,44 @@ func (p *Permission) IsAdmin() bool {
return p.AccessMode >= perm_model.AccessModeAdmin
}
// HasAccess returns true if the current user has at least read access to any unit of this repository
// HasAccess returns true if the current user might have at least read access to any unit of this repository
func (p *Permission) HasAccess() bool {
if p.UnitsMode == nil {
return p.AccessMode >= perm_model.AccessModeRead
}
return len(p.UnitsMode) > 0
return len(p.unitsMode) > 0 || p.AccessMode >= perm_model.AccessModeRead
}
// UnitAccessMode returns current user accessmode to the specify unit of the repository
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
if p.UnitsMode == nil {
for _, u := range p.Units {
if u.Type == unitType {
return p.AccessMode
}
}
return perm_model.AccessModeNone
// HasUnits returns true if the permission contains attached units
func (p *Permission) HasUnits() bool {
return len(p.units) > 0
}
// GetFirstUnitRepoID returns the repo ID of the first unit, it is a fragile design and should NOT be used anymore
// deprecated
func (p *Permission) GetFirstUnitRepoID() int64 {
if len(p.units) > 0 {
return p.units[0].RepoID
}
return 0
}
// UnitAccessMode returns current user access mode to the specify unit of the repository
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
if p.unitsMode != nil {
// if the units map contains the access mode, use it, but admin/owner mode could override it
if m, ok := p.unitsMode[unitType]; ok {
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
}
}
// if the units map does not contain the access mode, return the default access mode if the unit exists
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
return util.Iif(hasUnit, p.AccessMode, perm_model.AccessModeNone)
}
func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
p.units = units
p.unitsMode = make(map[unit.Type]perm_model.AccessMode)
for _, u := range p.units {
p.unitsMode[u.Type] = mode
}
return p.UnitsMode[unitType]
}
// CanAccess returns true if user has mode access to the unit of the repository
@ -102,23 +124,33 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
return p.CanWrite(unit.TypeIssues)
}
func (p *Permission) ReadableUnitTypes() []unit.Type {
types := make([]unit.Type, 0, len(p.units))
for _, u := range p.units {
if p.CanRead(u.Type) {
types = append(types, u.Type)
}
}
return types
}
func (p *Permission) LogString() string {
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)}
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
for i, unit := range p.Units {
for i, u := range p.units {
config := ""
if unit.Config != nil {
configBytes, err := unit.Config.ToDB()
if u.Config != nil {
configBytes, err := u.Config.ToDB()
config = string(configBytes)
if err != nil {
config = err.Error()
}
}
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config)
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
}
for key, value := range p.UnitsMode {
for key, value := range p.unitsMode {
format += "\nUnitMode[%-v]: %-v"
args = append(args, key.LogString(), value.LogString())
}
@ -126,23 +158,34 @@ func (p *Permission) LogString() string {
return fmt.Sprintf(format, args...)
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
var perm Permission
if log.IsTrace() {
defer func() {
if user == nil {
log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
repo,
perm)
return
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
if user != nil && user.ID > 0 {
for _, u := range perm.units {
if perm.unitsMode == nil {
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
}
log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
user,
repo,
perm)
}()
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.unitsMode[u.Type] {
perm.unitsMode[u.Type] = u.EveryoneAccessMode
}
}
}
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() {
if err == nil {
applyEveryoneRepoPermission(user, &perm)
}
if log.IsTrace() {
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
}
}()
if err = repo.LoadUnits(ctx); err != nil {
return perm, err
}
perm.units = repo.Units
// anonymous user visit private repo.
// TODO: anonymous user visit public unit of private repo???
@ -152,7 +195,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
var isCollaborator bool
var err error
if user != nil {
isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
if err != nil {
@ -160,7 +202,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
}
if err := repo.LoadOwner(ctx); err != nil {
if err = repo.LoadOwner(ctx); err != nil {
return perm, err
}
@ -171,12 +213,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
return perm, nil
}
if err := repo.LoadUnits(ctx); err != nil {
return perm, err
}
perm.Units = repo.Units
// anonymous visit public repo
if user == nil {
perm.AccessMode = perm_model.AccessModeRead
@ -195,19 +231,16 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
return perm, err
}
if err := repo.LoadOwner(ctx); err != nil {
return perm, err
}
if !repo.Owner.IsOrganization() {
return perm, nil
}
perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
// Collaborators on organization
if isCollaborator {
for _, u := range repo.Units {
perm.UnitsMode[u.Type] = perm.AccessMode
perm.unitsMode[u.Type] = perm.AccessMode
}
}
@ -221,7 +254,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
for _, team := range teams {
if team.AccessMode >= perm_model.AccessModeAdmin {
perm.AccessMode = perm_model.AccessModeOwner
perm.UnitsMode = nil
perm.unitsMode = nil
return perm, nil
}
}
@ -229,30 +262,26 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
for _, u := range repo.Units {
var found bool
for _, team := range teams {
teamMode := team.UnitAccessMode(ctx, u.Type)
if teamMode > perm_model.AccessModeNone {
m := perm.UnitsMode[u.Type]
if m < teamMode {
perm.UnitsMode[u.Type] = teamMode
}
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
found = true
}
}
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
if !found && !repo.IsPrivate && !user.IsRestricted {
if _, ok := perm.UnitsMode[u.Type]; !ok {
perm.UnitsMode[u.Type] = perm_model.AccessModeRead
if _, ok := perm.unitsMode[u.Type]; !ok {
perm.unitsMode[u.Type] = perm_model.AccessModeRead
}
}
}
// remove no permission units
perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
for t := range perm.UnitsMode {
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
for t := range perm.unitsMode {
for _, u := range repo.Units {
if u.Type == t {
perm.Units = append(perm.Units, u)
perm.units = append(perm.units, u)
}
}
}
@ -334,7 +363,7 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
if user.IsOrganization() {
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
}
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {

View File

@ -0,0 +1,98 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package access
import (
"testing"
perm_model "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
func TestApplyEveryoneRepoPermission(t *testing.T) {
perm := Permission{
AccessMode: perm_model.AccessModeNone,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeNone},
},
}
applyEveryoneRepoPermission(nil, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{
AccessMode: perm_model.AccessModeNone,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanRead(unit.TypeWiki))
perm = Permission{
AccessMode: perm_model.AccessModeWrite,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanRead(unit.TypeWiki))
assert.False(t, perm.CanWrite(unit.TypeWiki)) // because there is no unit mode, so the everyone-mode is used as the unit's access mode
perm = Permission{
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeWrite,
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanWrite(unit.TypeWiki))
}
func TestUnitAccessMode(t *testing.T) {
perm := Permission{
AccessMode: perm_model.AccessModeNone,
}
assert.Equal(t, perm_model.AccessModeNone, perm.UnitAccessMode(unit.TypeWiki), "no unit, no map, use AccessMode")
perm = Permission{
AccessMode: perm_model.AccessModeRead,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki},
},
}
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "only unit, no map, use AccessMode")
perm = Permission{
AccessMode: perm_model.AccessModeAdmin,
unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeRead,
},
}
assert.Equal(t, perm_model.AccessModeAdmin, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, admin overrides map")
perm = Permission{
AccessMode: perm_model.AccessModeNone,
unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeRead,
},
}
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, use map")
perm = Permission{
AccessMode: perm_model.AccessModeNone,
units: []*repo_model.RepoUnit{
{Type: unit.TypeWiki},
},
unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeRead,
},
}
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
}

View File

@ -5,25 +5,25 @@ package perm
import (
"fmt"
"slices"
"code.gitea.io/gitea/modules/util"
)
// AccessMode specifies the users access mode
type AccessMode int
const (
// AccessModeNone no access
AccessModeNone AccessMode = iota // 0
// AccessModeRead read access
AccessModeRead // 1
// AccessModeWrite write access
AccessModeWrite // 2
// AccessModeAdmin admin access
AccessModeAdmin // 3
// AccessModeOwner owner access
AccessModeOwner // 4
AccessModeNone AccessMode = iota // 0: no access
AccessModeRead // 1: read access
AccessModeWrite // 2: write access
AccessModeAdmin // 3: admin access
AccessModeOwner // 4: owner access
)
func (mode AccessMode) String() string {
// ToString returns the string representation of the access mode, do not make it a Stringer, otherwise it's difficult to render in templates
func (mode AccessMode) ToString() string {
switch mode {
case AccessModeRead:
return "read"
@ -39,19 +39,24 @@ func (mode AccessMode) String() string {
}
func (mode AccessMode) LogString() string {
return fmt.Sprintf("<AccessMode:%d:%s>", mode, mode.String())
return fmt.Sprintf("<AccessMode:%d:%s>", mode, mode.ToString())
}
// ParseAccessMode returns corresponding access mode to given permission string.
func ParseAccessMode(permission string) AccessMode {
func ParseAccessMode(permission string, allowed ...AccessMode) AccessMode {
m := AccessModeNone
switch permission {
case "read":
return AccessModeRead
m = AccessModeRead
case "write":
return AccessModeWrite
m = AccessModeWrite
case "admin":
return AccessModeAdmin
m = AccessModeAdmin
default:
return AccessModeNone
// the "owner" access is not really used for user input, it's mainly for checking access level in code, so don't parse it
}
if len(allowed) == 0 {
return m
}
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
}

View File

@ -0,0 +1,22 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package perm
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAccessMode(t *testing.T) {
names := []string{"none", "read", "write", "admin"}
for i, name := range names {
m := ParseAccessMode(name)
assert.Equal(t, AccessMode(i), m)
}
assert.Equal(t, AccessMode(4), AccessModeOwner)
assert.Equal(t, "owner", AccessModeOwner.ToString())
assert.Equal(t, AccessModeNone, ParseAccessMode("owner"))
assert.Equal(t, AccessModeNone, ParseAccessMode("invalid"))
}

View File

@ -53,7 +53,7 @@ func (repo *Repository) IsDependenciesEnabled(ctx context.Context) bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(ctx, unit.TypeIssues); err != nil {
log.Trace("%s", err)
log.Trace("IsDependenciesEnabled: %v", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies

View File

@ -104,18 +104,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
return nil
}
set := make(container.Set[int64])
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
return repo.OwnerID, true
})
repoIDs := make([]int64, len(repos))
for i := range repos {
set.Add(repos[i].OwnerID)
repoIDs[i] = repos[i].ID
}
// Load owners.
users := make(map[int64]*user_model.User, len(set))
users := make(map[int64]*user_model.User, len(userIDs))
if err := db.GetEngine(ctx).
Where("id > 0").
In("id", set.Values()).
In("id", userIDs).
Find(&users); err != nil {
return fmt.Errorf("find users: %w", err)
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
@ -41,11 +42,12 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
// RepoUnit describes all units of a repository
type RepoUnit struct { //revive:disable-line:exported
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type unit.Type `xorm:"INDEX(s)"`
Config convert.Conversion `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type unit.Type `xorm:"INDEX(s)"`
Config convert.Conversion `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
}
func init() {

View File

@ -191,16 +191,13 @@ type Unit struct {
NameKey string
URI string
DescKey string
Idx int
Priority int
MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
}
// IsLessThan compares order of two units
func (u Unit) IsLessThan(unit Unit) bool {
if (u.Type == TypeExternalTracker || u.Type == TypeExternalWiki) && unit.Type != TypeExternalTracker && unit.Type != TypeExternalWiki {
return false
}
return u.Idx < unit.Idx
return u.Priority < unit.Priority
}
// MaxPerm returns the max perms of this unit
@ -236,7 +233,7 @@ var (
"repo.ext_issues",
"/issues",
"repo.ext_issues.desc",
1,
101,
perm.AccessModeRead,
}
@ -272,7 +269,7 @@ var (
"repo.ext_wiki",
"/wiki",
"repo.ext_wiki.desc",
4,
102,
perm.AccessModeRead,
}

138
modules/cache/cache.go vendored
View File

@ -4,149 +4,75 @@
package cache
import (
"fmt"
"strconv"
"time"
"code.gitea.io/gitea/modules/setting"
mc "gitea.com/go-chi/cache"
_ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
)
var conn mc.Cache
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,
})
}
var defaultCache StringCache
// Init start cache service
func Init() error {
var err error
if conn == nil {
if conn, err = newCache(setting.CacheService.Cache); err != nil {
if defaultCache == nil {
c, err := NewStringCache(setting.CacheService.Cache)
if err != nil {
return err
}
if err = conn.Ping(); err != nil {
for i := 0; i < 10; i++ {
if err = c.Ping(); err == nil {
break
}
time.Sleep(time.Second)
}
if err != nil {
return err
}
defaultCache = c
}
return err
return nil
}
// GetCache returns the currently configured cache
func GetCache() mc.Cache {
return conn
func GetCache() StringCache {
return defaultCache
}
// GetString returns the key value from cache with callback when no key exists in cache
func GetString(key string, getFunc func() (string, error)) (string, error) {
if conn == nil || setting.CacheService.TTL == 0 {
if defaultCache == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
cached := conn.Get(key)
if cached == nil {
cached, exist := defaultCache.Get(key)
if !exist {
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
if value, ok := cached.(string); ok {
return value, nil
}
if stringer, ok := cached.(fmt.Stringer); ok {
return stringer.String(), nil
}
return fmt.Sprintf("%s", cached), nil
}
// GetInt returns key value from cache with callback when no key exists in cache
func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}
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 {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
return value, defaultCache.Put(key, value, setting.CacheService.TTLSeconds())
}
return cached, nil
}
// GetInt64 returns key value from cache with callback when no key exists in cache
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
s, err := GetString(key, func() (string, error) {
v, err := getFunc()
return strconv.FormatInt(v, 10), err
})
if err != nil {
return 0, err
}
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}
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 {
return 0, err
}
return value, nil
default:
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
if s == "" {
return 0, nil
}
return strconv.ParseInt(s, 10, 64)
}
// Remove key from cache
func Remove(key string) {
if conn == nil {
if defaultCache == nil {
return
}
_ = conn.Delete(key)
_ = defaultCache.Delete(key)
}

View File

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/nosql"
"gitea.com/go-chi/cache"
"gitea.com/go-chi/cache" //nolint:depguard
"github.com/redis/go-redis/v9"
)

View File

@ -14,7 +14,7 @@ import (
)
func createTestCache() {
conn, _ = newCache(setting.Cache{
defaultCache, _ = NewStringCache(setting.Cache{
Adapter: "memory",
TTL: time.Minute,
})
@ -25,7 +25,7 @@ func TestNewContext(t *testing.T) {
assert.NoError(t, Init())
setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"}
con, err := newCache(setting.Cache{
con, err := NewStringCache(setting.Cache{
Adapter: "rand",
Conn: "false conf",
Interval: 100,
@ -76,42 +76,6 @@ func TestGetString(t *testing.T) {
Remove("key")
}
func TestGetInt(t *testing.T) {
createTestCache()
data, err := GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.Error(t, err)
assert.Equal(t, 0, data)
data, err = GetInt("key", func() (int, error) {
return 0, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)
data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 0, data)
Remove("key")
data, err = GetInt("key", func() (int, error) {
return 100, nil
})
assert.NoError(t, err)
assert.Equal(t, 100, data)
data, err = GetInt("key", func() (int, error) {
return 0, fmt.Errorf("some error")
})
assert.NoError(t, err)
assert.Equal(t, 100, data)
Remove("key")
}
func TestGetInt64(t *testing.T) {
createTestCache()

View File

@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/modules/json"
mc "gitea.com/go-chi/cache"
mc "gitea.com/go-chi/cache" //nolint:depguard
lru "github.com/hashicorp/golang-lru/v2"
)

120
modules/cache/string_cache.go vendored Normal file
View File

@ -0,0 +1,120 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cache
import (
"errors"
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
chi_cache "gitea.com/go-chi/cache" //nolint:depguard
)
type GetJSONError struct {
err error
cachedError string // Golang error can't be stored in cache, only the string message could be stored
}
func (e *GetJSONError) ToError() error {
if e.err != nil {
return e.err
}
return errors.New("cached error: " + e.cachedError)
}
type StringCache interface {
Ping() error
Get(key string) (string, bool)
Put(key, value string, ttl int64) error
Delete(key string) error
IsExist(key string) bool
PutJSON(key string, v any, ttl int64) error
GetJSON(key string, ptr any) (exist bool, err *GetJSONError)
ChiCache() chi_cache.Cache
}
type stringCache struct {
chiCache chi_cache.Cache
}
func NewStringCache(cacheConfig setting.Cache) (StringCache, error) {
adapter := util.IfZero(cacheConfig.Adapter, "memory")
interval := util.IfZero(cacheConfig.Interval, 60)
cc, err := chi_cache.NewCacher(chi_cache.Options{
Adapter: adapter,
AdapterConfig: cacheConfig.Conn,
Interval: interval,
})
if err != nil {
return nil, err
}
return &stringCache{chiCache: cc}, nil
}
func (sc *stringCache) Ping() error {
return sc.chiCache.Ping()
}
func (sc *stringCache) Get(key string) (string, bool) {
v := sc.chiCache.Get(key)
if v == nil {
return "", false
}
s, ok := v.(string)
return s, ok
}
func (sc *stringCache) Put(key, value string, ttl int64) error {
return sc.chiCache.Put(key, value, ttl)
}
func (sc *stringCache) Delete(key string) error {
return sc.chiCache.Delete(key)
}
func (sc *stringCache) IsExist(key string) bool {
return sc.chiCache.IsExist(key)
}
const cachedErrorPrefix = "<CACHED-ERROR>:"
func (sc *stringCache) PutJSON(key string, v any, ttl int64) error {
var s string
switch v := v.(type) {
case error:
s = cachedErrorPrefix + v.Error()
default:
b, err := json.Marshal(v)
if err != nil {
return err
}
s = util.UnsafeBytesToString(b)
}
return sc.chiCache.Put(key, s, ttl)
}
func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) {
s, ok := sc.Get(key)
if !ok || s == "" {
return false, nil
}
s, isCachedError := strings.CutPrefix(s, cachedErrorPrefix)
if isCachedError {
return true, &GetJSONError{cachedError: s}
}
if err := json.Unmarshal(util.UnsafeStringToBytes(s), ptr); err != nil {
return false, &GetJSONError{err: err}
}
return true, nil
}
func (sc *stringCache) ChiCache() chi_cache.Cache {
return sc.chiCache
}

View File

@ -0,0 +1,21 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package container
import "slices"
// FilterSlice ranges over the slice and calls include() for each element.
// If the second returned value is true, the first returned value will be included in the resulting
// slice (after deduplication).
func FilterSlice[E any, T comparable](s []E, include func(E) (T, bool)) []T {
filtered := make([]T, 0, len(s)) // slice will be clipped before returning
seen := make(map[T]bool, len(s))
for i := range s {
if v, ok := include(s[i]); ok && !seen[v] {
filtered = append(filtered, v)
seen[v] = true
}
}
return slices.Clip(filtered)
}

View File

@ -0,0 +1,28 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package container
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilterMapUnique(t *testing.T) {
result := FilterSlice([]int{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
}, func(i int) (int, bool) {
switch i {
case 0:
return 0, true // included later
case 1:
return 0, true // duplicate of previous (should be ignored)
case 2:
return 2, false // not included
default:
return i, true
}
})
assert.Equal(t, []int{0, 3, 4, 5, 6, 7, 8, 9}, result)
}

View File

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"os"
"slices"
"strconv"
"strings"
@ -27,6 +28,7 @@ type GrepOptions struct {
MaxResultLimit int
ContextLineNumber int
IsFuzzy bool
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
}
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
@ -71,10 +73,20 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
defer stdoutReader.Close()
isInBlock := false
scanner := bufio.NewScanner(stdoutReader)
rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024))
var res *GrepResult
for scanner.Scan() {
line := scanner.Text()
for {
lineBytes, isPrefix, err := rd.ReadLine()
if isPrefix {
lineBytes = slices.Clone(lineBytes)
for isPrefix && err == nil {
_, isPrefix, err = rd.ReadLine()
}
}
if len(lineBytes) == 0 && err != nil {
break
}
line := string(lineBytes) // the memory of lineBytes is mutable
if !isInBlock {
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok {
isInBlock = true
@ -100,7 +112,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
res.LineCodes = append(res.LineCodes, lineCode)
}
}
return scanner.Err()
return nil
},
})
// git grep exits by cancel (killed), usually it is caused by the limit of results

View File

@ -41,6 +41,16 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
Filename: "java-hello/main.java",
LineNumbers: []int{3},
LineCodes: []string{" public static void main(String[] arg"},
},
}, res)
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err)
assert.Len(t, res, 0)

View File

@ -7,18 +7,11 @@ import (
"crypto/sha256"
"fmt"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// Cache represents a caching interface
type Cache interface {
// Put puts value into cache with key and expire time.
Put(key string, val any, timeout int64) error
// Get gets cached value by given key.
Get(key string) any
}
func getCacheKey(repoPath, commitID, entryPath string) string {
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
return fmt.Sprintf("last_commit:%x", hashBytes)
@ -30,11 +23,11 @@ type LastCommitCache struct {
ttl func() int64
repo *Repository
commitCache map[string]*Commit
cache Cache
cache cache.StringCache
}
// NewLastCommitCache creates a new last commit cache for repo
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache {
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache cache.StringCache) *LastCommitCache {
if cache == nil {
return nil
}
@ -65,7 +58,7 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
return nil, nil
}
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string)
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath))
if !ok || commitID == "" {
return nil, nil
}

View File

@ -709,7 +709,8 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
name += tail
image := false
switch ext := filepath.Ext(link); ext {
ext := filepath.Ext(link)
switch ext {
// fast path: empty string, ignore
case "":
// leave image as false
@ -767,11 +768,26 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
}
} else {
if !absoluteLink {
var base string
if ctx.IsWiki {
link = util.URLJoin(ctx.Links.WikiLink(), link)
switch ext {
case "":
// no file extension, create a regular wiki link
base = ctx.Links.WikiLink()
default:
// we have a file extension:
// return a regular wiki link if it's a renderable file (extension),
// raw link otherwise
if Type(link) != "" {
base = ctx.Links.WikiLink()
} else {
base = ctx.Links.WikiRawLink()
}
}
} else {
link = util.URLJoin(ctx.Links.SrcLink(), link)
base = ctx.Links.SrcLink()
}
link = util.URLJoin(base, link)
}
childNode.Type = html.TextNode
childNode.Data = name

View File

@ -427,6 +427,10 @@ func TestRender_ShortLinks(t *testing.T) {
otherImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+Other.jpg")
encodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+%23.jpg")
notencodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg")
renderableFileURL := util.URLJoin(tree, "markdown_file.md")
renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md")
unrenderableFileURL := util.URLJoin(tree, "file.zip")
unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "file.zip")
favicon := "http://google.com/favicon.ico"
test(
@ -481,6 +485,14 @@ func TestRender_ShortLinks(t *testing.T) {
"[[Link]] [[Other Link]] [[Link?]]",
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
test(
"[[markdown_file.md]]",
`<p><a href="`+renderableFileURL+`" rel="nofollow">markdown_file.md</a></p>`,
`<p><a href="`+renderableFileURLWiki+`" rel="nofollow">markdown_file.md</a></p>`)
test(
"[[file.zip]]",
`<p><a href="`+unrenderableFileURL+`" rel="nofollow">file.zip</a></p>`,
`<p><a href="`+unrenderableFileURLWiki+`" rel="nofollow">file.zip</a></p>`)
test(
"[[Link #.jpg]]",
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`,

View File

@ -653,9 +653,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/>
@ -711,9 +711,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="https://gitea.io/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="https://gitea.io/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="https://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/>
@ -769,9 +769,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
@ -829,9 +829,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
@ -889,9 +889,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
@ -951,9 +951,9 @@ space</p>
Expected: `<p>space @mention-user<br/>
/just/a/path.bin<br/>
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
<a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
<a href="https://example.com" rel="nofollow">remote link</a><br/>
<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
<a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>

View File

@ -4,6 +4,8 @@
package markdown
import (
"path/filepath"
"code.gitea.io/gitea/modules/markup"
giteautil "code.gitea.io/gitea/modules/util"
@ -18,7 +20,16 @@ func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link, r
if !isAnchorFragment && !markup.IsFullURLBytes(link) {
base := ctx.Links.Base
if ctx.IsWiki {
base = ctx.Links.WikiLink()
if filepath.Ext(string(link)) == "" {
// This link doesn't have a file extension - assume a regular wiki link
base = ctx.Links.WikiLink()
} else if markup.Type(string(link)) != "" {
// If it's a file type we can render, use a regular wiki link
base = ctx.Links.WikiLink()
} else {
// Otherwise, use a raw link instead
base = ctx.Links.WikiRawLink()
}
} else if ctx.Links.HasBranchInfo() {
base = ctx.Links.SrcLink()
}

View File

@ -65,7 +65,7 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-num$`)).OnElements("td")
policy.AllowAttrs("data-line-number").OnElements("span")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^lines-code chroma$`)).OnElements("td")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-inner$`)).OnElements("code")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^code-inner$`)).OnElements("div")
// For code preview (unicode escape)
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^file-view( unicode-escaped)?$`)).OnElements("table")

View File

@ -35,7 +35,7 @@ func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
return nil
}
func (o Option[T]) MarshalYAML() (interface{}, error) {
func (o Option[T]) MarshalYAML() (any, error) {
if !o.Has() {
return nil, nil
}

View File

@ -48,16 +48,18 @@ const maxNuspecFileSize = 3 * 1024 * 1024
// Package represents a Nuget package
type Package struct {
PackageType PackageType
ID string
Version string
Metadata *Metadata
PackageType PackageType
ID string
Version string
Metadata *Metadata
NuspecContent *bytes.Buffer
}
// Metadata represents the metadata of a Nuget package
type Metadata struct {
Description string `json:"description,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"`
Readme string `json:"readme,omitempty"`
Authors string `json:"authors,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
@ -71,6 +73,7 @@ type Dependency struct {
Version string `json:"version"`
}
// https://learn.microsoft.com/en-us/nuget/reference/nuspec
type nuspecPackage struct {
Metadata struct {
ID string `xml:"id"`
@ -80,6 +83,7 @@ type nuspecPackage struct {
ProjectURL string `xml:"projectUrl"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
Readme string `xml:"readme"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
@ -89,6 +93,11 @@ type nuspecPackage struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
Dependencies struct {
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
Group []struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct {
@ -122,16 +131,17 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
}
defer f.Close()
return ParseNuspecMetaData(f)
return ParseNuspecMetaData(archive, f)
}
}
return nil, ErrMissingNuspecFile
}
// ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
func ParseNuspecMetaData(r io.Reader) (*Package, error) {
func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
var nuspecBuf bytes.Buffer
var p nuspecPackage
if err := xml.NewDecoder(r).Decode(&p); err != nil {
if err := xml.NewDecoder(io.TeeReader(r, &nuspecBuf)).Decode(&p); err != nil {
return nil, err
}
@ -166,6 +176,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
Dependencies: make(map[string][]Dependency),
}
if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme)
if err == nil {
buf, _ := io.ReadAll(f)
m.Readme = string(buf)
_ = f.Close()
}
}
if len(p.Metadata.Dependencies.Dependency) > 0 {
deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
for _, dep := range p.Metadata.Dependencies.Dependency {
if dep.ID == "" || dep.Version == "" {
continue
}
deps = append(deps, Dependency{
ID: dep.ID,
Version: dep.Version,
})
}
m.Dependencies[""] = deps
}
for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency {
@ -182,10 +214,11 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
}
}
return &Package{
PackageType: packageType,
ID: p.Metadata.ID,
Version: toNormalizedVersion(v),
Metadata: m,
PackageType: packageType,
ID: p.Metadata.ID,
Version: toNormalizedVersion(v),
Metadata: m,
NuspecContent: &nuspecBuf,
}, nil
}

View File

@ -6,7 +6,6 @@ package nuget
import (
"archive/zip"
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -19,6 +18,7 @@ const (
projectURL = "https://gitea.io"
description = "Package Description"
releaseNotes = "Package Release Notes"
readme = "Readme"
repositoryURL = "https://gitea.io/gitea/gitea"
targetFramework = ".NETStandard2.1"
dependencyID = "System.Text.Json"
@ -36,6 +36,7 @@ const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<description>` + description + `</description>
<releaseNotes>` + releaseNotes + `</releaseNotes>
<repository url="` + repositoryURL + `" />
<readme>README.md</readme>
<dependencies>
<group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
@ -60,17 +61,19 @@ const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
</package>`
func TestParsePackageMetaData(t *testing.T) {
createArchive := func(name, content string) []byte {
createArchive := func(files map[string]string) []byte {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
w, _ := archive.Create(name)
w.Write([]byte(content))
for name, content := range files {
w, _ := archive.Create(name)
w.Write([]byte(content))
}
archive.Close()
return buf.Bytes()
}
t.Run("MissingNuspecFile", func(t *testing.T) {
data := createArchive("dummy.txt", "")
data := createArchive(map[string]string{"dummy.txt": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
data := createArchive("sub/package.nuspec", "")
data := createArchive(map[string]string{"sub/package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidNuspecFile", func(t *testing.T) {
data := createArchive("package.nuspec", "")
data := createArchive(map[string]string{"package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidPackageId", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata></metadata>
</package>`)
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) {
})
t.Run("InvalidPackageVersion", func(t *testing.T) {
data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>`+id+`</id>
<id>` + id + `</id>
</metadata>
</package>`)
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
})
t.Run("Valid", func(t *testing.T) {
data := createArchive("package.nuspec", nuspecContent)
t.Run("MissingReadme", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": nuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Empty(t, np.Metadata.Readme)
})
}
func TestParseNuspecMetaData(t *testing.T) {
t.Run("Dependency Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(nuspecContent))
data := createArchive(map[string]string{
"package.nuspec": nuspecContent,
"README.md": readme,
})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType)
@ -139,6 +146,7 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework)
@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) {
assert.Equal(t, dependencyVersion, deps[0].Version)
t.Run("NormalizedVersion", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version>
</metadata>
</package>`))
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version>
</metadata>
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
@ -162,7 +172,9 @@ func TestParseNuspecMetaData(t *testing.T) {
})
t.Run("Symbols Package", func(t *testing.T) {
np, err := ParseNuspecMetaData(strings.NewReader(symbolsNuspecContent))
data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, SymbolsPackage, np.PackageType)

View File

@ -11,6 +11,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
)
@ -32,13 +33,13 @@ const (
)
// Bool checks for a key in the map and parses as a boolean
func (g GitPushOptions) Bool(key string, def bool) bool {
func (g GitPushOptions) Bool(key string) optional.Option[bool] {
if val, ok := g[key]; ok {
if b, err := strconv.ParseBool(val); err == nil {
return b
return optional.Some(b)
}
}
return def
return optional.None[bool]()
}
// HookOptions represents the options for the Hook calls
@ -87,13 +88,17 @@ type HookProcReceiveResult struct {
// HookProcReceiveRefResult represents an individual result from ProcReceive
type HookProcReceiveRefResult struct {
OldOID string
NewOID string
Ref string
OriginalRef git.RefName
IsForcePush bool
IsNotMatched bool
Err string
OldOID string
NewOID string
Ref string
OriginalRef git.RefName
IsForcePush bool
IsNotMatched bool
Err string
IsCreatePR bool
URL string
ShouldShowMessage bool
HeadBranch string
}
// HookPreReceive check whether the provided commits are allowed

View File

@ -8,7 +8,7 @@ import (
"time"
)
const (
var (
backoffBegin = 50 * time.Millisecond
backoffUpper = 2 * time.Second
)
@ -18,6 +18,14 @@ type (
backoffFuncErr func() (retry bool, err error)
)
func mockBackoffDuration(d time.Duration) func() {
oldBegin, oldUpper := backoffBegin, backoffUpper
backoffBegin, backoffUpper = d, d
return func() {
backoffBegin, backoffUpper = oldBegin, oldUpper
}
}
func backoffRetErr[T any](ctx context.Context, begin, upper time.Duration, end <-chan time.Time, fn backoffFuncRetErr[T]) (ret T, err error) {
d := begin
for {

View File

@ -250,6 +250,7 @@ func TestWorkerPoolQueueShutdown(t *testing.T) {
func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) {
defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)()
defer mockBackoffDuration(10 * time.Millisecond)()
handler := func(items ...int) (unhandled []int) {
time.Sleep(50 * time.Millisecond)

View File

@ -6,6 +6,9 @@ package session
import (
"net/http"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"gitea.com/go-chi/session"
)
@ -18,6 +21,10 @@ type Store interface {
// RegenerateSession regenerates the underlying session and returns the new store
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
// Ensure that a cookie with a trailing slash does not take precedence over
// the cookie written by the middleware.
middleware.DeleteLegacySiteCookie(resp, setting.SessionConfig.CookieName)
s, err := session.RegenerateSession(resp, req)
return s, err
}

View File

@ -315,21 +315,25 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
}
}
// DeprecatedWarnings contains the warning message for various deprecations, including: setting option, file/folder, etc
var DeprecatedWarnings []string
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
var StartupProblems []string
func logStartupProblem(skip int, level log.Level, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
log.Log(skip+1, level, "%s", msg)
StartupProblems = append(StartupProblems, msg)
}
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
msg := fmt.Sprintf("Deprecated config option `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
log.Error("%v", msg)
DeprecatedWarnings = append(DeprecatedWarnings, msg)
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
}
}
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey)
logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
}
}

View File

@ -58,7 +58,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(Indexer.IssuePath) {
Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
}
checkOverlappedPath("indexer.ISSUE_INDEXER_PATH", Indexer.IssuePath)
checkOverlappedPath("[indexer].ISSUE_INDEXER_PATH", Indexer.IssuePath)
} else {
Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
if Indexer.IssueType == "meilisearch" {

View File

@ -22,11 +22,13 @@ const (
OAuth2UsernameNickname OAuth2UsernameType = "nickname"
// OAuth2UsernameEmail username of oauth2 email field will be used as gitea name
OAuth2UsernameEmail OAuth2UsernameType = "email"
// OAuth2UsernameEmail username of oauth2 preferred_username field will be used as gitea name
OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username"
)
func (username OAuth2UsernameType) isValid() bool {
switch username {
case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail:
case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail, OAuth2UsernamePreferredUsername:
return true
}
return false
@ -118,6 +120,10 @@ func loadOAuth2From(rootCfg ConfigProvider) {
return
}
if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" {
OAuth2.DefaultApplications = nil
}
// Handle the rename of ENABLE to ENABLED
deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0")
if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") {
@ -168,7 +174,7 @@ func GetGeneralTokenSigningSecret() []byte {
}
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) {
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
log.Warn("OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
logStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes")
return jwtSecret
}
return *generalSigningSecret.Load()

View File

@ -32,3 +32,21 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
assert.Len(t, actual, 32)
assert.EqualValues(t, expected, actual)
}
func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg)
assert.Equal(t, []string{"git-credential-oauth", "git-credential-manager", "tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = tea
`)
loadOAuth2From(cfg)
assert.Equal(t, []string{"tea"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS =
`)
loadOAuth2From(cfg)
assert.Nil(t, nil, OAuth2.DefaultApplications)
}

View File

@ -286,7 +286,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
RepoRootPath = filepath.Clean(RepoRootPath)
}
checkOverlappedPath("repository.ROOT", RepoRootPath)
checkOverlappedPath("[repository].ROOT", RepoRootPath)
defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
for _, charset := range Repository.DetectedCharsetsOrder {

View File

@ -331,7 +331,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
if !filepath.IsAbs(PprofDataPath) {
PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
}
checkOverlappedPath("server.PPROF_DATA_PATH", PprofDataPath)
checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath)
landingPage := sec.Key("LANDING_PAGE").MustString("home")
switch landingPage {

View File

@ -46,7 +46,7 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig)
checkOverlappedPath("session.PROVIDER_CONFIG", SessionConfig.ProviderConfig)
checkOverlappedPath("[session].PROVIDER_CONFIG", SessionConfig.ProviderConfig)
}
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
SessionConfig.CookiePath = AppSubURL

View File

@ -235,9 +235,7 @@ var configuredPaths = make(map[string]string)
func checkOverlappedPath(name, path string) {
// TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
if targetName, ok := configuredPaths[path]; ok && targetName != name {
msg := fmt.Sprintf("Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
log.Error("%s", msg)
DeprecatedWarnings = append(DeprecatedWarnings, msg)
logStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
}
configuredPaths[path] = name
}

View File

@ -240,7 +240,7 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
}
}
checkOverlappedPath("storage."+name+".PATH", storage.Path)
checkOverlappedPath("[storage."+name+"].PATH", storage.Path)
return &storage, nil
}

View File

@ -0,0 +1,10 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package structs
// Compare represents a comparison between two commits.
type Compare struct {
TotalCommits int `json:"total_commits"` // Total number of commits in the comparison.
Commits []*Commit `json:"commits"` // List of commits in the comparison.
}

View File

@ -20,6 +20,8 @@ type User struct {
// the user's authentication sign-in name.
// default: empty
LoginName string `json:"login_name"`
// The ID of the user's Authentication Source
SourceID int64 `json:"source_id"`
// the user's full name
FullName string `json:"full_name"`
// swagger:strfmt email

View File

@ -34,6 +34,7 @@ func NewFuncMap() template.FuncMap {
// -----------------------------------------------------------------
// html/template related functions
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
"Iif": Iif,
"Eval": Eval,
"SafeHTML": SafeHTML,
"HTMLFormat": HTMLFormat,
@ -53,13 +54,13 @@ func NewFuncMap() template.FuncMap {
"JsonUtils": NewJsonUtils,
// -----------------------------------------------------------------
// svg / avatar / icon
// svg / avatar / icon / color
"svg": svg.RenderHTML,
"EntryIcon": base.EntryIcon,
"MigrationIcon": MigrationIcon,
"ActionIcon": ActionIcon,
"SortArrow": SortArrow,
"SortArrow": SortArrow,
"ContrastColor": util.ContrastColor,
// -----------------------------------------------------------------
// time / number / format
@ -238,6 +239,17 @@ func DotEscape(raw string) string {
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
}
// Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
// and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
func Iif(condition bool, vals ...any) any {
if condition {
return vals[0]
} else if len(vals) > 1 {
return vals[1]
}
return nil
}
// Eval the expression and return the result, see the comment of eval.Expr for details.
// To use this helper function in templates, pass each token as a separate parameter.
//

View File

@ -142,35 +142,39 @@ type remoteAddress struct {
Password string
}
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress {
a := remoteAddress{}
remoteURL := m.OriginalURL
if ignoreOriginalURL || remoteURL == "" {
var err error
remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return a
}
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return ret
}
u, err := giturl.Parse(remoteURL)
if err != nil {
log.Error("giturl.Parse %v", err)
return a
return ret
}
if u.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil {
a.Username = u.User.Username()
a.Password, _ = u.User.Password()
ret.Username = u.User.Username()
ret.Password, _ = u.User.Password()
}
u.User = nil
}
a.Address = u.String()
return a
// The URL stored in the git repo could contain authentication,
// erase it, or it will be shown in the UI.
u.User = nil
ret.Address = u.String()
// Why not use m.OriginalURL to set ret.Address?
// It should be OK to use it, since m.OriginalURL should be the same as the authentication-erased URL from the Git repository.
// However, the old code has already stored authentication in m.OriginalURL when updating mirror settings.
// That means we need to use "giturl.Parse" for m.OriginalURL again to ensure authentication is erased.
// Instead of doing this, why not directly use the authentication-erased URL from the Git repository?
// It should be the same as long as there are no bugs.
return ret
}
func FilenameIsImage(filename string) bool {

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