diff options
112 files changed, 5001 insertions, 2394 deletions
diff --git a/.gitignore b/.gitignore index 272ea2b5ed..0791a17c71 100644 --- a/.gitignore +++ b/.gitignore @@ -42,14 +42,10 @@ _testmain.go coverage.all cpu.out -/modules/migration/bindata.go -/modules/migration/bindata.go.hash -/modules/options/bindata.go -/modules/options/bindata.go.hash -/modules/public/bindata.go -/modules/public/bindata.go.hash -/modules/templates/bindata.go -/modules/templates/bindata.go.hash +/modules/migration/bindata.* +/modules/options/bindata.* +/modules/public/bindata.* +/modules/templates/bindata.* *.db *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index ca2e67929c..b72ac4849a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,429 @@ This changelog goes through the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.com). +## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26 + +* BREAKING + * Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076) + * Improve log format (#33814) + * Fix markdown render behaviors (#34122) + * Add package version api endpoints (#34173) + +* FEATURES + * Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187) + * Add fullscreen mode as a more efficient operation way to view projects (#34081) + * Add anonymous access support for private/unlisted repositories (#34051) + * Support public code/issue access for private repositories (#33127) + * Add middleware for request prioritization (#33951) + * Add cli flags LDAP group configuration (#33933) + * Add file tree to file view page (#32721) + * Add material icons for file list (#33837) + * Artifacts download api for artifact actions v4 (#33510) + * Support choose email when creating a commit via web UI (#33432) + * Add basic auth support to rss/atom feeds (#33371) + * Add sorting by exclusive labels (issue priority) (#33206) + * Add sub issue list support (#32940) + * Private README.md for organization (#32872) + * Email option to embed images as base64 instead of link (#32061) + * Option to delay conflict checking of old pull requests until page view (#27779) + * Worktime tracking for the organization level (#19808) + +* PERFORMANCE + * Add cache for common package queries (#22491) + * Move issue pin to an standalone table for querying performance (#33452) + * Improve commits list performance to reduce unnecessary database queries (#33528) + * Optimize total count of feed when loading activities in user dashboard. (#33841) + * Optimize heatmap query (#33853) + * Only use prev and next buttons for pagination on user dashboard (#33981) + * Improve pull request list API performance (#34052) + * Cache GPG keys, emails and users when list commits (#34086) + * Refactor Git Attribute & performance optimization (#34154) + * Performance optimization for tags synchronization (#34355) #34522 + +* ENHANCEMENTS + * Code + * Display when a release attachment was uploaded (#34261) + * Support creating relative link to raw path in markdown (#34105) + * Improve code block readability and isolate copy button (#34009) + * Improve repository commit view (#33877) + * Full-file syntax highlighting for diff pages (#33766) + * Clone repository with Tea CLI (#33725) + * Improve sync fork behavior (#33319) + * Make git clone URL could use current signed-in user (#33091) + * Add submodule diff links (#33097) + * Link to tree views of submodules if possible (#33424) + * Only keep popular licenses (#33832) + * De-emphasize signed commits (#31160) + + * Actions + * Add flat-square action badge style (#34062) + * Update action status badge layout (#34018) + * Download actions job logs from API (#33858) + * Always show the "rerun" button for action jobs (#33692) + * Add auto-expanding running actions step (#30058) + * Update status check for all supported on.pull_request.types in Gitea (#33117) + * Workflow_dispatch use workflow from trigger branch (#33098) + * Add action auto-scroll (#30057) + * Add workflow_job webhook (#33694) + * Add a button editing action secret (#34462) + + * Pull Request + * Auto expand "New PR" form (#33971) + * Mark parent directory as viewed when all files are viewed (#33958) + * Show info about maintainers are allowed to edit a PR (#33738) + * Automerge supports deleting branch automatically after merging (#32343) + * Add additional command hints for PowerShell & CMD (#33548) + + * Issues + * Allow filtering issues by any assignee (#33343) + * Show warning on navigation if currently editing comment or title (#32920) + * Make tracked time representation display as hours (#33315) + * Add No Results Prompt Message on Issue List Page (#33699) + * Add sort option recentclose for issues and pulls (#34525) #34539 + + * Packages + * Link to nuget dependencies (#26554) + * Add composor source field (#33502) + + * Administration + * Improve navbar: add "admin" tip, add "active" style (#32927) + * Add a option "--user-type bot" to admin user create, improve role display (#27885) + * Improve admin user view page (#33735) + * Support performance trace (#32973) + * Change pprof labels to be prometheus compatible (#32865) + * Allow admins and org owners to change org member public status (#28294) + * Optimize the installation page (#32994) + * Make public URL generation configurable (#34250) + * Add a --fullname arg to gitea admin user create. (#34241) + + * Others + * Improve oauth2 error handling (#33969) + * Fail mirroring more gracefully (#34002) + * Align User Details Page Header Layout with Design Specifications (#34192) + * Webhook add X-Gitea-Hook-Installation-Target-Type Header (#33752) + * Optimize the dashboard (#32990) + * Improve button layout on small screens (#33633) + * Add cropping support when modifying the user/org/repo avatar (#33498) + * Make ROOT_URL support using request Host header (#32564) + * Add `show more` organizations icon in user's profile (#32986) + * Introduce `--page-space-bottom` at 64px (#30692) + * Improve theme display (#30671) + * Add alphabetical project sorting (#33504) + * Add global lock for migrations to make upgrade more safe with multiple replications (#33706) + * Add descriptions for private repo public access settings and improve the UI (#34057) + +* API + * Actions Runner rest api (#33873) + * Inclusion of rename organization api (#33303) + * Add API to support link package to repository and unlink it (#33481) + * Add API endpoint to request contents of multiple files simultaniously (#34139) + * Actions artifacts API list/download check status upload confirmed (#34273) + * Add API routes to lock and unlock issues (#34165) + * Fix some user name usages (#33689) + * Allow filtering /repos/{owner}/{repo}/pulls by target base branch queryparam (#33684) + * Improve swagger generation (#33664) + * Support Ephemeral action runners (#33570) + * Support workflow event dispatch via API (#33545) + * Support workflow event dispatch via API (#32059) + * Added Description Field for Secrets and Variables (#33526) + * Reject star-related requests if stars are disabled (#33208) + * Let API create and edit system webhooks, attempt 2 (#33180) + * Use `Project-URL` metadata field to get a PyPI package's homepage URL (#33089) + * Add `last_committer_date` and `last_author_date` for file contents API (#32921) + +* REFACTORS + * Remove context from git struct (#33793) + * Refactor admin/common.ts (#33788) + * Refactor repo-settings.ts (#33785) + * Refactor repo-issue.ts (#33784) + * Small refactor to reduce unnecessary database queries and remove duplicated functions (#33779) + * Refactor initRepoBranchTagSelector to use new init framework (#33776) + * Refactor buttons to use new init framework (#33774) + * Refactor markup and pdf-viewer to use new init framework (#33772) + * Refactor error system (#33771) + * Refactor mail code (#33768) + * Update TypeScript types (#33799) + * Refactor older tests to use testify (#33140) + * Move notifywatch to service layer (#33825) + * Decouple context from repository related structs (#33823) + * Remove context from mail struct (#33811) + * Refactor dropdown ellipsis (#34123) + * Refactor functions to reduce repopath expose (#33892) + * Refactor repo-diff.ts (#33746) + * Refactor web route handler (#33488) + * Refactor user & avatar (#33433) + * Refactor user package (#33423) + * Refactor decouple context from migration structs (#33399) + * Refactor context flash msg and global variables (#33375) + * Refactor response writer & access logger (#33323) + * Refactor ref type (#33242) + * Refactor context repository (#33202) + * Refactor legacy JS (#33115) + * Refactor legacy line-number and scroll code (#33094) + * Refactor env var related code (#33075) + * Move SetMerged to service layer (#33045) + * Merge updatecommentattachment functions (#33044) + * Refactor pull-request compare&create page (#33071) + * Refactor repo-new.ts (#33070) + * Refactor pagination (#33037) + * Refactor tests (#33021) + * Refactor markup render to fix various path problems (#34114) + * Refactor Branch struct in package modules/git (#33980) + * Don't create duplicated functions for code repositories and wiki repositories (#33924) + * Move git references checking to gitrepo packages to reduce expose of repository path (#33891) + * Refactor cache-control (#33861) + * Decouple diff stats query from actual diffing (#33810) + * Move part of updating protected branch logic to service layer (#33742) + * Decouple Batch from git.Repository to simplify usage without requiring the creation of a Repository struct. (#34001) + * Refactor tmpl and blob_excerpt (#32967) + * Refactor template & test related code (#32938) + * Refactor db package and remove unnecessary `DumpTables` (#32930) + * Refactor pprof labels and process desc (#32909) + * Refactor repo-projects.ts (#32892) + * Refactor getpatch/getdiff functions and remove unnecessary fallback (#32817) + * Uniform all temporary directories and allow customizing temp path (#32352) + * Remove context from retry downloader (#33871) + * Refactor global init code and add more comments (#33755) + * Remove some unnecessary template helpers (#33069) + * Move and rename UpdateRepository (#34136) + * Move hooks function to gitrepo and reduce expose repopath (#33890) + * Add abstraction layer to delete repository from disk (#33879) + * Add abstraction layer to check if the repository exists on disk (#33874) + * Move ParseCommitWithSSHSignature to service layer (#34087) + * Move duplicated functions (#33977) + * Extract code to their own functions for push update (#33944) + * Move gitgraph from modules to services layer (#33527) + * Move commits signature and verify functions to service layers (#33605) + * Use `CloseIssue` and `ReopenIssue` instead of `ChangeStatus` (#32467) + * Refactor arch route handlers (#32993) + * Refactor "string truncate" (#32984) + * Refactor arch route handlers (#32972) + * Clarify path param naming (#32969) + * Refactor request context (#32956) + * Move some errors to their own sub packages (#32880) + * Move RepoTransfer from models to models/repo sub package (#32506) + * Move delete deploy keys into service layer (#32201) + * Refactor webhook events (#33337) + * Move some Actions related functions from `routers` to `services` (#33280) + * Refactor RefName (#33234) + * Refactor context RefName and RepoAssignment (#33226) + * Refactor repository transfer (#33211) + * Refactor error system (#33626) + * Refactor error system (#33610) + * Refactor package (routes and error handling, npm peer dependency) (#33111) + * Use test context in tests and new loop system in benchmarks (#33648) + * Some small refactors (#33144) + * Simplify context ref name (#33267) + +* BUGFIXES + * Fix some dropdown problems on the issue sidebar (#34308) #34327 + * Do not return archive download URLs in API if downloads are disabled (#34324) #34338 + * Fix LFS files being editable in web UI (#34356) #34362 + * Fix only text/* being viewable in web UI (#34374) #34378 + * Fix LFS file not stored in LFS when uploaded/edited via API or web UI (#34367) + * Grey out expired artifact on Artifacts list (#34314) #34404 + * Fix incorrect divergence cache after switching default branch (#34370) #34406 + * Refactor commit message rendering and fix bugs (#34412) #34414 + * Merge and tweak markup editor expander CSS (#34409) #34415 + * Fix GetUsersByEmails (#34423) #34425 + * Only git operations should update last changed of a repository (#34388) #34427 + * Fix comment textarea scroll issue in Firefox (#34438) #34446 + * Fix repo broken check (#34444) #34452 + * Fix remove org user failure on mssql (#34449) #34453 + * Fix Workflow run Not Found page (#34459) #34466 + * When updating comment, if the content is the same, just return and not update the database (#34422) #34464 + * Fix project board view (#34470) #34475 + * Fix get / delete runner to use consistent http 404 and 500 status (#34480) #34488 + * Fix url validation in webhook add/edit API (#34492) #34496 + * Fix edithook api can not update package, status and workflow_job events (#34495) #34499 + * Fix ephemeral runner deletion (#34447) #34513 + * Don't display error log when .git-blame-ignore-revs doesn't exist (#34457) + * Only allow admins to rename default/protected branches (#33276) + * Improve "lock conversation" UI (#34207) + * Fix incorrect file links (#34189) + * Optimize Overflow Menu (#34183) + * Check user/org repo limit instead of doer (#34147) + * Make markdown render match GitHub's behavior (#34129) + * Fix team permission (#34128) + * Correctly handle submodule view and avoid throwing 500 error (#34121) + * Fix users being able bypass limits with repo transfers (#34031) + * Avoid creating unnecessary temporary cat file sub process (#33942) + * Refactor organization menu (#33928) + * Fix various Fomantic UI and htmx problems (#33851) + * Fix 500 error when error occurred in migration page (#33256) + * Validate that the tag doesn't exist when creating a tag via the web (#33241) + * Add missed transaction on setmerged (#33079) + * Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035) + * Valid email address should only start with alphanumeric (#28174) + * Fix webhook url (#34186) + * Fix "toAbsoluteLocaleDate" test when system locale is not en-US (#33939) + * Fix file name could not be searched if the file was not a text file when using the Bleve indexer (#33959) + * Fix cannot delete runners via the modal dialog (#33895) + * Fix unpin hint on the pinned pull requests (#33207) + * Fix parentCommit invalid memory address or nil pointer dereference. (#33204) + * Fix comment header padding (#33377) + * Fix some migration and repo name problems (#33986) + * Fix various trivial frontend problems (#34263) + * Fix Set Email Preference dropdown and button placement (#34255) + * Fix quoted replies incorrectly render user input as part of the quote (#34216) + * Fix button alignments and remove unnecessary styles (#34206) + * Restore form inputs on organization create error (#34201) + * Try to fix ACME (3rd) (#33807) + * Fix incorrect ref "blob" (#33240) + * Fix dynamic content loading init problem (#33748) + * Fix git empty check and HEAD request (#33690) + * Fix Untranslated Text on Actions Page (#33635) + * Fix issue label delete incorrect labels webhook payload (#34575) + * Fix incorrect page navigation with up and down arrow on last item of dashboard repos (#34570) + * Fix/improve avatar sync from LDAP (#34573) + * Fix some trivial problems (#34579) + * Retain issue sort type when a keyword search is introduced (#34559) + * Always use an empty line to separate the commit message and trailer (#34512) + * Fix line-button issue after file selection in file tree (#34574) + * Fix doctor deleting orphaned issues attachments (#34142) + * Add webhook assigning test and fix possible bug (#34420) + * Fix possible nil description of pull request when migrating from CodeCommit (#34541) + * Refactor commit reader (#34542) + * Fix possible pull request broken when leave the page immediately after clicking the update button #34509 + * Ignore "Close" error when uploading container blob (#34620) + * Fix missed merge commit sha and time when migrating from codecommit (#34645) + * Fix GetUsersByEmails (#34643) + * Misc CSS fixes (#34638) + * Add codecommit to supported services in api docs (#34626) + * Validate hex colors when creating/editing labels (#34623) + * Fix possible pull request broken when leave the page immediately after clicking the update button (#34509) + * Fix margin issue in markup paragraph rendering (#34599) + * Fix migration pull request title too long (#34577) + * Fix footnote jump behavior on the issue page. (#34621) + * Fix "oras" OCI client compatibility (#34666) + * Fix last admin check when syncing users (#34649) + * Fix skip paths check on tag push events in workflows (#34602) #34670 + +* MISC + + * Bump to alpine 3.22 (#34613) + * Make pull request and issue history more compact (#34588) + * Run integration tests against postgres 14 (#34514) #34536 + * Enable addtional linters (#34085) + * Enable testifylint rules (#34075) + * Enable staticcheck QFxxxx rules (#34064) + * Improve Actions test (#32883) + * Drop fomantic build (#33845) + * Go1.24 (#33562) + * Run yamllint with strict mode, fix issue (#33551) + * Disable cron task to update license (#33486) + * Optimize makefile help information generation (#33390) + * Convert github.com/xanzy/go-gitlab into gitlab.com/gitlab-org/api/client-go (#33126) + * Add missed changelogs (#33649) + * Update .changelog file to add performance label group (#33472) + * Add missing POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES in app.example.ini (#33363) + * Update README screenshots (#33347) + * Update unrs-resolver (#34279) + * Update go&js dependencies (#34262) + * Optimize the calling code of queryElems (#34235) + * Update protected_branch.tmpl (#34193) + * Feat/optimize span svg layout (#34185) + * Set MERMAID_MAX_SOURCE_CHARACTERS to 50000 (#34152) + * Update JS and PY deps (#34143) + * Add Chinese translations for README files (#34132) + * Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126) + * Clarify ownership in password change error messages (#34092) + * Add toggleClass function in dom.ts (#34063) + * Update to golangci-lint v2 (#34054) + * Update Makefile test comments (#34013) + * Update go mod dependencies (#33988) + * Use filepath.Join instead of path.Join for file system file operations (#33978) + * Prepare common tmpl functions in a middleware (#33957) + * Remove unused or abused styles (#33918) + * Update JS and PY deps, misc tweaks (#33903) + * Try to figure out attribute checker problem (#33901) + * Add lock for a repository pull mirror (#33876) + * Fine tune push mirror UI (#33866) + * Improve issue & code search (#33860) + * Use pullrequestlist instead of []*pullrequest (#33765) + * Upgrade act to 0.261.4 and actions-proto-go to v0.4.1 (#33760) + * Align sidebar gears to the right (#33721) + * Update Go dependencies (skip blevesearch, meilisearch) (#33655) + * Add migrations and doctor fixes (#33556) + * Remove "class-name" from svg icon (#33540) + * Update MAINTAINERS (#33529) + * Add "No data available" display when list is empty (#33517) + * Use `git diff-tree` for `DiffFileTree` on diff pages (#33514) + * Give organisation members access to organisation feeds (#33508) + * Update feishu icon (#33470) + * Hide/disable unusable UI elements when a repository is archived (#33459) + * Update `@github/text-expander-element` to 2.9.0 (#33435) + * Do not access GitRepo when a repo is being created (#33380) + * Fix incorrect ref usages (#33301) + * Prepare for support performance trace (#33286) + * Enable Typescript `noImplicitThis` (#33250) + * Remove unused CSS styles and move some styles to proper files (#33217) + * Add .run to gitignore (#33175) + * Fix typo in gitea downloader test and add missing codebase in `ToGitServiceType` (#33146) + * Remove extended glob pattern from branch protection UI (#33125) + * Clean up legacy form CSS styles (#33081) + * Unset XDG_HOME_CONFIG as gitea manages configuration locations (#33067) + * Add IntelliJ Gateway's .uuid to gitignore (#33052) + * User facing messages for AGit errors (#33012) + * Always show assignees on right (#33006) + * Fix eslint (#33002) + * Update JS dependencies (#32914) + * Bump x/net (#32896) (#32900) + * Only activity tab needs heatmap data loading (#34652) + +## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11 + +* SECURITY + * Fix a bug when uploading file via lfs ssh command (#34408) (#34411) + * Update net package (#34228) (#34232) +* BUGFIXES + * Fix releases sidebar navigation link (#34436) #34439 + * Fix bug webhook milestone is not right. (#34419) #34429 + * Fix two missed null value checks on the wiki page. (#34205) (#34215) + * Swift files can be passed either as file or as form value (#34068) (#34236) + * Fix bug when API get pull changed files for deleted head repository (#34333) (#34368) + * Upgrade github v61 -> v71 to fix migrating bug (#34389) + * Fix bug when visiting comparation page (#34334) (#34364) + * Fix wrong review requests when updating the pull request (#34286) (#34304) + * Fix github migration error when using multiple tokens (#34144) (#34302) + * Explicitly not update indexes when sync database schemas (#34281) (#34295) + * Fix panic when comment is nil (#34257) (#34277) + * Fix project board links to related Pull Requests (#34213) (#34222) + * Don't assume the default wiki branch is master in the wiki API (#34244) (#34245) +* DOCUMENTATION + * Update token creation API swagger documentation (#34288) (#34296) +* MISC + * Fix CI Build (#34315) + * Add riscv64 support (#34199) (#34204) + * Bump go version in go.mod (#34160) + * remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158) + +## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07 + +* Enhancements + * Add a config option to block "expensive" pages for anonymous users (#34024) (#34071) + * Also check default ssh-cert location for host (#34099) (#34100) (#34116) +* BUGFIXES + * Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124) + * Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120) + * Fix invalid version in RPM package path (#34112) (#34115) + * Return default avatar url when user id is zero rather than updating database (#34094) (#34095) + * Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070) + * Try to fix check-attr bug (#34029) (#34033) + * Git client will follow 301 but 307 (#34005) (#34010) + * Fix block expensive for 1.23 (#34127) + * Fix markdown frontmatter rendering (#34102) (#34107) + * Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103) + * Do not show 500 error when default branch doesn't exist (#34096) (#34097) + * Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065) + * Simplify emoji rendering (#34048) (#34049) + * Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047) + * Pull request updates will also trigger code owners review requests (#33744) (#34045) + * Fix org repo creation being limited by user limits (#34030) (#34044) + * Fix git client accessing renamed repo (#34034) (#34043) + * Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036) + * Polyfill WeakRef (#34025) (#34028) + ## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24 * SECURITY @@ -120,8 +120,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts -BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go -BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST)) +BINDATA_DEST := modules/public/bindata.dat modules/options/bindata.dat modules/templates/bindata.dat GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go @@ -149,14 +148,8 @@ SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini GO_SOURCES := $(wildcard *.go) -GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) +GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go") GO_SOURCES += $(GENERATED_GO_DEST) -GO_SOURCES_NO_BINDATA := $(GO_SOURCES) - -ifeq ($(filter $(TAGS_SPLIT),bindata),bindata) - GO_SOURCES += $(BINDATA_DEST) - GENERATED_GO_DEST += $(BINDATA_DEST) -endif # Force installation of playwright dependencies by setting this flag ifdef DEPS_PLAYWRIGHT @@ -226,7 +219,7 @@ clean-all: clean ## delete backend, frontend and integration files .PHONY: clean clean: ## delete backend and integration files - rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \ + rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) \ integrations*.test \ e2e*.test \ tests/integration/gitea-integration-* \ @@ -268,7 +261,7 @@ endif .PHONY: generate-swagger generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments -$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT) +$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT) $(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)' .PHONY: swagger-check @@ -373,7 +366,7 @@ lint-go-gitea-vet: ## lint go files with gitea-vet .PHONY: lint-go-gopls lint-go-gopls: ## lint go files with gopls @echo "Running gopls check..." - @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA) + @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES) .PHONY: lint-editorconfig lint-editorconfig: diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 3827a092f1..d961444239 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -1080,9 +1080,14 @@ "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License." }, { - "name": "github.com/urfave/cli/v2", - "path": "github.com/urfave/cli/v2/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject 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,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + "name": "github.com/urfave/cli-docs/v3", + "path": "github.com/urfave/cli-docs/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject 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,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + }, + { + "name": "github.com/urfave/cli/v3", + "path": "github.com/urfave/cli/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject 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,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/valyala/fastjson", @@ -1110,11 +1115,6 @@ "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" }, { - "name": "github.com/xrash/smetrics", - "path": "github.com/xrash/smetrics/LICENSE", - "licenseText": "Copyright (C) 2016 Felipe da Cunha Gonçalves\nAll Rights Reserved.\n\nMIT LICENSE\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" - }, - { "name": "github.com/yohcop/openid-go", "path": "github.com/yohcop/openid-go/LICENSE", "licenseText": "Copyright 2015 Yohann Coppel\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n" @@ -5,19 +5,10 @@ package main -// Libraries that are included to vendor utilities used during build. +// Libraries that are included to vendor utilities used during Makefile build. // These libraries will not be included in a normal compilation. import ( - // for embed - _ "github.com/shurcooL/vfsgen" - - // for cover merge - _ "golang.org/x/tools/cover" - // for vet _ "code.gitea.io/gitea-vet" - - // for swagger - _ "github.com/go-swagger/go-swagger/cmd/swagger" ) diff --git a/build/generate-bindata.go b/build/generate-bindata.go index 2fcb7c2f2a..2553770762 100644 --- a/build/generate-bindata.go +++ b/build/generate-bindata.go @@ -6,87 +6,22 @@ package main import ( - "bytes" - "crypto/sha1" "fmt" - "log" - "net/http" "os" - "path/filepath" - "strconv" - "github.com/shurcooL/vfsgen" + "code.gitea.io/gitea/modules/assetfs" ) -func needsUpdate(dir, filename string) (bool, []byte) { - needRegen := false - _, err := os.Stat(filename) - if err != nil { - needRegen = true - } - - oldHash, err := os.ReadFile(filename + ".hash") - if err != nil { - oldHash = []byte{} - } - - hasher := sha1.New() - - err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - info, err := d.Info() - if err != nil { - return err - } - _, _ = hasher.Write([]byte(d.Name())) - _, _ = hasher.Write([]byte(info.ModTime().String())) - _, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16))) - return nil - }) - if err != nil { - return true, oldHash - } - - newHash := hasher.Sum([]byte{}) - - if bytes.Compare(oldHash, newHash) != 0 { - return true, newHash - } - - return needRegen, newHash -} - func main() { - if len(os.Args) < 4 { - log.Fatal("Insufficient number of arguments. Need: directory packageName filename") - } - - dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3] - var useGlobalModTime bool - if len(os.Args) == 5 { - useGlobalModTime, _ = strconv.ParseBool(os.Args[4]) - } - - update, newHash := needsUpdate(dir, filename) - - if !update { - fmt.Printf("bindata for %s already up-to-date\n", packageName) - return + if len(os.Args) != 3 { + fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}") + os.Exit(1) } - fmt.Printf("generating bindata for %s\n", packageName) - var fsTemplates http.FileSystem = http.Dir(dir) - err := vfsgen.Generate(fsTemplates, vfsgen.Options{ - PackageName: packageName, - BuildTags: "bindata", - VariableName: "Assets", - Filename: filename, - UseGlobalModTime: useGlobalModTime, - }) - if err != nil { - log.Fatalf("%v\n", err) + dir, filename := os.Args[1], os.Args[2] + fmt.Printf("generating bindata for %s to %s\n", dir, filename) + if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil { + fmt.Printf("failed: %s\n", err.Error()) + os.Exit(1) } - _ = os.WriteFile(filename+".hash", newHash, 0o666) } diff --git a/cmd/actions.go b/cmd/actions.go index f582c16c81..2c51c6a1bc 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -17,7 +18,7 @@ var ( CmdActions = &cli.Command{ Name: "actions", Usage: "Manage Gitea Actions", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdActionsGenRunnerToken, }, } @@ -38,10 +39,7 @@ var ( } ) -func runGenerateActionsRunnerToken(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { setting.MustInstalled() scope := c.String("scope") diff --git a/cmd/admin.go b/cmd/admin.go index 7f2d985169..559544edd3 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -15,7 +15,7 @@ import ( "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -23,7 +23,7 @@ var ( CmdAdmin = &cli.Command{ Name: "admin", Usage: "Perform common administrative operations", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdUser, subcmdRepoSyncReleases, subcmdRegenerate, @@ -41,7 +41,7 @@ var ( subcmdRegenerate = &cli.Command{ Name: "regenerate", Usage: "Regenerate specific files", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdRegenHooks, microcmdRegenKeys, }, @@ -50,15 +50,15 @@ var ( subcmdAuth = &cli.Command{ Name: "auth", Usage: "Modify external auth providers", - Subcommands: []*cli.Command{ - microcmdAuthAddOauth, - microcmdAuthUpdateOauth, - microcmdAuthAddLdapBindDn, - microcmdAuthUpdateLdapBindDn, - microcmdAuthAddLdapSimpleAuth, - microcmdAuthUpdateLdapSimpleAuth, - microcmdAuthAddSMTP, - microcmdAuthUpdateSMTP, + Commands: []*cli.Command{ + microcmdAuthAddOauth(), + microcmdAuthUpdateOauth(), + microcmdAuthAddLdapBindDn(), + microcmdAuthUpdateLdapBindDn(), + microcmdAuthAddLdapSimpleAuth(), + microcmdAuthUpdateLdapSimpleAuth(), + microcmdAuthAddSMTP(), + microcmdAuthUpdateSMTP(), microcmdAuthList, microcmdAuthDelete, }, @@ -70,9 +70,9 @@ var ( Action: runSendMail, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "title", - Usage: `a title of a message`, - Value: "", + Name: "title", + Usage: "a title of a message", + Required: true, }, &cli.StringFlag{ Name: "content", @@ -86,17 +86,16 @@ var ( }, }, } +) - idFlag = &cli.Int64Flag{ +func idFlag() *cli.Int64Flag { + return &cli.Int64Flag{ Name: "id", Usage: "ID of authentication source", } -) - -func runRepoSyncReleases(_ *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() +} +func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 4777a92908..1a09366722 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -13,14 +14,14 @@ import ( "code.gitea.io/gitea/models/db" auth_service "code.gitea.io/gitea/services/auth" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( microcmdAuthDelete = &cli.Command{ Name: "delete", Usage: "Delete specific auth source", - Flags: []cli.Flag{idFlag}, + Flags: []cli.Flag{idFlag()}, Action: runDeleteAuth, } microcmdAuthList = &cli.Command{ @@ -56,10 +57,7 @@ var ( } ) -func runListAuth(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runListAuth(ctx context.Context, c *cli.Command) error { if err := initDB(ctx); err != nil { return err } @@ -90,14 +88,11 @@ func runListAuth(c *cli.Context) error { return nil } -func runDeleteAuth(c *cli.Context) error { +func runDeleteAuth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index d2eeb7c0d6..069ad6600c 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/ldap" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type ( @@ -24,8 +24,8 @@ type ( } ) -var ( - commonLdapCLIFlags = []cli.Flag{ +func commonLdapCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Usage: "Authentication name.", @@ -103,8 +103,10 @@ var ( Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", }, } +} - ldapBindDnCLIFlags = append(commonLdapCLIFlags, +func ldapBindDnCLIFlags() []cli.Flag { + return append(commonLdapCLIFlags(), &cli.StringFlag{ Name: "bind-dn", Usage: "The DN to bind to the LDAP server with when searching for the user.", @@ -157,49 +159,59 @@ var ( Name: "group-team-map-removal", Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group", }) +} - ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, +func ldapSimpleAuthCLIFlags() []cli.Flag { + return append(commonLdapCLIFlags(), &cli.StringFlag{ Name: "user-dn", Usage: "The user's DN.", }) +} - microcmdAuthAddLdapBindDn = &cli.Command{ +func microcmdAuthAddLdapBindDn() *cli.Command { + return &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapBindDn(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().addLdapBindDn(ctx, cmd) }, - Flags: ldapBindDnCLIFlags, + Flags: ldapBindDnCLIFlags(), } +} - microcmdAuthUpdateLdapBindDn = &cli.Command{ +func microcmdAuthUpdateLdapBindDn() *cli.Command { + return &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapBindDn(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().updateLdapBindDn(ctx, cmd) }, - Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), + Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...), } +} - microcmdAuthAddLdapSimpleAuth = &cli.Command{ +func microcmdAuthAddLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapSimpleAuth(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().addLdapSimpleAuth(ctx, cmd) }, - Flags: ldapSimpleAuthCLIFlags, + Flags: ldapSimpleAuthCLIFlags(), } +} - microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ +func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapSimpleAuth(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().updateLdapSimpleAuth(ctx, cmd) }, - Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), + Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...), } -) +} // newAuthService creates a service with default functions. func newAuthService() *authService { @@ -212,7 +224,7 @@ func newAuthService() *authService { } // parseAuthSourceLdap assigns values on authSource according to command line flags. -func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) { +func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) { if c.IsSet("name") { authSource.Name = c.String("name") } @@ -232,7 +244,7 @@ func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) { } // parseLdapConfig assigns values on config according to command line flags. -func parseLdapConfig(c *cli.Context, config *ldap.Source) error { +func parseLdapConfig(c *cli.Command, config *ldap.Source) error { if c.IsSet("name") { config.Name = c.String("name") } @@ -245,7 +257,7 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error { if c.IsSet("security-protocol") { p, ok := findLdapSecurityProtocolByName(c.String("security-protocol")) if !ok { - return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol")) + return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol")) } config.SecurityProtocol = p } @@ -337,32 +349,27 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getAuthSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. -func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) { +func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { if err := argsSet(c, "id"); err != nil { return nil, err } - authSource, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return nil, err } if authSource.Type != authType { - return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) + return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) } return authSource, nil } // addLdapBindDn adds a new LDAP via Bind DN authentication source. -func (a *authService) addLdapBindDn(c *cli.Context) error { +func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { return err } - - ctx, cancel := installSignals() - defer cancel() - if err := a.initDB(ctx); err != nil { return err } @@ -384,10 +391,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. -func (a *authService) updateLdapBindDn(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -406,14 +410,11 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. -func (a *authService) addLdapSimpleAuth(c *cli.Context) error { +func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { return err } - ctx, cancel := installSignals() - defer cancel() - if err := a.initDB(ctx); err != nil { return err } @@ -435,10 +436,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { } // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. -func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 52ab78fe13..2da7ebc573 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/services/auth/source/ldap" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestAddLdapBindDn(t *testing.T) { @@ -134,7 +134,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--email-attribute", "mail", }, - errMsg: "Unknown security protocol name: zzzzz", + errMsg: "unknown security protocol name: zzzzz", }, // case 3 { @@ -238,12 +238,13 @@ func TestAddLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapBindDn.Flags - app.Action = service.addLdapBindDn + app := cli.Command{ + Flags: microcmdAuthAddLdapBindDn().Flags, + Action: service.addLdapBindDn, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -345,12 +346,12 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--name", "ldap (simple auth) source", "--security-protocol", "zzzzz", "--host", "ldap-server", - "--port", "123", + "--port", "1234", "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "Unknown security protocol name: zzzzz", + errMsg: "unknown security protocol name: zzzzz", }, // case 3 { @@ -467,12 +468,13 @@ func TestAddLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapSimpleAuth.Flags - app.Action = service.addLdapSimpleAuth + app := &cli.Command{ + Flags: microcmdAuthAddLdapSimpleAuth().Flags, + Action: service.addLdapSimpleAuth, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -859,7 +861,7 @@ func TestUpdateLdapBindDn(t *testing.T) { "--id", "1", "--security-protocol", "xxxxx", }, - errMsg: "Unknown security protocol name: xxxxx", + errMsg: "unknown security protocol name: xxxxx", }, // case 22 { @@ -878,7 +880,7 @@ func TestUpdateLdapBindDn(t *testing.T) { Type: auth.OAuth2, Cfg: &ldap.Source{}, }, - errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", + errMsg: "invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", }, // case 24 { @@ -942,12 +944,12 @@ func TestUpdateLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapBindDn.Flags - app.Action = service.updateLdapBindDn - + app := cli.Command{ + Flags: microcmdAuthUpdateLdapBindDn().Flags, + Action: service.updateLdapBindDn, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -1250,7 +1252,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { "--id", "1", "--security-protocol", "xxxxx", }, - errMsg: "Unknown security protocol name: xxxxx", + errMsg: "unknown security protocol name: xxxxx", }, // case 18 { @@ -1269,7 +1271,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { Type: auth.PAM, Cfg: &ldap.Source{}, }, - errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", + errMsg: "invalid authentication type. expected: LDAP (simple auth), actual: PAM", }, // case 20 { @@ -1330,12 +1332,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags - app.Action = service.updateLdapSimpleAuth - + app := cli.Command{ + Flags: microcmdAuthUpdateLdapSimpleAuth().Flags, + Action: service.updateLdapSimpleAuth, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index be5345d992..d1aa753500 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "net/url" @@ -12,11 +13,11 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/oauth2" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - oauthCLIFlags = []cli.Flag{ +func oauthCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -121,23 +122,34 @@ var ( Usage: "Activate automatic team membership removal depending on groups", }, } +} - microcmdAuthAddOauth = &cli.Command{ - Name: "add-oauth", - Usage: "Add new Oauth authentication source", - Action: runAddOauth, - Flags: oauthCLIFlags, +func microcmdAuthAddOauth() *cli.Command { + return &cli.Command{ + Name: "add-oauth", + Usage: "Add new Oauth authentication source", + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().runAddOauth(ctx, cmd) + }, + Flags: oauthCLIFlags(), } +} - microcmdAuthUpdateOauth = &cli.Command{ - Name: "update-oauth", - Usage: "Update existing Oauth authentication source", - Action: runUpdateOauth, - Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), +func microcmdAuthUpdateOauth() *cli.Command { + return &cli.Command{ + Name: "update-oauth", + Usage: "Update existing Oauth authentication source", + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().runUpdateOauth(ctx, cmd) + }, + Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{ + Name: "id", + Usage: "ID of authentication source", + }}, oauthCLIFlags()[1:]...)...), } -) +} -func parseOAuth2Config(c *cli.Context) *oauth2.Source { +func parseOAuth2Config(c *cli.Command) *oauth2.Source { var customURLMapping *oauth2.CustomURLMapping if c.IsSet("use-custom-urls") { customURLMapping = &oauth2.CustomURLMapping{ @@ -168,11 +180,8 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source { } } -func runAddOauth(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { +func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error { + if err := a.initDB(ctx); err != nil { return err } @@ -184,7 +193,7 @@ func runAddOauth(c *cli.Context) error { } } - return auth_model.CreateSource(ctx, &auth_model.Source{ + return a.createAuthSource(ctx, &auth_model.Source{ Type: auth_model.OAuth2, Name: c.String("name"), IsActive: true, @@ -193,19 +202,16 @@ func runAddOauth(c *cli.Context) error { }) } -func runUpdateOauth(c *cli.Context) error { +func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } - source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + source, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return err } @@ -296,5 +302,5 @@ func runUpdateOauth(c *cli.Context) error { oAuth2Config.CustomURLMapping = customURLMapping source.Cfg = oAuth2Config source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") - return auth_model.UpdateSource(ctx, source) + return a.updateAuthSource(ctx, source) } diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go new file mode 100644 index 0000000000..df1bd9c1a6 --- /dev/null +++ b/cmd/admin_auth_oauth_test.go @@ -0,0 +1,333 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/auth/source/oauth2" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestAddOauth(t *testing.T) { + testCases := []struct { + name string + args []string + source *auth_model.Source + errMsg string + }{ + { + name: "valid config", + args: []string{ + "--name", "test", + "--provider", "github", + "--key", "some_key", + "--secret", "some_secret", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Scopes: []string{}, + Provider: "github", + ClientID: "some_key", + ClientSecret: "some_secret", + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with openid connect", + args: []string{ + "--name", "test", + "--provider", "openidConnect", + "--key", "some_key", + "--secret", "some_secret", + "--auto-discover-url", "https://example.com", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Scopes: []string{}, + Provider: "openidConnect", + ClientID: "some_key", + ClientSecret: "some_secret", + OpenIDConnectAutoDiscoveryURL: "https://example.com", + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with options", + args: []string{ + "--name", "test", + "--provider", "gitlab", + "--key", "some_key", + "--secret", "some_secret", + "--use-custom-urls", "true", + "--custom-token-url", "https://example.com/token", + "--custom-auth-url", "https://example.com/auth", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--custom-tenant-id", "some_tenant", + "--icon-url", "https://example.com/icon", + "--scopes", "scope1,scope2", + "--skip-local-2fa", "true", + "--required-claim-name", "claim_name", + "--required-claim-value", "claim_value", + "--group-claim-name", "group_name", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"group1": [1,2]}`, + "--group-team-map-removal=true", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "some_key", + ClientSecret: "some_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://example.com/token", + AuthURL: "https://example.com/auth", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "some_tenant", + }, + IconURL: "https://example.com/icon", + Scopes: []string{"scope1", "scope2"}, + RequiredClaimName: "claim_name", + RequiredClaimValue: "claim_value", + GroupClaimName: "group_name", + AdminGroup: "admin", + RestrictedGroup: "restricted", + GroupTeamMap: `{"group1": [1,2]}`, + GroupTeamMapRemoval: true, + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var createdSource *auth_model.Source + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, source *auth_model.Source) error { + createdSource = source + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthAddOauth().Flags, + Action: a.runAddOauth, + } + + args := []string{"oauth-test"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.source, createdSource) + } + }) + } +} + +func TestUpdateOauth(t *testing.T) { + testCases := []struct { + name string + args []string + id int64 + existingAuthSource *auth_model.Source + authSource *auth_model.Source + errMsg string + }{ + { + name: "missing id", + args: []string{ + "--name", "test", + }, + errMsg: "--id flag is missing", + }, + { + name: "valid config", + id: 1, + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "old name", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "github", + ClientID: "old_key", + ClientSecret: "old_secret", + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--provider", "gitlab", + "--key", "new_key", + "--secret", "new_secret", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "new_key", + ClientSecret: "new_secret", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with options", + id: 1, + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "old name", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "old_key", + ClientSecret: "old_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://old.example.com/token", + AuthURL: "https://old.example.com/auth", + ProfileURL: "https://old.example.com/profile", + EmailURL: "https://old.example.com/email", + Tenant: "old_tenant", + }, + IconURL: "https://old.example.com/icon", + Scopes: []string{"old_scope1", "old_scope2"}, + RequiredClaimName: "old_claim_name", + RequiredClaimValue: "old_claim_value", + GroupClaimName: "old_group_name", + AdminGroup: "old_admin", + RestrictedGroup: "old_restricted", + GroupTeamMap: `{"old_group1": [1,2]}`, + GroupTeamMapRemoval: true, + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--provider", "github", + "--key", "new_key", + "--secret", "new_secret", + "--use-custom-urls", "true", + "--custom-token-url", "https://example.com/token", + "--custom-auth-url", "https://example.com/auth", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--custom-tenant-id", "new_tenant", + "--icon-url", "https://example.com/icon", + "--scopes", "scope1,scope2", + "--skip-local-2fa=true", + "--required-claim-name", "claim_name", + "--required-claim-value", "claim_value", + "--group-claim-name", "group_name", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"group1": [1,2]}`, + "--group-team-map-removal=false", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "github", + ClientID: "new_key", + ClientSecret: "new_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://example.com/token", + AuthURL: "https://example.com/auth", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "new_tenant", + }, + IconURL: "https://example.com/icon", + Scopes: []string{"scope1", "scope2"}, + RequiredClaimName: "claim_name", + RequiredClaimValue: "claim_value", + GroupClaimName: "group_name", + AdminGroup: "admin", + RestrictedGroup: "restricted", + GroupTeamMap: `{"group1": [1,2]}`, + GroupTeamMapRemoval: false, + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) { + return &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + TwoFactorPolicy: "skip", + }, nil + }, + updateAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.authSource, source) + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthUpdateOauth().Flags, + Action: a.runUpdateOauth, + } + + args := []string{"oauth-test"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_smtp.go index babcf78cea..e9daf71809 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_smtp.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "strings" @@ -11,11 +12,11 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/smtp" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var ( - smtpCLIFlags = []cli.Flag{ +func smtpCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -71,23 +72,34 @@ var ( Value: true, }, } +} - microcmdAuthAddSMTP = &cli.Command{ - Name: "add-smtp", - Usage: "Add new SMTP authentication source", - Action: runAddSMTP, - Flags: smtpCLIFlags, +func microcmdAuthUpdateSMTP() *cli.Command { + return &cli.Command{ + Name: "update-smtp", + Usage: "Update existing SMTP authentication source", + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().runUpdateSMTP(ctx, cmd) + }, + Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{ + Name: "id", + Usage: "ID of authentication source", + }}, smtpCLIFlags()[1:]...)...), } +} - microcmdAuthUpdateSMTP = &cli.Command{ - Name: "update-smtp", - Usage: "Update existing SMTP authentication source", - Action: runUpdateSMTP, - Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), +func microcmdAuthAddSMTP() *cli.Command { + return &cli.Command{ + Name: "add-smtp", + Usage: "Add new SMTP authentication source", + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().runAddSMTP(ctx, cmd) + }, + Flags: smtpCLIFlags(), } -) +} -func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { +func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { if c.IsSet("auth-type") { conf.Auth = c.String("auth-type") validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} @@ -120,11 +132,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { return nil } -func runAddSMTP(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { +func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error { + if err := a.initDB(ctx); err != nil { return err } @@ -152,7 +161,7 @@ func runAddSMTP(c *cli.Context) error { smtpConfig.Auth = "PLAIN" } - return auth_model.CreateSource(ctx, &auth_model.Source{ + return a.createAuthSource(ctx, &auth_model.Source{ Type: auth_model.SMTP, Name: c.String("name"), IsActive: active, @@ -161,19 +170,16 @@ func runAddSMTP(c *cli.Context) error { }) } -func runUpdateSMTP(c *cli.Context) error { +func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } - source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + source, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return err } @@ -194,5 +200,5 @@ func runUpdateSMTP(c *cli.Context) error { source.Cfg = smtpConfig source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") - return auth_model.UpdateSource(ctx, source) + return a.updateAuthSource(ctx, source) } diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go new file mode 100644 index 0000000000..9778ff87d2 --- /dev/null +++ b/cmd/admin_auth_smtp_test.go @@ -0,0 +1,285 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/auth/source/smtp" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestAddSMTP(t *testing.T) { + testCases := []struct { + name string + args []string + source *auth_model.Source + errMsg string + }{ + { + name: "missing name", + args: []string{ + "--host", "localhost", + "--port", "25", + }, + errMsg: "name must be set", + }, + { + name: "missing host", + args: []string{ + "--name", "test", + "--port", "25", + }, + errMsg: "host must be set", + }, + { + name: "missing port", + args: []string{ + "--name", "test", + "--host", "localhost", + }, + errMsg: "port must be set", + }, + { + name: "valid config", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + source: &auth_model.Source{ + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "localhost", + Port: 25, + // ForceSMTPS: true, + // SkipVerify: true, + }, + TwoFactorPolicy: "skip", + }, + }, + { + name: "valid config with options", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + "--auth-type", "LOGIN", + "--force-smtps=false", + "--skip-verify=false", + "--helo-hostname", "example.com", + "--disable-helo=false", + "--allowed-domains", "example.com,example.org", + "--skip-local-2fa=false", + "--active=false", + }, + source: &auth_model.Source{ + Type: auth_model.SMTP, + Name: "test", + IsActive: false, + Cfg: &smtp.Source{ + Auth: "LOGIN", + Host: "localhost", + Port: 25, + ForceSMTPS: false, + SkipVerify: false, + HeloHostname: "example.com", + DisableHelo: false, + AllowedDomains: "example.com,example.org", + }, + TwoFactorPolicy: "", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.source, source) + return nil + }, + } + + cmd := &cli.Command{ + Flags: microcmdAuthAddSMTP().Flags, + Action: a.runAddSMTP, + } + + args := []string{"smtp-test"} + args = append(args, tc.args...) + + t.Log(args) + err := cmd.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUpdateSMTP(t *testing.T) { + testCases := []struct { + name string + args []string + existingAuthSource *auth_model.Source + authSource *auth_model.Source + errMsg string + }{ + { + name: "missing id", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + errMsg: "--id flag is missing", + }, + { + name: "valid config", + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "old name", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "old host", + Port: 26, + ForceSMTPS: true, + SkipVerify: true, + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "localhost", + Port: 25, + ForceSMTPS: true, + SkipVerify: true, + }, + TwoFactorPolicy: "skip", + }, + }, + { + name: "valid config with options", + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "old name", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "old host", + Port: 26, + ForceSMTPS: true, + SkipVerify: true, + HeloHostname: "old.example.com", + DisableHelo: false, + AllowedDomains: "old.example.com", + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--host", "localhost", + "--port", "25", + "--auth-type", "LOGIN", + "--force-smtps=false", + "--skip-verify=false", + "--helo-hostname", "example.com", + "--disable-helo=true", + "--allowed-domains", "example.com,example.org", + "--skip-local-2fa=true", + "--active=false", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: false, + Cfg: &smtp.Source{ + Auth: "LOGIN", + Host: "localhost", + Port: 25, + ForceSMTPS: false, + SkipVerify: false, + HeloHostname: "example.com", + DisableHelo: true, + AllowedDomains: "example.com,example.org", + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) { + return &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + SkipVerify: true, + ForceSMTPS: true, + }, + TwoFactorPolicy: "skip", + }, nil + }, + + updateAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.authSource, source) + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthUpdateSMTP().Flags, + Action: a.runUpdateSMTP, + } + args := []string{"smtp-tests"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index ab769f6d0c..a5f1bd5105 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -4,11 +4,13 @@ package cmd import ( + "context" + "code.gitea.io/gitea/modules/graceful" asymkey_service "code.gitea.io/gitea/services/asymkey" repo_service "code.gitea.io/gitea/services/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -25,20 +27,14 @@ var ( } ) -func runRegenerateHooks(_ *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } -func runRegenerateKeys(_ *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 967a6ed88a..3a24c3e56f 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -4,18 +4,18 @@ package cmd import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var subcmdUser = &cli.Command{ Name: "user", Usage: "Modify users", - Subcommands: []*cli.Command{ - microcmdUserCreate, + Commands: []*cli.Command{ + microcmdUserCreate(), microcmdUserList, - microcmdUserChangePassword, - microcmdUserDelete, + microcmdUserChangePassword(), + microcmdUserDelete(), microcmdUserGenerateAccessToken, - microcmdUserMustChangePassword, + microcmdUserMustChangePassword(), }, } diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index f1ed46e70b..c27905b4db 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" @@ -13,44 +14,41 @@ import ( "code.gitea.io/gitea/modules/setting" user_service "code.gitea.io/gitea/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserChangePassword = &cli.Command{ - Name: "change-password", - Usage: "Change a user's password", - Action: runChangePassword, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "The user to change password for", +func microcmdUserChangePassword() *cli.Command { + return &cli.Command{ + Name: "change-password", + Usage: "Change a user's password", + Action: runChangePassword, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "The user to change password for", + Required: true, + }, + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "New password to set for user", + Required: true, + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "User must change password (can be disabled by --must-change-password=false)", + Value: true, + }, }, - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Value: "", - Usage: "New password to set for user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password (can be disabled by --must-change-password=false)", - Value: true, - }, - }, -} - -func runChangePassword(c *cli.Context) error { - if err := argsSet(c, "username", "password"); err != nil { - return err } +} - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err +func runChangePassword(ctx context.Context, c *cli.Command) error { + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } user, err := user_model.GetUserByName(ctx, c.String("username")) diff --git a/cmd/admin_user_change_password_test.go b/cmd/admin_user_change_password_test.go new file mode 100644 index 0000000000..17d0382af7 --- /dev/null +++ b/cmd/admin_user_change_password_test.go @@ -0,0 +1,91 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChangePasswordCommand(t *testing.T) { + ctx := t.Context() + + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + }() + + t.Run("change password successfully", func(t *testing.T) { + // defer func() { + // require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + // }() + // Prepare test user + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + + // load test user + userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + + // Change the password + err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"}) + require.NoError(t, err) + + // Verify the password has been changed + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.NotEqual(t, userBase.Passwd, user.Passwd) + assert.NotEqual(t, userBase.Salt, user.Salt) + + // Additional check for must-change-password flag + require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"})) + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, user.MustChangePassword) + + require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"})) + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, user.MustChangePassword) + }) + + t.Run("failure cases", func(t *testing.T) { + testCases := []struct { + name string + args []string + expectedErr string + }{ + { + name: "user does not exist", + args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"}, + expectedErr: "user does not exist", + }, + { + name: "missing username", + args: []string{"change-password", "--password", "newpassword"}, + expectedErr: `"username" not set`, + }, + { + name: "missing password", + args: []string{"change-password", "--username", "testuser"}, + expectedErr: `"password" not set`, + }, + { + name: "too short password", + args: []string{"change-password", "--username", "testuser", "--password", "1"}, + expectedErr: "password is not long enough", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := microcmdUserChangePassword().Run(ctx, tc.args) + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + }) + } + }) +} diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 97f9bb7f06..cbdb5f90e2 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -16,87 +16,95 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserCreate = &cli.Command{ - Name: "create", - Usage: "Create a new user in database", - Action: runCreateUser, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Usage: "Username. DEPRECATED: use username instead", +func microcmdUserCreate() *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "Create a new user in database", + Action: runCreateUser, + MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ + { + Flags: [][]cli.Flag{ + { + &cli.StringFlag{ + Name: "name", + Usage: "Username. DEPRECATED: use username instead", + }, + &cli.StringFlag{ + Name: "username", + Usage: "Username", + }, + }, + }, + Required: true, + }, }, - &cli.StringFlag{ - Name: "username", - Usage: "Username", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "user-type", + Usage: "Set user's type: individual or bot", + Value: "individual", + }, + &cli.StringFlag{ + Name: "password", + Usage: "User password", + }, + &cli.StringFlag{ + Name: "email", + Usage: "User email address", + Required: true, + }, + &cli.BoolFlag{ + Name: "admin", + Usage: "User is an admin", + }, + &cli.BoolFlag{ + Name: "random-password", + Usage: "Generate a random password for the user", + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", + HideDefault: true, + }, + &cli.IntFlag{ + Name: "random-password-length", + Usage: "Length of the random password to be generated", + Value: 12, + }, + &cli.BoolFlag{ + Name: "access-token", + Usage: "Generate access token for the user", + }, + &cli.StringFlag{ + Name: "access-token-name", + Usage: `Name of the generated access token`, + Value: "gitea-admin", + }, + &cli.StringFlag{ + Name: "access-token-scopes", + Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, + Value: "all", + }, + &cli.BoolFlag{ + Name: "restricted", + Usage: "Make a restricted user account", + }, + &cli.StringFlag{ + Name: "fullname", + Usage: `The full, human-readable name of the user`, + }, }, - &cli.StringFlag{ - Name: "user-type", - Usage: "Set user's type: individual or bot", - Value: "individual", - }, - &cli.StringFlag{ - Name: "password", - Usage: "User password", - }, - &cli.StringFlag{ - Name: "email", - Usage: "User email address", - }, - &cli.BoolFlag{ - Name: "admin", - Usage: "User is an admin", - }, - &cli.BoolFlag{ - Name: "random-password", - Usage: "Generate a random password for the user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", - DisableDefaultText: true, - }, - &cli.IntFlag{ - Name: "random-password-length", - Usage: "Length of the random password to be generated", - Value: 12, - }, - &cli.BoolFlag{ - Name: "access-token", - Usage: "Generate access token for the user", - }, - &cli.StringFlag{ - Name: "access-token-name", - Usage: `Name of the generated access token`, - Value: "gitea-admin", - }, - &cli.StringFlag{ - Name: "access-token-scopes", - Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, - Value: "all", - }, - &cli.BoolFlag{ - Name: "restricted", - Usage: "Make a restricted user account", - }, - &cli.StringFlag{ - Name: "fullname", - Usage: `The full, human-readable name of the user`, - }, - }, + } } -func runCreateUser(c *cli.Context) error { +func runCreateUser(ctx context.Context, c *cli.Command) error { // this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. setting.LoadSettings() - if err := argsSet(c, "email"); err != nil { - return err - } - userTypes := map[string]user_model.UserType{ "individual": user_model.UserTypeIndividual, "bot": user_model.UserTypeBot, @@ -113,12 +121,6 @@ func runCreateUser(c *cli.Context) error { return errors.New("password can only be set for individual users") } } - if c.IsSet("name") && c.IsSet("username") { - return errors.New("cannot set both --name and --username flags") - } - if !c.IsSet("name") && !c.IsSet("username") { - return errors.New("one of --name or --username flags must be set") - } if c.IsSet("password") && c.IsSet("random-password") { return errors.New("cannot set both -random-password and -password flags") @@ -129,16 +131,12 @@ func runCreateUser(c *cli.Context) error { username = c.String("username") } else { username = c.String("name") - _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n") + _, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n") } - ctx := c.Context if !setting.IsInTesting { - // FIXME: need to refactor the "installSignals/initDB" related code later + // FIXME: need to refactor the "initDB" related code later // it doesn't make sense to call it in (almost) every command action function - var cancel context.CancelFunc - ctx, cancel = installSignals() - defer cancel() if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d5952412c3..437e07d9a2 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -18,8 +18,6 @@ import ( ) func TestAdminUserCreate(t *testing.T) { - app := NewMainApp(AppVersion{}) - reset := func() { require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) @@ -31,8 +29,9 @@ func TestAdminUserCreate(t *testing.T) { IsAdmin bool MustChangePassword bool } + createCheck := func(name, args string) check { - require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) + require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} } @@ -51,7 +50,7 @@ func TestAdminUserCreate(t *testing.T) { }) createUser := func(name string, args ...string) error { - return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...)) + return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...)) } t.Run("UserType", func(t *testing.T) { diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 520557554a..f91041577c 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -4,53 +4,56 @@ package cmd import ( + "context" "errors" "fmt" "strings" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" user_service "code.gitea.io/gitea/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserDelete = &cli.Command{ - Name: "delete", - Usage: "Delete specific user by id, name or email", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "id", - Usage: "ID of user of the user to delete", +func microcmdUserDelete() *cli.Command { + return &cli.Command{ + Name: "delete", + Usage: "Delete specific user by id, name or email", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "id", + Usage: "ID of user of the user to delete", + }, + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "Username of the user to delete", + }, + &cli.StringFlag{ + Name: "email", + Aliases: []string{"e"}, + Usage: "Email of the user to delete", + }, + &cli.BoolFlag{ + Name: "purge", + Usage: "Purge user, all their repositories, organizations and comments", + }, }, - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Usage: "Username of the user to delete", - }, - &cli.StringFlag{ - Name: "email", - Aliases: []string{"e"}, - Usage: "Email of the user to delete", - }, - &cli.BoolFlag{ - Name: "purge", - Usage: "Purge user, all their repositories, organizations and comments", - }, - }, - Action: runDeleteUser, + Action: runDeleteUser, + } } -func runDeleteUser(c *cli.Context) error { +func runDeleteUser(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") } - ctx, cancel := installSignals() - defer cancel() - - if err := initDB(ctx); err != nil { - return err + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } if err := storage.Init(); err != nil { @@ -70,11 +73,11 @@ func runDeleteUser(c *cli.Context) error { return err } if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) { - return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username")) + return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username")) } if c.IsSet("id") && user.ID != c.Int64("id") { - return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) + return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id")) } return user_service.DeleteUser(ctx, user, c.Bool("purge")) diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go new file mode 100644 index 0000000000..d0330582d7 --- /dev/null +++ b/cmd/admin_user_delete_test.go @@ -0,0 +1,111 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "strconv" + "strings" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/require" +) + +func TestAdminUserDelete(t *testing.T) { + ctx := t.Context() + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) + }() + + setupTestUser := func(t *testing.T) { + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + } + + t.Run("delete user by id", func(t *testing.T) { + setupTestUser(t) + + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) + t.Run("delete user by username", func(t *testing.T) { + setupTestUser(t) + + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) + t.Run("delete user by email", func(t *testing.T) { + setupTestUser(t) + + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) + t.Run("delete user by all 3 attributes", func(t *testing.T) { + setupTestUser(t) + + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) +} + +func TestAdminUserDeleteFailure(t *testing.T) { + testCases := []struct { + name string + args []string + expectedErr string + }{ + { + name: "no user to delete", + args: []string{"delete", "--username", "nonexistentuser"}, + expectedErr: "user does not exist", + }, + { + name: "user exists but provided username does not match", + args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"}, + expectedErr: "the user testuser who has email testuser@gitea.local does not match the provided username wrongusername", + }, + { + name: "user exists but provided id does not match", + args: []string{"delete", "--username", "testuser", "--id", "999"}, + expectedErr: "the user testuser does not match the provided id 999", + }, + { + name: "no required flags are provided", + args: []string{"delete"}, + expectedErr: "You must provide the id, username or email of a user to delete", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := t.Context() + if strings.Contains(tc.name, "user exists") { + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + } + + err := microcmdUserDelete().Run(ctx, tc.args) + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + }) + + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) + } +} diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index f6db7a74bd..61064fdef4 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "errors" "fmt" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserGenerateAccessToken = &cli.Command{ @@ -41,14 +42,11 @@ var microcmdUserGenerateAccessToken = &cli.Command{ Action: runGenerateAccessToken, } -func runGenerateAccessToken(c *cli.Context) error { +func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("you must provide a username to generate a token for") } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index 4c2b26d1df..e3d345e2f2 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "fmt" "os" "text/tabwriter" user_model "code.gitea.io/gitea/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserList = &cli.Command{ @@ -25,10 +26,7 @@ var microcmdUserList = &cli.Command{ }, } -func runListUsers(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runListUsers(ctx context.Context, c *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go index 2794414259..8521853dc1 100644 --- a/cmd/admin_user_must_change_password.go +++ b/cmd/admin_user_must_change_password.go @@ -4,40 +4,41 @@ package cmd import ( + "context" "errors" "fmt" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -var microcmdUserMustChangePassword = &cli.Command{ - Name: "must-change-password", - Usage: "Set the must change password flag for the provided users or all users", - Action: runMustChangePassword, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "all", - Aliases: []string{"A"}, - Usage: "All users must change password, except those explicitly excluded with --exclude", +func microcmdUserMustChangePassword() *cli.Command { + return &cli.Command{ + Name: "must-change-password", + Usage: "Set the must change password flag for the provided users or all users", + Action: runMustChangePassword, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Aliases: []string{"A"}, + Usage: "All users must change password, except those explicitly excluded with --exclude", + }, + &cli.StringSliceFlag{ + Name: "exclude", + Aliases: []string{"e"}, + Usage: "Do not change the must-change-password flag for these users", + }, + &cli.BoolFlag{ + Name: "unset", + Usage: "Instead of setting the must-change-password flag, unset it", + }, }, - &cli.StringSliceFlag{ - Name: "exclude", - Aliases: []string{"e"}, - Usage: "Do not change the must-change-password flag for these users", - }, - &cli.BoolFlag{ - Name: "unset", - Usage: "Instead of setting the must-change-password flag, unset it", - }, - }, + } } -func runMustChangePassword(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runMustChangePassword(ctx context.Context, c *cli.Command) error { if c.NArg() == 0 && !c.IsSet("all") { return errors.New("either usernames or --all must be provided") } @@ -46,8 +47,10 @@ func runMustChangePassword(c *cli.Context) error { all := c.Bool("all") exclude := c.StringSlice("exclude") - if err := initDB(ctx); err != nil { - return err + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude) diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go new file mode 100644 index 0000000000..a6611fdc04 --- /dev/null +++ b/cmd/admin_user_must_change_password_test.go @@ -0,0 +1,78 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMustChangePassword(t *testing.T) { + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + }() + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"}) + require.NoError(t, err) + // Reset password change flag + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"}) + require.NoError(t, err) + + testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Make all users change password + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.True(t, testUserExclude.MustChangePassword) + + // Reset password change flag but exclude all tested users + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.True(t, testUserExclude.MustChangePassword) + + // Reset password change flag by listing multiple users + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Exclude a user from all user + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Unset a flag for single user + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) +} diff --git a/cmd/cert.go b/cmd/cert.go index 38241d71a3..8cc9f43528 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -6,6 +6,7 @@ package cmd import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -13,6 +14,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "log" "math/big" "net" @@ -20,47 +22,59 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -// CmdCert represents the available cert sub-command. -var CmdCert = &cli.Command{ - Name: "cert", - Usage: "Generate self-signed certificate", - Description: `Generate a self-signed X.509 certificate for a TLS server. +// cmdCert represents the available cert sub-command. +func cmdCert() *cli.Command { + return &cli.Command{ + Name: "cert", + Usage: "Generate self-signed certificate", + Description: `Generate a self-signed X.509 certificate for a TLS server. Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, - Action: runCert, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "host", - Value: "", - Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Action: runCert, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "host", + Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Required: true, + }, + &cli.StringFlag{ + Name: "ecdsa-curve", + Value: "", + Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", + }, + &cli.IntFlag{ + Name: "rsa-bits", + Value: 3072, + Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", + }, + &cli.StringFlag{ + Name: "start-date", + Value: "", + Usage: "Creation date formatted as Jan 1 15:04:05 2011", + }, + &cli.DurationFlag{ + Name: "duration", + Value: 365 * 24 * time.Hour, + Usage: "Duration that certificate is valid for", + }, + &cli.BoolFlag{ + Name: "ca", + Usage: "whether this cert should be its own Certificate Authority", + }, + &cli.StringFlag{ + Name: "out", + Value: "cert.pem", + Usage: "Path to the file where there certificate will be saved", + }, + &cli.StringFlag{ + Name: "keyout", + Value: "key.pem", + Usage: "Path to the file where there certificate key will be saved", + }, }, - &cli.StringFlag{ - Name: "ecdsa-curve", - Value: "", - Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", - }, - &cli.IntFlag{ - Name: "rsa-bits", - Value: 3072, - Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", - }, - &cli.StringFlag{ - Name: "start-date", - Value: "", - Usage: "Creation date formatted as Jan 1 15:04:05 2011", - }, - &cli.DurationFlag{ - Name: "duration", - Value: 365 * 24 * time.Hour, - Usage: "Duration that certificate is valid for", - }, - &cli.BoolFlag{ - Name: "ca", - Usage: "whether this cert should be its own Certificate Authority", - }, - }, + } } func publicKey(priv any) any { @@ -89,11 +103,7 @@ func pemBlockForKey(priv any) *pem.Block { } } -func runCert(c *cli.Context) error { - if err := argsSet(c, "host"); err != nil { - return err - } - +func runCert(_ context.Context, c *cli.Command) error { var priv any var err error switch c.String("ecdsa-curve") { @@ -108,17 +118,17 @@ func runCert(c *cli.Context) error { case "P521": priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) default: - log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve")) + err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve")) } if err != nil { - log.Fatalf("Failed to generate private key: %v", err) + return fmt.Errorf("failed to generate private key: %w", err) } var notBefore time.Time if startDate := c.String("start-date"); startDate != "" { notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate) if err != nil { - log.Fatalf("Failed to parse creation date: %v", err) + return fmt.Errorf("failed to parse creation date %w", err) } } else { notBefore = time.Now() @@ -129,7 +139,7 @@ func runCert(c *cli.Context) error { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - log.Fatalf("Failed to generate serial number: %v", err) + return fmt.Errorf("failed to generate serial number: %w", err) } template := x509.Certificate{ @@ -162,35 +172,35 @@ func runCert(c *cli.Context) error { derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { - log.Fatalf("Failed to create certificate: %v", err) + return fmt.Errorf("failed to create certificate: %w", err) } - certOut, err := os.Create("cert.pem") + certOut, err := os.Create(c.String("out")) if err != nil { - log.Fatalf("Failed to open cert.pem for writing: %v", err) + return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) } err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if err != nil { - log.Fatalf("Failed to encode certificate: %v", err) + return fmt.Errorf("failed to encode certificate: %w", err) } err = certOut.Close() if err != nil { - log.Fatalf("Failed to write cert: %v", err) + return fmt.Errorf("failed to write cert: %w", err) } - log.Println("Written cert.pem") + fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out")) - keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { - log.Fatalf("Failed to open key.pem for writing: %v", err) + return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) } err = pem.Encode(keyOut, pemBlockForKey(priv)) if err != nil { - log.Fatalf("Failed to encode key: %v", err) + return fmt.Errorf("failed to encode key: %w", err) } err = keyOut.Close() if err != nil { - log.Fatalf("Failed to write key: %v", err) + return fmt.Errorf("failed to write key: %w", err) } - log.Println("Written key.pem") + fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout")) return nil } diff --git a/cmd/cert_test.go b/cmd/cert_test.go new file mode 100644 index 0000000000..4242d8915b --- /dev/null +++ b/cmd/cert_test.go @@ -0,0 +1,123 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCertCommand(t *testing.T) { + cases := []struct { + name string + args []string + }{ + { + name: "RSA cert generation", + args: []string{ + "cert-test", + "--host", "localhost", + "--rsa-bits", "2048", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + { + name: "ECDSA cert generation", + args: []string{ + "cert-test", + "--host", "localhost", + "--ecdsa-curve", "P256", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + { + name: "mixed host, certificate authority", + args: []string{ + "cert-test", + "--host", "localhost,127.0.0.1", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + app := cmdCert() + tempDir := t.TempDir() + + certFile := filepath.Join(tempDir, "cert.pem") + keyFile := filepath.Join(tempDir, "key.pem") + + err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile)) + require.NoError(t, err) + + assert.FileExists(t, certFile) + assert.FileExists(t, keyFile) + }) + } +} + +func TestCertCommandFailures(t *testing.T) { + cases := []struct { + name string + args []string + errMsg string + }{ + { + name: "Start Date Parsing failure", + args: []string{ + "cert-test", + "--host", "localhost", + "--start-date", "invalid-date", + }, + errMsg: "parsing time", + }, + { + name: "Unknown curve", + args: []string{ + "cert-test", + "--host", "localhost", + "--ecdsa-curve", "invalid-curve", + }, + errMsg: "unrecognized elliptic curve", + }, + { + name: "Key generation failure", + args: []string{ + "cert-test", + "--host", "localhost", + "--rsa-bits", "invalid-bits", + }, + }, + { + name: "Missing parameters", + args: []string{ + "cert-test", + }, + errMsg: `"host" not set`, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + app := cmdCert() + tempDir := t.TempDir() + + certFile := filepath.Join(tempDir, "cert.pem") + keyFile := filepath.Join(tempDir, "key.pem") + err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile)) + require.Error(t, err) + if c.errMsg != "" { + assert.ErrorContains(t, err, c.errMsg) + } + assert.NoFileExists(t, certFile) + assert.NoFileExists(t, keyFile) + }) + } +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 423dce2674..7a4d5d0d89 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -18,20 +18,19 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // argsSet checks that all the required arguments are set. args is a list of // arguments that must be set in the passed Context. -func argsSet(c *cli.Context, args ...string) error { +func argsSet(c *cli.Command, args ...string) error { for _, a := range args { if !c.IsSet(a) { return errors.New(a + " is not set") } - if util.IsEmptyString(c.String(a)) { + if c.Value(a) == nil { return errors.New(a + " is required") } } @@ -109,7 +108,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) } -func globalBool(c *cli.Context, name string) bool { +func globalBool(c *cli.Command, name string) bool { for _, ctx := range c.Lineage() { if ctx.Bool(name) { return true @@ -120,8 +119,8 @@ func globalBool(c *cli.Context, name string) bool { // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. -func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error { - return func(c *cli.Context) error { +func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) { + return func(ctx context.Context, c *cli.Command) (context.Context, error) { level := defaultLevel if globalBool(c, "quiet") { level = log.FATAL @@ -130,6 +129,6 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error level = log.TRACE } log.SetConsoleLogger(log.DEFAULT, "console-default", level) - return nil + return ctx, nil } } diff --git a/cmd/docs.go b/cmd/docs.go index 605d02e3ef..098c0e9a8a 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -4,11 +4,13 @@ package cmd import ( + "context" "fmt" "os" "strings" - "github.com/urfave/cli/v2" + cli_docs "github.com/urfave/cli-docs/v3" + "github.com/urfave/cli/v3" ) // CmdDocs represents the available docs sub-command. @@ -30,16 +32,16 @@ var CmdDocs = &cli.Command{ }, } -func runDocs(ctx *cli.Context) error { - docs, err := ctx.App.ToMarkdown() - if ctx.Bool("man") { - docs, err = ctx.App.ToMan() +func runDocs(_ context.Context, cmd *cli.Command) error { + docs, err := cli_docs.ToMarkdown(cmd.Root()) + if cmd.Bool("man") { + docs, err = cli_docs.ToMan(cmd.Root()) } if err != nil { return err } - if !ctx.Bool("man") { + if !cmd.Bool("man") { // Clean up markdown. The following bug was fixed in v2, but is present in v1. // It affects markdown output (even though the issue is referring to man pages) // https://github.com/urfave/cli/issues/1040 @@ -51,8 +53,8 @@ func runDocs(ctx *cli.Context) error { } out := os.Stdout - if ctx.String("output") != "" { - fi, err := os.Create(ctx.String("output")) + if cmd.String("output") != "" { + fi, err := os.Create(cmd.String("output")) if err != nil { return err } diff --git a/cmd/doctor.go b/cmd/doctor.go index 4a12b957f5..9e0fcbf877 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -20,7 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/doctor" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "xorm.io/xorm" ) @@ -30,7 +30,7 @@ var CmdDoctor = &cli.Command{ Usage: "Diagnose and optionally fix problems, convert or re-create database tables", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ cmdDoctorCheck, cmdRecreateTable, cmdDoctorConvert, @@ -93,16 +93,13 @@ You should back-up your database before doing this and ensure that your database Action: runRecreateTable, } -func runRecreateTable(ctx *cli.Context) error { - stdCtx, cancel := installSignals() - defer cancel() - +func runRecreateTable(ctx context.Context, cmd *cli.Command) error { // Redirect the default golog to here golog.SetFlags(0) golog.SetPrefix("") golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) - debug := ctx.Bool("debug") + debug := cmd.Bool("debug") setting.MustInstalled() setting.LoadDBSetting() @@ -113,15 +110,15 @@ func runRecreateTable(ctx *cli.Context) error { } setting.Database.LogSQL = debug - if err := db.InitEngine(stdCtx); err != nil { + if err := db.InitEngine(ctx); err != nil { fmt.Println(err) fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") return nil } - args := ctx.Args() - names := make([]string, 0, ctx.NArg()) - for i := 0; i < ctx.NArg(); i++ { + args := cmd.Args() + names := make([]string, 0, cmd.NArg()) + for i := 0; i < cmd.NArg(); i++ { names = append(names, args.Get(i)) } @@ -131,7 +128,7 @@ func runRecreateTable(ctx *cli.Context) error { } recreateTables := migrate_base.RecreateTables(beans...) - return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error { + return db.InitEngineWithMigration(ctx, func(ctx context.Context, x *xorm.Engine) error { if err := migrations.EnsureUpToDate(ctx, x); err != nil { return err } @@ -139,11 +136,11 @@ func runRecreateTable(ctx *cli.Context) error { }) } -func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { +func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) { // Silence the default loggers setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) - logFile := ctx.String("log-file") + logFile := cmd.String("log-file") switch logFile { case "": return // if no doctor log-file is set, do not show any log from default logger @@ -161,23 +158,20 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { } } -func runDoctorCheck(ctx *cli.Context) error { - stdCtx, cancel := installSignals() - defer cancel() - +func runDoctorCheck(ctx context.Context, cmd *cli.Command) error { colorize := log.CanColorStdout - if ctx.IsSet("color") { - colorize = ctx.Bool("color") + if cmd.IsSet("color") { + colorize = cmd.Bool("color") } - setupDoctorDefaultLogger(ctx, colorize) + setupDoctorDefaultLogger(cmd, colorize) // Finally redirect the default golang's log to here golog.SetFlags(0) golog.SetPrefix("") golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) - if ctx.IsSet("list") { + if cmd.IsSet("list") { w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) _, _ = w.Write([]byte("Default\tName\tTitle\n")) doctor.SortChecks(doctor.Checks) @@ -195,12 +189,12 @@ func runDoctorCheck(ctx *cli.Context) error { } var checks []*doctor.Check - if ctx.Bool("all") { + if cmd.Bool("all") { checks = make([]*doctor.Check, len(doctor.Checks)) copy(checks, doctor.Checks) - } else if ctx.IsSet("run") { - addDefault := ctx.Bool("default") - runNamesSet := container.SetOf(ctx.StringSlice("run")...) + } else if cmd.IsSet("run") { + addDefault := cmd.Bool("default") + runNamesSet := container.SetOf(cmd.StringSlice("run")...) for _, check := range doctor.Checks { if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { checks = append(checks, check) @@ -217,5 +211,5 @@ func runDoctorCheck(ctx *cli.Context) error { } } } - return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) + return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks) } diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 48c835ad0e..8cb718d383 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdDoctorConvert represents the available convert sub-command. @@ -21,11 +22,8 @@ var cmdDoctorConvert = &cli.Command{ Action: runDoctorConvert, } -func runDoctorConvert(ctx *cli.Context) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { + if err := initDB(ctx); err != nil { return err } diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index 3e1ff299c5..da942b38b6 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/services/doctor" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestDoctorRun(t *testing.T) { @@ -22,12 +22,13 @@ func TestDoctorRun(t *testing.T) { SkipDatabaseInitialization: true, }) - app := cli.NewApp() - app.Commands = []*cli.Command{cmdDoctorCheck} - err := app.Run([]string{"./gitea", "check", "--run", "test-check"}) + app := &cli.Command{ + Commands: []*cli.Command{cmdDoctorCheck}, + } + err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) assert.NoError(t, err) - err = app.Run([]string{"./gitea", "check", "--run", "no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) assert.ErrorContains(t, err, `unknown checks: "no-such"`) - err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) assert.ErrorContains(t, err, `unknown checks: "no-such"`) } diff --git a/cmd/dump.go b/cmd/dump.go index 7d640b78fd..ed19e3d4bf 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "os" "path" "path/filepath" @@ -20,7 +21,7 @@ import ( "gitea.com/go-chi/session" "github.com/mholt/archiver/v3" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDump represents the available dump sub-command. @@ -101,17 +102,17 @@ func fatal(format string, args ...any) { log.Fatal(format, args...) } -func runDump(ctx *cli.Context) error { +func runDump(ctx context.Context, cmd *cli.Command) error { setting.MustInstalled() - quite := ctx.Bool("quiet") - verbose := ctx.Bool("verbose") + quite := cmd.Bool("quiet") + verbose := cmd.Bool("verbose") if verbose && quite { fatal("Option --quiet and --verbose cannot both be set") } // outFileName is either "-" or a file name (will be made absolute) - outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type")) + outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type")) if outType == "" { fatal("Invalid output type") } @@ -136,10 +137,7 @@ func runDump(ctx *cli.Context) error { setting.DisableLoggerInit() setting.LoadSettings() // cannot access session settings otherwise - stdCtx, cancel := installSignals() - defer cancel() - - err := db.InitEngine(stdCtx) + err := db.InitEngine(ctx) if err != nil { return err } @@ -165,7 +163,7 @@ func runDump(ctx *cli.Context) error { } dumper.GlobalExcludeAbsPath(outFileName) - if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { + if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") { log.Info("Skip dumping local repositories") } else { log.Info("Dumping local repositories... %s", setting.RepoRootPath) @@ -173,7 +171,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include repositories: %v", err) } - if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { + if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") { log.Info("Skip dumping LFS data") } else if !setting.LFS.StartServer { log.Info("LFS isn't enabled. Skip dumping LFS data") @@ -188,12 +186,12 @@ func runDump(ctx *cli.Context) error { } } - if ctx.Bool("skip-db") { + if cmd.Bool("skip-db") { // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. dumper.GlobalExcludeAbsPath(setting.Database.Path) log.Info("Skipping database") } else { - tmpDir := ctx.String("tempdir") + tmpDir := cmd.String("tempdir") if _, err := os.Stat(tmpDir); os.IsNotExist(err) { fatal("Path does not exist: %s", tmpDir) } @@ -209,7 +207,7 @@ func runDump(ctx *cli.Context) error { } }() - targetDBType := ctx.String("database") + targetDBType := cmd.String("database") if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) } else { @@ -230,7 +228,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include specified app.ini: %v", err) } - if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { + if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") { log.Info("Skipping custom directory") } else { customDir, err := os.Stat(setting.CustomPath) @@ -263,7 +261,7 @@ func runDump(ctx *cli.Context) error { excludes = append(excludes, opts.ProviderConfig) } - if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { + if cmd.IsSet("skip-index") && cmd.Bool("skip-index") { excludes = append(excludes, setting.Indexer.RepoPath) excludes = append(excludes, setting.Indexer.IssuePath) } @@ -278,7 +276,7 @@ func runDump(ctx *cli.Context) error { } } - if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { + if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") { log.Info("Skip dumping attachment data") } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { info, err := object.Stat() @@ -290,7 +288,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to dump attachments: %v", err) } - if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { + if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") { log.Info("Skip dumping package data") } else if !setting.Packages.Enabled { log.Info("Packages isn't enabled. Skip dumping package data") @@ -307,7 +305,7 @@ func runDump(ctx *cli.Context) error { // Doesn't check if LogRootPath exists before processing --skip-log intentionally, // ensuring that it's clear the dump is skipped whether the directory's initialized // yet or not. - if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { + if cmd.IsSet("skip-log") && cmd.Bool("skip-log") { log.Info("Skip dumping log files") } else { isExist, err := util.IsExist(setting.Log.RootPath) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 11d0270404..8dd4fd86e7 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -19,7 +19,7 @@ import ( "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/migrations" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDumpRepository represents the available dump repository sub-command. @@ -79,16 +79,13 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runDumpRepository(ctx *cli.Context) error { +func runDumpRepository(ctx context.Context, cmd *cli.Command) error { setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) setting.DisableLoggerInit() setting.LoadSettings() // cannot access skip_tls_verify settings otherwise - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { + if err := initDB(ctx); err != nil { return err } @@ -105,8 +102,8 @@ func runDumpRepository(ctx *cli.Context) error { var ( serviceType structs.GitServiceType - cloneAddr = ctx.String("clone_addr") - serviceStr = ctx.String("git_service") + cloneAddr = cmd.String("clone_addr") + serviceStr = cmd.String("git_service") ) if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") { @@ -124,13 +121,13 @@ func runDumpRepository(ctx *cli.Context) error { opts := base.MigrateOptions{ GitServiceType: serviceType, CloneAddr: cloneAddr, - AuthUsername: ctx.String("auth_username"), - AuthPassword: ctx.String("auth_password"), - AuthToken: ctx.String("auth_token"), - RepoName: ctx.String("repo_name"), + AuthUsername: cmd.String("auth_username"), + AuthPassword: cmd.String("auth_password"), + AuthToken: cmd.String("auth_token"), + RepoName: cmd.String("repo_name"), } - if len(ctx.String("units")) == 0 { + if len(cmd.String("units")) == 0 { opts.Wiki = true opts.Issues = true opts.Milestones = true @@ -140,7 +137,7 @@ func runDumpRepository(ctx *cli.Context) error { opts.PullRequests = true opts.ReleaseAssets = true } else { - units := strings.Split(ctx.String("units"), ",") + units := strings.Split(cmd.String("units"), ",") for _, unit := range units { switch strings.ToLower(strings.TrimSpace(unit)) { case "": @@ -169,7 +166,7 @@ func runDumpRepository(ctx *cli.Context) error { // the repo_dir will be removed if error occurs in DumpRepository // make sure the directory doesn't exist or is empty, prevent from deleting user files - repoDir := ctx.String("repo_dir") + repoDir := cmd.String("repo_dir") if exists, err := util.IsExist(repoDir); err != nil { return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err) } else if exists { @@ -184,7 +181,7 @@ func runDumpRepository(ctx *cli.Context) error { if err := migrations.DumpRepository( context.Background(), repoDir, - ctx.String("owner_name"), + cmd.String("owner_name"), opts, ); err != nil { log.Fatal("Failed to dump repository: %v", err) diff --git a/cmd/embedded.go b/cmd/embedded.go index 9f03f7be7c..6a2fa07a93 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -19,7 +20,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdEmbedded represents the available extract sub-command. @@ -28,7 +29,7 @@ var ( Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdList, subcmdView, subcmdExtract, @@ -100,7 +101,7 @@ type assetFile struct { path string } -func initEmbeddedExtractor(c *cli.Context) error { +func initEmbeddedExtractor(c *cli.Command) error { setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) patterns, err := compileCollectPatterns(c.Args().Slice()) @@ -115,31 +116,31 @@ func initEmbeddedExtractor(c *cli.Context) error { return nil } -func runList(c *cli.Context) error { +func runList(_ context.Context, c *cli.Command) error { if err := runListDo(c); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runView(c *cli.Context) error { +func runView(_ context.Context, c *cli.Command) error { if err := runViewDo(c); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runExtract(c *cli.Context) error { +func runExtract(_ context.Context, c *cli.Command) error { if err := runExtractDo(c); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) return err } return nil } -func runListDo(c *cli.Context) error { +func runListDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -151,7 +152,7 @@ func runListDo(c *cli.Context) error { return nil } -func runViewDo(c *cli.Context) error { +func runViewDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -174,7 +175,7 @@ func runViewDo(c *cli.Context) error { return nil } -func runExtractDo(c *cli.Context) error { +func runExtractDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -216,7 +217,7 @@ func runExtractDo(c *cli.Context) error { for _, a := range matchedAssetFiles { if err := extractAsset(destdir, a, overwrite, rename); err != nil { // Non-fatal error - fmt.Fprintf(os.Stderr, "%s: %v", a.path, err) + _, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err) } } @@ -271,7 +272,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error { return nil } -func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) { +func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { fs := assetfs.Layered(layer) files, err := fs.ListAllFiles(".", true) if err != nil { diff --git a/cmd/generate.go b/cmd/generate.go index 90b32ecaf0..cf491604ef 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -5,13 +5,14 @@ package cmd import ( + "context" "fmt" "os" "code.gitea.io/gitea/modules/generate" "github.com/mattn/go-isatty" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -19,7 +20,7 @@ var ( CmdGenerate = &cli.Command{ Name: "generate", Usage: "Generate Gitea's secrets/keys/tokens", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdSecret, }, } @@ -27,7 +28,7 @@ var ( subcmdSecret = &cli.Command{ Name: "secret", Usage: "Generate a secret token", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdGenerateInternalToken, microcmdGenerateLfsJwtSecret, microcmdGenerateSecretKey, @@ -54,7 +55,7 @@ var ( } ) -func runGenerateInternalToken(c *cli.Context) error { +func runGenerateInternalToken(_ context.Context, c *cli.Command) error { internalToken, err := generate.NewInternalToken() if err != nil { return err @@ -69,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error { return nil } -func runGenerateLfsJwtSecret(c *cli.Context) error { +func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() if err != nil { return err @@ -84,7 +85,7 @@ func runGenerateLfsJwtSecret(c *cli.Context) error { return nil } -func runGenerateSecretKey(c *cli.Context) error { +func runGenerateSecretKey(_ context.Context, c *cli.Command) error { secretKey, err := generate.NewSecretKey() if err != nil { return err diff --git a/cmd/hook.go b/cmd/hook.go index 6f0aa5a203..4621137e01 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -20,7 +20,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( @@ -34,7 +34,7 @@ var ( Usage: "(internal) Should only be called by Git", Description: "Delegate commands to corresponding Git hooks", Before: PrepareConsoleLoggerLevel(log.FATAL), - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdHookPreReceive, subcmdHookUpdate, subcmdHookPostReceive, @@ -161,12 +161,10 @@ func (n *nilWriter) WriteString(s string) (int, error) { return len(s), nil } -func runHookPreReceive(c *cli.Context) error { +func runHookPreReceive(ctx context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } - ctx, cancel := installSignals() - defer cancel() setup(ctx, c.Bool("debug")) @@ -292,7 +290,7 @@ Gitea or set your environment appropriately.`, "") // runHookUpdate avoid to do heavy operations on update hook because it will be // invoked for every ref update which does not like pre-receive and post-receive -func runHookUpdate(c *cli.Context) error { +func runHookUpdate(_ context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } @@ -309,15 +307,12 @@ func runHookUpdate(c *cli.Context) error { return nil } -func runHookPostReceive(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runHookPostReceive(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { - return fmt.Errorf("Failed to call 'git update-server-info': %w", err) + return fmt.Errorf("failed to call 'git update-server-info': %w", err) } // Now if we're an internal don't do anything else @@ -496,10 +491,7 @@ func pushOptions() map[string]string { return opts } -func runHookProcReceive(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runHookProcReceive(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { diff --git a/cmd/keys.go b/cmd/keys.go index 7fdbe16119..8710756a81 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdKeys represents the available keys sub-command @@ -49,7 +50,7 @@ var CmdKeys = &cli.Command{ }, } -func runKeys(c *cli.Context) error { +func runKeys(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("No username provided") } @@ -68,9 +69,6 @@ func runKeys(c *cli.Context) error { return errors.New("No key type and content provided") } - ctx, cancel := installSignals() - defer cancel() - setup(ctx, c.Bool("debug")) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) @@ -78,6 +76,6 @@ func runKeys(c *cli.Context) error { if extra.Error != nil { return extra.Error } - _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text)) + _, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) return nil } diff --git a/cmd/mailer.go b/cmd/mailer.go index 0c5f2c8c8d..72bd8e5601 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -4,24 +4,18 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -func runSendMail(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runSendMail(ctx context.Context, c *cli.Command) error { setting.MustInstalled() - if err := argsSet(c, "title"); err != nil { - return err - } - subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") diff --git a/cmd/main.go b/cmd/main.go index 7251bd09a3..128b8776b4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "fmt" "os" "strings" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdHelp is our own help subcommand with more information @@ -22,18 +23,18 @@ func cmdHelp() *cli.Command { Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *cli.Context) (err error) { - lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil} + Action: func(ctx context.Context, c *cli.Command) (err error) { + lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea targetCmdIdx := 0 - if c.Command.Name == "help" { + if c.Name == "help" { targetCmdIdx = 1 } - if lineage[targetCmdIdx+1].Command != nil { - err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name) + if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() { + err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */) } else { err = cli.ShowAppHelp(c) } - _, _ = fmt.Fprintf(c.App.Writer, ` + _, _ = fmt.Fprintf(c.Root().Writer, ` DEFAULT CONFIGURATION: AppPath: %s WorkPath: %s @@ -74,25 +75,25 @@ func appGlobalFlags() []cli.Flag { } } -func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) { - command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...) +func prepareSubcommandWithGlobalFlags(command *cli.Command) { + command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...) command.Action = prepareWorkPathAndCustomConf(command.Action) command.HideHelp = true if command.Name != "help" { - command.Subcommands = append(command.Subcommands, cmdHelp()) + command.Commands = append(command.Commands, cmdHelp()) } - for i := range command.Subcommands { - prepareSubcommandWithConfig(command.Subcommands[i], globalFlags) + for i := range command.Commands { + prepareSubcommandWithGlobalFlags(command.Commands[i]) } } // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times -func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error { - return func(ctx *cli.Context) error { +func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error { + return func(ctx context.Context, cmd *cli.Command) error { var args setting.ArgWorkPathAndCustomConf // from children to parent, check the global flags - for _, curCtx := range ctx.Lineage() { + for _, curCtx := range cmd.Lineage() { if curCtx.IsSet("work-path") && args.WorkPath == "" { args.WorkPath = curCtx.String("work-path") } @@ -104,11 +105,11 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) } } setting.InitWorkPathAndCommonConfig(os.Getenv, args) - if ctx.Bool("help") || action == nil { + if cmd.Bool("help") || action == nil { // the default behavior of "urfave/cli": "nil action" means "show help" - return cmdHelp().Action(ctx) + return cmdHelp().Action(ctx, cmd) } - return action(ctx) + return action(ctx, cmd) } } @@ -117,14 +118,13 @@ type AppVersion struct { Extra string } -func NewMainApp(appVer AppVersion) *cli.App { - app := cli.NewApp() - app.Name = "Gitea" - app.HelpName = "gitea" +func NewMainApp(appVer AppVersion) *cli.Command { + app := &cli.Command{} + app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]" app.Usage = "A painless self-hosted Git service" app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` app.Version = appVer.Version + appVer.Extra - app.EnableBashCompletion = true + app.EnableShellCompletion = true // these sub-commands need to use config file subCmdWithConfig := []*cli.Command{ @@ -147,20 +147,19 @@ func NewMainApp(appVer AppVersion) *cli.App { // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ - CmdCert, + cmdCert(), CmdGenerate, CmdDocs, } app.DefaultCommand = CmdWeb.Name - globalFlags := appGlobalFlags() app.Flags = append(app.Flags, cli.VersionFlag) - app.Flags = append(app.Flags, globalFlags...) + app.Flags = append(app.Flags, appGlobalFlags()...) app.HideHelp = true // use our own help action to show helps (with more information like default config) app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { - prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags) + prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) } app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdStandalone...) @@ -169,8 +168,10 @@ func NewMainApp(appVer AppVersion) *cli.App { return app } -func RunMainApp(app *cli.App, args ...string) error { - err := app.Run(args) +func RunMainApp(app *cli.Command, args ...string) error { + ctx, cancel := installSignals() + defer cancel() + err := app.Run(ctx, args) if err == nil { return nil } diff --git a/cmd/main_test.go b/cmd/main_test.go index 9573cacbd4..7dfa87a0ef 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "io" @@ -16,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestMain(m *testing.M) { @@ -27,10 +28,10 @@ func makePathOutput(workPath, customPath, customConf string) string { return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) } -func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App { +func newTestApp(testCmdAction cli.ActionFunc) *cli.Command { app := NewMainApp(AppVersion{}) testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} - prepareSubcommandWithConfig(testCmd, appGlobalFlags()) + prepareSubcommandWithGlobalFlags(testCmd) app.Commands = append(app.Commands, testCmd) app.DefaultCommand = testCmd.Name return app @@ -42,7 +43,7 @@ type runResult struct { ExitCode int } -func runTestApp(app *cli.App, args ...string) (runResult, error) { +func runTestApp(app *cli.Command, args ...string) (runResult, error) { outBuf := new(strings.Builder) errBuf := new(strings.Builder) app.Writer = outBuf @@ -65,7 +66,7 @@ func TestCliCmd(t *testing.T) { defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") cli.CommandHelpTemplate = "(command help template)" - cli.AppHelpTemplate = "(app help template)" + cli.RootCommandHelpTemplate = "(app help template)" cli.SubcommandHelpTemplate = "(subcommand help template)" cases := []struct { @@ -109,12 +110,12 @@ func TestCliCmd(t *testing.T) { }, } - app := newTestApp(func(ctx *cli.Context) error { - _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil - }) for _, c := range cases { t.Run(c.cmd, func(t *testing.T) { + app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }) for k, v := range c.env { t.Setenv(k, v) } @@ -128,28 +129,28 @@ func TestCliCmd(t *testing.T) { } func TestCliCmdError(t *testing.T) { - app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") }) + app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }) r, err := runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "Command error: normal error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 2, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "exit error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) - assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) - assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... + assert.Empty(t, r.Stdout) + assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.NoError(t, err) assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called diff --git a/cmd/manager.go b/cmd/manager.go index bd2da8edc7..f0935ea065 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "os" "time" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -18,7 +19,7 @@ var ( Name: "manager", Usage: "Manage the running gitea process", Description: "This is a command for managing the running gitea process", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdShutdown, subcmdRestart, subcmdReloadTemplates, @@ -108,46 +109,31 @@ var ( } ) -func runShutdown(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runShutdown(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Shutdown(ctx) return handleCliResponseExtra(extra) } -func runRestart(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runRestart(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Restart(ctx) return handleCliResponseExtra(extra) } -func runReloadTemplates(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runReloadTemplates(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.ReloadTemplates(ctx) return handleCliResponseExtra(extra) } -func runFlushQueues(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runFlushQueues(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) return handleCliResponseExtra(extra) } -func runProcesses(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runProcesses(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) return handleCliResponseExtra(extra) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c2ae25ec57..c83073e9c6 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -60,7 +61,7 @@ var ( subcmdLogging = &cli.Command{ Name: "logging", Usage: "Adjust logging commands", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "pause", Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", @@ -104,7 +105,7 @@ var ( }, { Name: "add", Usage: "Add a logger", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "file", Usage: "Add a file logger", @@ -195,10 +196,7 @@ var ( } ) -func runRemoveLogger(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runRemoveLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) logger := c.String("logger") if len(logger) == 0 { @@ -210,10 +208,7 @@ func runRemoveLogger(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runAddConnLogger(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runAddConnLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) vals := map[string]any{} mode := "conn" @@ -237,13 +232,10 @@ func runAddConnLogger(c *cli.Context) error { if c.IsSet("reconnect-on-message") { vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func runAddFileLogger(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runAddFileLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) vals := map[string]any{} mode := "file" @@ -270,10 +262,10 @@ func runAddFileLogger(c *cli.Context) error { if c.IsSet("compression-level") { vals["compressionLevel"] = c.Int("compression-level") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { +func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { if len(c.String("level")) > 0 { vals["level"] = log.LevelFromString(c.String("level")).String() } @@ -300,46 +292,33 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { if c.IsSet("writer") { writer = c.String("writer") } - ctx, cancel := installSignals() - defer cancel() extra := private.AddLogger(ctx, logger, writer, mode, vals) return handleCliResponseExtra(extra) } -func runPauseLogging(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runPauseLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.PauseLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runResumeLogging(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runResumeLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.ResumeLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runReleaseReopenLogging(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.ReleaseReopenLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runSetLogSQL(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() +func runSetLogSQL(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.SetLogSQL(ctx, !c.Bool("off")) diff --git a/cmd/migrate.go b/cmd/migrate.go index 25d8b50c45..e24dc9e572 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/versioned_migration" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdMigrate represents the available migrate sub-command. @@ -22,11 +22,8 @@ var CmdMigrate = &cli.Command{ Action: runMigrate, } -func runMigrate(ctx *cli.Context) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runMigrate(ctx context.Context, c *cli.Command) error { + if err := initDB(ctx); err != nil { return err } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index f9ed140395..2c63e15f50 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -22,7 +22,7 @@ import ( "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/services/versioned_migration" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdMigrateStorage represents the available migrate storage sub-command. @@ -213,11 +213,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora }) } -func runMigrateStorage(ctx *cli.Context) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runMigrateStorage(ctx context.Context, cmd *cli.Command) error { + if err := initDB(ctx); err != nil { return err } @@ -238,51 +235,51 @@ func runMigrateStorage(ctx *cli.Context) error { var dstStorage storage.ObjectStorage var err error - switch strings.ToLower(ctx.String("storage")) { + switch strings.ToLower(cmd.String("storage")) { case "": fallthrough case string(setting.LocalStorageType): - p := ctx.String("path") + p := cmd.String("path") if p == "" { log.Fatal("Path must be given when storage is local") return nil } dstStorage, err = storage.NewLocalStorage( - stdCtx, + ctx, &setting.Storage{ Path: p, }) case string(setting.MinioStorageType): dstStorage, err = storage.NewMinioStorage( - stdCtx, + ctx, &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ - Endpoint: ctx.String("minio-endpoint"), - AccessKeyID: ctx.String("minio-access-key-id"), - SecretAccessKey: ctx.String("minio-secret-access-key"), - Bucket: ctx.String("minio-bucket"), - Location: ctx.String("minio-location"), - BasePath: ctx.String("minio-base-path"), - UseSSL: ctx.Bool("minio-use-ssl"), - InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"), - ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"), - BucketLookUpType: ctx.String("minio-bucket-lookup-type"), + Endpoint: cmd.String("minio-endpoint"), + AccessKeyID: cmd.String("minio-access-key-id"), + SecretAccessKey: cmd.String("minio-secret-access-key"), + Bucket: cmd.String("minio-bucket"), + Location: cmd.String("minio-location"), + BasePath: cmd.String("minio-base-path"), + UseSSL: cmd.Bool("minio-use-ssl"), + InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"), + ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"), + BucketLookUpType: cmd.String("minio-bucket-lookup-type"), }, }) case string(setting.AzureBlobStorageType): dstStorage, err = storage.NewAzureBlobStorage( - stdCtx, + ctx, &setting.Storage{ AzureBlobConfig: setting.AzureBlobStorageConfig{ - Endpoint: ctx.String("azureblob-endpoint"), - AccountName: ctx.String("azureblob-account-name"), - AccountKey: ctx.String("azureblob-account-key"), - Container: ctx.String("azureblob-container"), - BasePath: ctx.String("azureblob-base-path"), + Endpoint: cmd.String("azureblob-endpoint"), + AccountName: cmd.String("azureblob-account-name"), + AccountKey: cmd.String("azureblob-account-key"), + Container: cmd.String("azureblob-container"), + BasePath: cmd.String("azureblob-base-path"), }, }) default: - return fmt.Errorf("unsupported storage type: %s", ctx.String("storage")) + return fmt.Errorf("unsupported storage type: %s", cmd.String("storage")) } if err != nil { return err @@ -299,14 +296,14 @@ func runMigrateStorage(ctx *cli.Context) error { "actions-artifacts": migrateActionsArtifacts, } - tp := strings.ToLower(ctx.String("type")) + tp := strings.ToLower(cmd.String("type")) if m, ok := migratedMethods[tp]; ok { - if err := m(stdCtx, dstStorage); err != nil { + if err := m(ctx, dstStorage); err != nil { return err } log.Info("%s files have successfully been copied to the new storage.", tp) return nil } - return fmt.Errorf("unsupported storage: %s", ctx.String("type")) + return fmt.Errorf("unsupported storage: %s", cmd.String("type")) } diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 37b32aa304..c61f5a582e 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "strings" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdRestoreRepository represents the available restore a repository sub-command. @@ -48,10 +49,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runRestoreRepository(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runRestoreRepository(ctx context.Context, c *cli.Command) error { setting.MustInstalled() var units []string if s := c.String("units"); s != "" { diff --git a/cmd/serv.go b/cmd/serv.go index 26a3af50f3..e4434450d6 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -33,7 +33,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/kballard/go-shellquote" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdServ represents the available serv sub-command. @@ -152,10 +152,7 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC return "Bearer " + tokenString, nil } -func runServ(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runServ(ctx context.Context, c *cli.Command) error { // FIXME: This needs to internationalised setup(ctx, c.Bool("debug")) diff --git a/cmd/web.go b/cmd/web.go index e47b171455..39e336fe54 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -28,7 +28,7 @@ import ( "code.gitea.io/gitea/routers/install" "github.com/felixge/fgprof" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // PIDFile could be set from build tag @@ -130,19 +130,19 @@ func showWebStartupMessage(msg string) { } } -func serveInstall(ctx *cli.Context) error { +func serveInstall(cmd *cli.Command) error { showWebStartupMessage("Prepare to run install page") routers.InitWebInstallPage(graceful.GetManager().HammerContext()) // Flag for port number in case first time run conflict - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { + if cmd.IsSet("port") { + if err := setPort(cmd.String("port")); err != nil { return err } } - if ctx.IsSet("install-port") { - if err := setPort(ctx.String("install-port")); err != nil { + if cmd.IsSet("install-port") { + if err := setPort(cmd.String("install-port")); err != nil { return err } } @@ -163,7 +163,7 @@ func serveInstall(ctx *cli.Context) error { return nil } -func serveInstalled(ctx *cli.Context) error { +func serveInstalled(c *cli.Command) error { setting.InitCfgProvider(setting.CustomConf) setting.LoadCommonSettings() setting.MustInstalled() @@ -218,8 +218,8 @@ func serveInstalled(ctx *cli.Context) error { setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour) // Override the provided port number within the configuration - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { + if c.IsSet("port") { + if err := setPort(c.String("port")); err != nil { return err } } @@ -244,7 +244,7 @@ func servePprof() { finished() } -func runWeb(ctx *cli.Context) error { +func runWeb(_ context.Context, cmd *cli.Command) error { defer func() { if panicked := recover(); panicked != nil { log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) @@ -262,12 +262,12 @@ func runWeb(ctx *cli.Context) error { } // Set pid file setting - if ctx.IsSet("pid") { - createPIDFile(ctx.String("pid")) + if cmd.IsSet("pid") { + createPIDFile(cmd.String("pid")) } if !setting.InstallLock { - if err := serveInstall(ctx); err != nil { + if err := serveInstall(cmd); err != nil { return err } } else { @@ -278,7 +278,7 @@ func runWeb(ctx *cli.Context) error { go servePprof() } - return serveInstalled(ctx) + return serveInstalled(cmd) } func setPort(port string) error { diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index 44e4eacf90..630bd77531 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -12,21 +12,19 @@ import ( "net/http" "os" "os/exec" - "os/signal" "path" "strconv" "strings" - "syscall" "github.com/google/go-github/v71/github" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "gopkg.in/yaml.v3" ) const defaultVersion = "v1.18" // to backport to func main() { - app := cli.NewApp() + app := &cli.Command{} app.Name = "backport" app.Usage = "Backport provided PR-number on to the current or previous released version" app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version` @@ -91,7 +89,7 @@ func main() { Usage: "Set this flag to continue from a git cherry-pick that has broken", }, } - cli.AppHelpTemplate = `NAME: + cli.RootCommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} @@ -105,16 +103,12 @@ OPTIONS: ` app.Action = runBackport - - if err := app.Run(os.Args); err != nil { + if err := app.Run(context.Background(), os.Args); err != nil { fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) } } -func runBackport(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - +func runBackport(ctx context.Context, c *cli.Command) error { continuing := c.Bool("continue") var pr string @@ -460,25 +454,3 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string, return "", nil } - -func installSignals() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) - go func() { - // install notify - signalChannel := make(chan os.Signal, 1) - - signal.Notify( - signalChannel, - syscall.SIGINT, - syscall.SIGTERM, - ) - select { - case <-signalChannel: - case <-ctx.Done(): - } - cancel() - signal.Reset() - }() - - return ctx, cancel -} diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index a7d7a6d293..5eb576c6fe 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -4,16 +4,17 @@ package main import ( + "context" "os" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func main() { - app := cli.NewApp() + app := cli.Command{} app.Name = "environment-to-ini" app.Usage = "Use provided environment to update configuration ini" app.Description = `As a helper to allow docker users to update the gitea configuration @@ -72,13 +73,13 @@ func main() { }, } app.Action = runEnvironmentToIni - err := app.Run(os.Args) + err := app.Run(context.Background(), os.Args) if err != nil { log.Fatal("Failed to run app with %s: %v", os.Args, err) } } -func runEnvironmentToIni(c *cli.Context) error { +func runEnvironmentToIni(_ context.Context, c *cli.Command) error { // the config system may change the environment variables, so get a copy first, to be used later env := append([]string{}, os.Environ()...) setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index e9a378e877..aa2fcee765 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1186,17 +1186,24 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey +;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey +;; Depending on the value of SIGNING_FORMAT this is either: +;; - openpgp: the GPG key ID +;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password ;; run in the context of the RUN_USER ;; Switch to none to stop signing completely ;SIGNING_KEY = default ;; -;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer. +;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format. ;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to -;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden +;; the results of git config --get user.name, git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden ;; by setting the SIGNING_KEY ID to the correct ID.) ;SIGNING_NAME = ;SIGNING_EMAIL = +;; SIGNING_FORMAT can be one of: +;; - openpgp (default): use GPG to sign commits +;; - ssh: use SSH to sign commits +;SIGNING_FORMAT = openpgp ;; ;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter ;DEFAULT_TRUST_MODEL = collaborator @@ -1223,6 +1230,13 @@ LEVEL = Info ;; - commitssigned: require that all the commits in the head branch are signed. ;; - approved: only sign when merging an approved pr to a protected branch ;MERGES = pubkey, twofa, basesigned, commitssigned +;; +;; Determines which additional ssh keys are trusted for all signed commits regardless of the user +;; This is useful for ssh signing key rotation. +;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value. +;; Multiple keys should be comma separated. +;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>". +;TRUSTED_SSH_KEYS = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1,6 +1,6 @@ module code.gitea.io/gitea -go 1.24.2 +go 1.24.4 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: @@ -60,7 +60,6 @@ require ( github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-sql-driver/mysql v1.9.2 - github.com/go-swagger/go-swagger v0.31.0 github.com/go-webauthn/webauthn v0.12.3 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f @@ -105,12 +104,12 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/sassoftware/go-rpmutils v0.4.0 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 - github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 github.com/ulikunitz/xz v0.5.12 - github.com/urfave/cli/v2 v2.27.6 + github.com/urfave/cli-docs/v3 v3.0.0-alpha6 + github.com/urfave/cli/v3 v3.3.3 github.com/wneessen/go-mail v0.6.2 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yohcop/openid-go v1.0.1 @@ -125,7 +124,6 @@ require ( golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 golang.org/x/text v0.26.0 - golang.org/x/tools v0.33.0 google.golang.org/grpc v1.72.0 google.golang.org/protobuf v1.36.6 gopkg.in/ini.v1 v1.67.0 @@ -143,15 +141,11 @@ require ( git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect - github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect @@ -186,7 +180,7 @@ require ( github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/goutils v0.1.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect @@ -194,7 +188,6 @@ require ( github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect @@ -203,18 +196,6 @@ require ( github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-openapi/analysis v0.23.0 // indirect - github.com/go-openapi/errors v0.22.1 // indirect - github.com/go-openapi/inflect v0.21.2 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/loads v0.22.0 // indirect - github.com/go-openapi/runtime v0.28.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/strfmt v0.23.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/validate v0.24.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-webauthn/x v0.1.20 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -228,7 +209,6 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-tpm v0.9.3 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -236,12 +216,9 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jessevdk/go-flags v1.6.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/libdns/libdns v1.0.0-beta.1 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/markbates/going v1.0.3 // indirect @@ -252,19 +229,15 @@ require ( github.com/miekg/dns v1.1.65 // indirect github.com/minio/crc64nvme v1.0.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -273,22 +246,11 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/rhysd/actionlint v1.7.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.9.0 // indirect - github.com/shopspring/decimal v1.4.0 // indirect - github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/spf13/viper v1.20.1 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/valyala/fastjson v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -296,11 +258,9 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.0 // indirect - go.mongodb.org/mongo-driver v1.17.3 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -308,6 +268,7 @@ require ( golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -315,8 +276,6 @@ require ( replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 -replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 - replace github.com/nektos/act => gitea.com/gitea/act v0.261.6 // TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why @@ -62,12 +62,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= -github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -103,8 +97,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= @@ -223,8 +215,8 @@ github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9B github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -274,12 +266,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -325,28 +313,6 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= -github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= -github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU= -github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= -github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM= -github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= -github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= -github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= -github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= -github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= -github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= @@ -357,13 +323,9 @@ github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkv github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-swagger/go-swagger v0.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc= -github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE= github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY= github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw= @@ -446,8 +408,6 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= -github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= -github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY= @@ -497,8 +457,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= -github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw= github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -540,8 +498,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI= -github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= @@ -577,14 +533,10 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc= github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -607,8 +559,6 @@ github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= @@ -629,8 +579,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -674,7 +622,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -682,8 +629,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= -github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= @@ -692,10 +637,6 @@ github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLS github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= -github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -707,22 +648,12 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= @@ -745,13 +676,9 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -761,8 +688,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= -github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= +github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= +github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -782,8 +711,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= @@ -809,8 +736,6 @@ gitlab.com/gitlab-org/api/client-go v0.127.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAj go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= -go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= -go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -21,7 +21,7 @@ import ( _ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/orgmode" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // these flags will be set by the build flags diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go index a400f7b908..ae0fbf0abd 100644 --- a/models/renderhelper/repo_comment.go +++ b/models/renderhelper/repo_comment.go @@ -48,10 +48,7 @@ type RepoCommentOptions struct { } func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repository, opts ...RepoCommentOptions) *markup.RenderContext { - helper := &RepoComment{ - repoLink: repo.Link(), - opts: util.OptionalArg(opts), - } + helper := &RepoComment{opts: util.OptionalArg(opts)} rctx := markup.NewRenderContext(ctx) helper.ctx = rctx var metas map[string]string @@ -60,15 +57,16 @@ func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repositor helper.commitChecker = newCommitChecker(ctx, repo) metas = repo.ComposeCommentMetas(ctx) } else { - // this is almost dead code, only to pass the incorrect tests - helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) - rctx = rctx.WithMetas(map[string]string{ - "user": helper.opts.DeprecatedOwnerName, - "repo": helper.opts.DeprecatedRepoName, - - "markdownNewLineHardBreak": "true", - "markupAllowShortIssuePattern": "true", - }) + // repo can be nil when rendering a commit message in user's dashboard feedback whose repository has been deleted + metas = map[string]string{} + if helper.opts.DeprecatedOwnerName != "" { + // this is almost dead code, only to pass the incorrect tests + helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) + metas["user"] = helper.opts.DeprecatedOwnerName + metas["repo"] = helper.opts.DeprecatedRepoName + } + metas["markdownNewLineHardBreak"] = "true" + metas["markupAllowShortIssuePattern"] = "true" } metas["footnoteContextId"] = helper.opts.FootnoteContextID rctx = rctx.WithMetas(metas).WithHelper(helper) diff --git a/models/renderhelper/repo_comment_test.go b/models/renderhelper/repo_comment_test.go index 776152db96..3b13bff73c 100644 --- a/models/renderhelper/repo_comment_test.go +++ b/models/renderhelper/repo_comment_test.go @@ -72,4 +72,11 @@ func TestRepoComment(t *testing.T) { <a href="/user2/repo1/commit/1234/image" target="_blank" rel="nofollow noopener"><img src="/user2/repo1/commit/1234/image" alt="./image"/></a></p> `, rendered) }) + + t.Run("NoRepo", func(t *testing.T) { + rctx := NewRenderContextRepoComment(t.Context(), nil).WithMarkupType(markdown.MarkupName) + rendered, err := markup.RenderString(rctx, "any") + assert.NoError(t, err) + assert.Equal(t, "<p>any</p>\n", rendered) + }) } diff --git a/modules/assetfs/embed.go b/modules/assetfs/embed.go new file mode 100644 index 0000000000..95176372d1 --- /dev/null +++ b/modules/assetfs/embed.go @@ -0,0 +1,375 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package assetfs + +import ( + "bytes" + "compress/gzip" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" +) + +type EmbeddedFile interface { + io.ReadSeeker + fs.ReadDirFile + ReadDir(n int) ([]fs.DirEntry, error) +} + +type EmbeddedFileInfo interface { + fs.FileInfo + fs.DirEntry + GetGzipContent() ([]byte, bool) +} + +type decompressor interface { + io.Reader + Close() error + Reset(io.Reader) error +} + +type embeddedFileInfo struct { + fs *embeddedFS + fullName string + data []byte + + BaseName string `json:"n"` + OriginSize int64 `json:"s,omitempty"` + DataBegin int64 `json:"b,omitempty"` + DataLen int64 `json:"l,omitempty"` + Children []*embeddedFileInfo `json:"c,omitempty"` +} + +func (fi *embeddedFileInfo) GetGzipContent() ([]byte, bool) { + // when generating the bindata, if the compressed data equals or is larger than the original data, we store the original data + if fi.DataLen == fi.OriginSize { + return nil, false + } + return fi.data, true +} + +type EmbeddedFileBase struct { + info *embeddedFileInfo + dataReader io.ReadSeeker + seekPos int64 +} + +func (f *EmbeddedFileBase) ReadDir(n int) ([]fs.DirEntry, error) { + // this method is used to satisfy the "func (f ioFile) ReadDir(...)" in httpfs + l, err := f.info.fs.ReadDir(f.info.fullName) + if err != nil { + return nil, err + } + if n < 0 || n > len(l) { + return l, nil + } + return l[:n], nil +} + +type EmbeddedOriginFile struct { + EmbeddedFileBase +} + +type EmbeddedCompressedFile struct { + EmbeddedFileBase + decompressor decompressor + decompressorPos int64 +} + +type embeddedFS struct { + meta func() *EmbeddedMeta + + files map[string]*embeddedFileInfo + filesMu sync.RWMutex + + data []byte +} + +type EmbeddedMeta struct { + Root *embeddedFileInfo +} + +func NewEmbeddedFS(data []byte) fs.ReadDirFS { + efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)} + efs.meta = sync.OnceValue(func() *EmbeddedMeta { + var meta EmbeddedMeta + p := bytes.LastIndexByte(data, '\n') + if p < 0 { + return &meta + } + if err := json.Unmarshal(data[p+1:], &meta); err != nil { + panic("embedded file is not valid") + } + return &meta + }) + return efs +} + +var _ fs.ReadDirFS = (*embeddedFS)(nil) + +func (e *embeddedFS) ReadDir(name string) (l []fs.DirEntry, err error) { + fi, err := e.getFileInfo(name) + if err != nil { + return nil, err + } + if !fi.IsDir() { + return nil, fs.ErrNotExist + } + l = make([]fs.DirEntry, len(fi.Children)) + for i, child := range fi.Children { + l[i], err = e.getFileInfo(name + "/" + child.BaseName) + if err != nil { + return nil, err + } + } + return l, nil +} + +func (e *embeddedFS) getFileInfo(fullName string) (*embeddedFileInfo, error) { + // no need to do heavy "path.Clean()" because we don't want to support "foo/../bar" or absolute paths + fullName = strings.TrimPrefix(fullName, "./") + if fullName == "" { + fullName = "." + } + + e.filesMu.RLock() + fi := e.files[fullName] + e.filesMu.RUnlock() + if fi != nil { + return fi, nil + } + + fields := strings.Split(fullName, "/") + fi = e.meta().Root + if fullName != "." { + found := true + for _, field := range fields { + for _, child := range fi.Children { + if found = child.BaseName == field; found { + fi = child + break + } + } + if !found { + return nil, fs.ErrNotExist + } + } + } + + e.filesMu.Lock() + defer e.filesMu.Unlock() + if fi != nil { + fi.fs = e + fi.fullName = fullName + fi.data = e.data[fi.DataBegin : fi.DataBegin+fi.DataLen] + e.files[fullName] = fi // do not cache nil, otherwise keeping accessing random non-existing file will cause OOM + return fi, nil + } + return nil, fs.ErrNotExist +} + +func (e *embeddedFS) Open(name string) (fs.File, error) { + info, err := e.getFileInfo(name) + if err != nil { + return nil, err + } + base := EmbeddedFileBase{info: info} + base.dataReader = bytes.NewReader(base.info.data) + if info.DataLen != info.OriginSize { + decomp, err := gzip.NewReader(base.dataReader) + if err != nil { + return nil, err + } + return &EmbeddedCompressedFile{EmbeddedFileBase: base, decompressor: decomp}, nil + } + return &EmbeddedOriginFile{base}, nil +} + +var ( + _ EmbeddedFileInfo = (*embeddedFileInfo)(nil) + _ EmbeddedFile = (*EmbeddedOriginFile)(nil) + _ EmbeddedFile = (*EmbeddedCompressedFile)(nil) +) + +func (f *EmbeddedOriginFile) Read(p []byte) (n int, err error) { + return f.dataReader.Read(p) +} + +func (f *EmbeddedCompressedFile) Read(p []byte) (n int, err error) { + if f.decompressorPos > f.seekPos { + if err = f.decompressor.Reset(bytes.NewReader(f.info.data)); err != nil { + return 0, err + } + f.decompressorPos = 0 + } + if f.decompressorPos < f.seekPos { + if _, err = io.CopyN(io.Discard, f.decompressor, f.seekPos-f.decompressorPos); err != nil { + return 0, err + } + f.decompressorPos = f.seekPos + } + n, err = f.decompressor.Read(p) + f.decompressorPos += int64(n) + f.seekPos = f.decompressorPos + return n, err +} + +func (f *EmbeddedFileBase) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + f.seekPos = offset + case io.SeekCurrent: + f.seekPos += offset + case io.SeekEnd: + f.seekPos = f.info.OriginSize + offset + } + return f.seekPos, nil +} + +func (f *EmbeddedFileBase) Stat() (fs.FileInfo, error) { + return f.info, nil +} + +func (f *EmbeddedOriginFile) Close() error { + return nil +} + +func (f *EmbeddedCompressedFile) Close() error { + return f.decompressor.Close() +} + +func (fi *embeddedFileInfo) Name() string { + return fi.BaseName +} + +func (fi *embeddedFileInfo) Size() int64 { + return fi.OriginSize +} + +func (fi *embeddedFileInfo) Mode() fs.FileMode { + return util.Iif(fi.IsDir(), fs.ModeDir|0o555, 0o444) +} + +func (fi *embeddedFileInfo) ModTime() time.Time { + return getExecutableModTime() +} + +func (fi *embeddedFileInfo) IsDir() bool { + return fi.Children != nil +} + +func (fi *embeddedFileInfo) Sys() any { + return nil +} + +func (fi *embeddedFileInfo) Type() fs.FileMode { + return util.Iif(fi.IsDir(), fs.ModeDir, 0) +} + +func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) { + return fi, nil +} + +// getExecutableModTime returns the modification time of the executable file. +// In bindata, we can't use the ModTime of the files because we need to make the build reproducible +var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) { + exePath, err := os.Executable() + if err != nil { + return modTime + } + exePath, err = filepath.Abs(exePath) + if err != nil { + return modTime + } + exePath, err = filepath.EvalSymlinks(exePath) + if err != nil { + return modTime + } + st, err := os.Stat(exePath) + if err != nil { + return modTime + } + return st.ModTime() +}) + +func GenerateEmbedBindata(fsRootPath, outputFile string) error { + output, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer output.Close() + + meta := &EmbeddedMeta{} + meta.Root = &embeddedFileInfo{} + var outputOffset int64 + var embedFiles func(parent *embeddedFileInfo, fsPath, embedPath string) error + embedFiles = func(parent *embeddedFileInfo, fsPath, embedPath string) error { + dirEntries, err := os.ReadDir(fsPath) + if err != nil { + return err + } + for _, dirEntry := range dirEntries { + if err != nil { + return err + } + if dirEntry.IsDir() { + child := &embeddedFileInfo{ + BaseName: dirEntry.Name(), + Children: []*embeddedFileInfo{}, // non-nil means it's a directory + } + parent.Children = append(parent.Children, child) + if err = embedFiles(child, filepath.Join(fsPath, dirEntry.Name()), path.Join(embedPath, dirEntry.Name())); err != nil { + return err + } + } else { + data, err := os.ReadFile(filepath.Join(fsPath, dirEntry.Name())) + if err != nil { + return err + } + var compressed bytes.Buffer + gz, _ := gzip.NewWriterLevel(&compressed, gzip.BestCompression) + if _, err = gz.Write(data); err != nil { + return err + } + if err = gz.Close(); err != nil { + return err + } + + // only use the compressed data if it is smaller than the original data + outputBytes := util.Iif(len(compressed.Bytes()) < len(data), compressed.Bytes(), data) + child := &embeddedFileInfo{ + BaseName: dirEntry.Name(), + OriginSize: int64(len(data)), + DataBegin: outputOffset, + DataLen: int64(len(outputBytes)), + } + if _, err = output.Write(outputBytes); err != nil { + return err + } + outputOffset += child.DataLen + parent.Children = append(parent.Children, child) + } + } + return nil + } + + if err = embedFiles(meta.Root, fsRootPath, ""); err != nil { + return err + } + jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL + if err != nil { + return err + } + _, _ = output.Write([]byte{'\n'}) + _, err = output.Write(jsonBuf) + return err +} diff --git a/modules/assetfs/embed_test.go b/modules/assetfs/embed_test.go new file mode 100644 index 0000000000..06598da4c4 --- /dev/null +++ b/modules/assetfs/embed_test.go @@ -0,0 +1,98 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package assetfs + +import ( + "bytes" + "io/fs" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEmbed(t *testing.T) { + tmpDir := t.TempDir() + tmpDataDir := tmpDir + "/data" + _ = os.MkdirAll(tmpDataDir+"/foo/bar", 0o755) + _ = os.WriteFile(tmpDataDir+"/a.txt", []byte("a"), 0o644) + _ = os.WriteFile(tmpDataDir+"/foo/bar/b.txt", bytes.Repeat([]byte("a"), 1000), 0o644) + _ = os.WriteFile(tmpDataDir+"/foo/c.txt", []byte("c"), 0o644) + require.NoError(t, GenerateEmbedBindata(tmpDataDir, tmpDir+"/out.dat")) + + data, err := os.ReadFile(tmpDir + "/out.dat") + require.NoError(t, err) + efs := NewEmbeddedFS(data) + + // test a non-existing file + _, err = fs.ReadFile(efs, "not exist") + assert.ErrorIs(t, err, fs.ErrNotExist) + + // test a normal file (no compression) + content, err := fs.ReadFile(efs, "a.txt") + require.NoError(t, err) + assert.Equal(t, "a", string(content)) + fi, err := fs.Stat(efs, "a.txt") + require.NoError(t, err) + _, ok := fi.(EmbeddedFileInfo).GetGzipContent() + assert.False(t, ok) + + // test a compressed file + content, err = fs.ReadFile(efs, "foo/bar/b.txt") + require.NoError(t, err) + assert.Equal(t, bytes.Repeat([]byte("a"), 1000), content) + fi, err = fs.Stat(efs, "foo/bar/b.txt") + require.NoError(t, err) + assert.False(t, fi.Mode().IsDir()) + assert.True(t, fi.Mode().IsRegular()) + gzipContent, ok := fi.(EmbeddedFileInfo).GetGzipContent() + assert.True(t, ok) + assert.Greater(t, len(gzipContent), 1) + assert.Less(t, len(gzipContent), 1000) + + // test list root directory + entries, err := fs.ReadDir(efs, ".") + require.NoError(t, err) + assert.Len(t, entries, 2) + assert.Equal(t, "a.txt", entries[0].Name()) + assert.False(t, entries[0].IsDir()) + + // test list subdirectory + entries, err = fs.ReadDir(efs, "foo") + require.NoError(t, err) + require.Len(t, entries, 2) + assert.Equal(t, "bar", entries[0].Name()) + assert.True(t, entries[0].IsDir()) + assert.Equal(t, "c.txt", entries[1].Name()) + assert.False(t, entries[1].IsDir()) + + // test directory mode + fi, err = fs.Stat(efs, "foo") + require.NoError(t, err) + assert.True(t, fi.IsDir()) + assert.True(t, fi.Mode().IsDir()) + assert.False(t, fi.Mode().IsRegular()) + + // test httpfs + hfs := http.FS(efs) + hf, err := hfs.Open("foo/bar/b.txt") + require.NoError(t, err) + hi, err := hf.Stat() + require.NoError(t, err) + fiEmbedded, ok := hi.(EmbeddedFileInfo) + require.True(t, ok) + gzipContent, ok = fiEmbedded.GetGzipContent() + assert.True(t, ok) + assert.Greater(t, len(gzipContent), 1) + assert.Less(t, len(gzipContent), 1000) + + // test httpfs directory listing + hf, err = hfs.Open("foo") + require.NoError(t, err) + dirs, err := hf.Readdir(1) + require.NoError(t, err) + assert.Len(t, dirs, 1) +} diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 4f3811ba2b..ce55475bd9 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -52,8 +52,8 @@ func Local(name, base string, sub ...string) *Layer { } // Bindata returns a new Layer with the given name, it serves files from the given bindata asset. -func Bindata(name string, fs http.FileSystem) *Layer { - return &Layer{name: name, fs: fs} +func Bindata(name string, fs fs.FS) *Layer { + return &Layer{name: name, fs: http.FS(fs)} } // LayeredFS is a layered asset file-system. It works like http.FileSystem, but it can have multiple layers. diff --git a/modules/git/command.go b/modules/git/command.go index eaaa4969d0..22f1d02339 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -47,6 +47,7 @@ type Command struct { globalArgsLength int brokenArgs []string cmd *exec.Cmd // for debug purpose only + configArgs []string } func logArgSanitize(arg string) string { @@ -196,6 +197,16 @@ func (c *Command) AddDashesAndList(list ...string) *Command { return c } +func (c *Command) AddConfig(key, value string) *Command { + kv := key + "=" + value + if !isSafeArgumentValue(kv) { + c.brokenArgs = append(c.brokenArgs, key) + } else { + c.configArgs = append(c.configArgs, "-c", kv) + } + return c +} + // ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs // In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead func ToTrustedCmdArgs(args []string) TrustedCmdArgs { @@ -321,7 +332,7 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error { startTime := time.Now() - cmd := exec.CommandContext(ctx, c.prog, c.args...) + cmd := exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...) c.cmd = cmd // for debug purpose only if opts.Env == nil { cmd.Env = os.Environ() diff --git a/modules/git/key.go b/modules/git/key.go new file mode 100644 index 0000000000..2513c048b7 --- /dev/null +++ b/modules/git/key.go @@ -0,0 +1,15 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat +const ( + SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli + SigningKeyFormatSSH = "ssh" +) + +type SigningKey struct { + KeyID string + Format string +} diff --git a/modules/git/repo.go b/modules/git/repo.go index 45937a8d5f..239866fe9d 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -28,6 +28,7 @@ type GPGSettings struct { Email string Name string PublicKeyContent string + Format string } const prettyLogFormat = `--pretty=format:%H` diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go index 8f91b4dce5..0021a7bda7 100644 --- a/modules/git/repo_gpg.go +++ b/modules/git/repo_gpg.go @@ -6,6 +6,7 @@ package git import ( "fmt" + "os" "strings" "code.gitea.io/gitea/modules/process" @@ -13,6 +14,14 @@ import ( // LoadPublicKeyContent will load the key from gpg func (gpgSettings *GPGSettings) LoadPublicKeyContent() error { + if gpgSettings.Format == SigningKeyFormatSSH { + content, err := os.ReadFile(gpgSettings.KeyID) + if err != nil { + return fmt.Errorf("unable to read SSH public key file: %s, %w", gpgSettings.KeyID, err) + } + gpgSettings.PublicKeyContent = string(content) + return nil + } content, stderr, err := process.GetManager().Exec( "gpg -a --export", "gpg", "-a", "--export", gpgSettings.KeyID) @@ -44,6 +53,9 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, signingKey, _, _ := NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) gpgSettings.KeyID = strings.TrimSpace(signingKey) + format, _, _ := NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) + gpgSettings.Format = strings.TrimSpace(format) + defaultEmail, _, _ := NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) gpgSettings.Email = strings.TrimSpace(defaultEmail) diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 70e5aee023..309a73d759 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -15,7 +15,7 @@ import ( type CommitTreeOpts struct { Parents []string Message string - KeyID string + Key *SigningKey NoGPGSign bool AlwaysSign bool } @@ -43,8 +43,13 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt _, _ = messageBytes.WriteString(opts.Message) _, _ = messageBytes.WriteString("\n") - if opts.KeyID != "" || opts.AlwaysSign { - cmd.AddOptionFormat("-S%s", opts.KeyID) + if opts.Key != nil { + if opts.Key.Format != "" { + cmd.AddConfig("gpg.format", opts.Key.Format) + } + cmd.AddOptionFormat("-S%s", opts.Key.KeyID) + } else if opts.AlwaysSign { + cmd.AddOptionFormat("-S") } if opts.NoGPGSign { diff --git a/modules/markup/html.go b/modules/markup/html.go index d45153d95b..e8391341d9 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -86,8 +86,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) - // cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style") - v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style\b))`) + // cleans: "<foo/bar", "<any words/", ("<html", "<head", "<script", "<style", "<?", "<%") + v.tagCleaner = regexp.MustCompile(`(?i)<(/?\w+/\w+|/[\w ]+/|/?(html|head|script|style|%|\?)\b)`) v.nulCleaner = strings.NewReplacer("\000", "") return v }) @@ -253,7 +253,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output node, err := html.Parse(io.MultiReader( // prepend "<html><body>" strings.NewReader("<html><body>"), - // Strip out nuls - they're always invalid + // strip out NULLs (they're always invalid), and escape known tags bytes.NewReader(globalVars().tagCleaner.ReplaceAll([]byte(globalVars().nulCleaner.Replace(string(rawHTML))), []byte("<$1"))), // close the tags strings.NewReader("</body></html>"), diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 58f71bdd7b..5fdbf43f7c 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -525,6 +525,10 @@ func TestPostProcess(t *testing.T) { test("<script>a</script>", `<script>a</script>`) test("<STYLE>a", `<STYLE>a`) test("<style>a</STYLE>", `<style>a</STYLE>`) + + // other special tags, our special behavior + test("<?php\nfoo", "<?php\nfoo") + test("<%asp\nfoo", "<%asp\nfoo") } func TestIssue16020(t *testing.T) { diff --git a/modules/migration/schemas_bindata.go b/modules/migration/schemas_bindata.go index c5db3b3461..695c2c1135 100644 --- a/modules/migration/schemas_bindata.go +++ b/modules/migration/schemas_bindata.go @@ -3,6 +3,28 @@ //go:build bindata +//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas bindata.dat + package migration -//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas migration bindata.go +import ( + "io" + "io/fs" + "path" + "sync" + + _ "embed" + + "code.gitea.io/gitea/modules/assetfs" +) + +//go:embed bindata.dat +var bindata []byte + +var BuiltinAssets = sync.OnceValue(func() fs.FS { + return assetfs.NewEmbeddedFS(bindata) +}) + +func openSchema(filename string) (io.ReadCloser, error) { + return BuiltinAssets().Open(path.Base(filename)) +} diff --git a/modules/migration/schemas_static.go b/modules/migration/schemas_static.go deleted file mode 100644 index 8a0c340a65..0000000000 --- a/modules/migration/schemas_static.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build bindata - -package migration - -import ( - "io" - "path" -) - -func openSchema(filename string) (io.ReadCloser, error) { - return Assets.Open(path.Base(filename)) -} diff --git a/modules/options/options_bindata.go b/modules/options/options_bindata.go index 29151cb3cb..b2321d7eb5 100644 --- a/modules/options/options_bindata.go +++ b/modules/options/options_bindata.go @@ -3,6 +3,21 @@ //go:build bindata +//go:generate go run ../../build/generate-bindata.go ../../options bindata.dat + package options -//go:generate go run ../../build/generate-bindata.go ../../options options bindata.go +import ( + "sync" + + _ "embed" + + "code.gitea.io/gitea/modules/assetfs" +) + +//go:embed bindata.dat +var bindata []byte + +var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer { + return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata)) +}) diff --git a/modules/options/dynamic.go b/modules/options/options_dynamic.go index 085492d11c..085492d11c 100644 --- a/modules/options/dynamic.go +++ b/modules/options/options_dynamic.go diff --git a/modules/options/static.go b/modules/options/static.go deleted file mode 100644 index 72b28e990e..0000000000 --- a/modules/options/static.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build bindata - -package options - -import ( - "code.gitea.io/gitea/modules/assetfs" -) - -func BuiltinAssets() *assetfs.Layer { - return assetfs.Bindata("builtin(bindata)", Assets) -} diff --git a/modules/public/public.go b/modules/public/public.go index 7f8ce29056..2bc55b7869 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -89,19 +89,16 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, servePublicAsset(w, req, fi, fi.ModTime(), f) } -type GzipBytesProvider interface { - GzipBytes() []byte -} - // servePublicAsset serve http content func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { setWellKnownContentType(w, fi.Name()) httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic()) encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) - if encodings.Contains("gzip") { - // try to provide gzip content directly from bindata (provided by vfsgenÛ°CompressedFileInfo) - if compressed, ok := fi.(GzipBytesProvider); ok { - rdGzip := bytes.NewReader(compressed.GzipBytes()) + fiEmbedded, _ := fi.(assetfs.EmbeddedFileInfo) + if encodings.Contains("gzip") && fiEmbedded != nil { + // try to provide gzip content directly from bindata + if gzipBytes, ok := fiEmbedded.GetGzipContent(); ok { + rdGzip := bytes.NewReader(gzipBytes) // all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name // then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data if w.Header().Get("Content-Type") == "" { diff --git a/modules/public/public_bindata.go b/modules/public/public_bindata.go index 4878f88ad1..2dcf3e72e4 100644 --- a/modules/public/public_bindata.go +++ b/modules/public/public_bindata.go @@ -5,4 +5,19 @@ package public -//go:generate go run ../../build/generate-bindata.go ../../public public bindata.go true +//go:generate go run ../../build/generate-bindata.go ../../public bindata.dat + +import ( + "sync" + + _ "embed" + + "code.gitea.io/gitea/modules/assetfs" +) + +//go:embed bindata.dat +var bindata []byte + +var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer { + return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata)) +}) diff --git a/modules/public/serve_dynamic.go b/modules/public/public_dynamic.go index a668b17c34..a668b17c34 100644 --- a/modules/public/serve_dynamic.go +++ b/modules/public/public_dynamic.go diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go deleted file mode 100644 index e79085021e..0000000000 --- a/modules/public/serve_static.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build bindata - -package public - -import ( - "time" - - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/timeutil" -) - -var _ GzipBytesProvider = (*vfsgenÛ°CompressedFileInfo)(nil) - -// GlobalModTime provide a global mod time for embedded asset files -func GlobalModTime(filename string) time.Time { - return timeutil.GetExecutableModTime() -} - -func BuiltinAssets() *assetfs.Layer { - return assetfs.Bindata("builtin(bindata)", Assets) -} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index c6bdc65b32..318cf41108 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -100,11 +100,13 @@ var ( SigningKey string SigningName string SigningEmail string + SigningFormat string InitialCommit []string CRUDActions []string `ini:"CRUD_ACTIONS"` Merges []string Wiki []string DefaultTrustModel string + TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"` } `ini:"repository.signing"` }{ DetectedCharsetsOrder: []string{ @@ -242,20 +244,24 @@ var ( SigningKey string SigningName string SigningEmail string + SigningFormat string InitialCommit []string CRUDActions []string `ini:"CRUD_ACTIONS"` Merges []string Wiki []string DefaultTrustModel string + TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"` }{ SigningKey: "default", SigningName: "", SigningEmail: "", + SigningFormat: "openpgp", // git.SigningKeyFormatOpenPGP InitialCommit: []string{"always"}, CRUDActions: []string{"pubkey", "twofa", "parentsigned"}, Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"}, Wiki: []string{"never"}, DefaultTrustModel: "collaborator", + TrustedSSHKeys: []string{}, }, } RepoRootPath string diff --git a/modules/templates/static.go b/modules/templates/static.go deleted file mode 100644 index b5a7e561ec..0000000000 --- a/modules/templates/static.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2016 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build bindata - -package templates - -import ( - "time" - - "code.gitea.io/gitea/modules/assetfs" - "code.gitea.io/gitea/modules/timeutil" -) - -// GlobalModTime provide a global mod time for embedded asset files -func GlobalModTime(filename string) time.Time { - return timeutil.GetExecutableModTime() -} - -func BuiltinAssets() *assetfs.Layer { - return assetfs.Bindata("builtin(bindata)", Assets) -} diff --git a/modules/templates/templates_bindata.go b/modules/templates/templates_bindata.go index 6f1d3cf539..a919591ecf 100644 --- a/modules/templates/templates_bindata.go +++ b/modules/templates/templates_bindata.go @@ -3,6 +3,21 @@ //go:build bindata +//go:generate go run ../../build/generate-bindata.go ../../templates bindata.dat + package templates -//go:generate go run ../../build/generate-bindata.go ../../templates templates bindata.go true +import ( + "sync" + + _ "embed" + + "code.gitea.io/gitea/modules/assetfs" +) + +//go:embed bindata.dat +var bindata []byte + +var BuiltinAssets = sync.OnceValue(func() *assetfs.Layer { + return assetfs.Bindata("builtin(bindata)", assetfs.NewEmbeddedFS(bindata)) +}) diff --git a/modules/templates/dynamic.go b/modules/templates/templates_dynamic.go index e1babd83c9..e1babd83c9 100644 --- a/modules/templates/dynamic.go +++ b/modules/templates/templates_dynamic.go diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 8d9ba1000c..14655a53c3 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -38,8 +38,8 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils { // RenderCommitMessage renders commit message with XSS-safe and special links. func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML { cleanMsg := template.HTMLEscapeString(msg) - // we can safely assume that it will not return any error, since there - // shouldn't be any special HTML. + // we can safely assume that it will not return any error, since there shouldn't be any special HTML. + // "repo" can be nil when rendering commit messages for deleted repositories in a user's dashboard feed. fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg) if err != nil { log.Error("PostProcessCommitMessage: %v", err) @@ -47,7 +47,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) te } msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") if len(msgLines) == 0 { - return template.HTML("") + return "" } return renderCodeBlock(template.HTML(msgLines[0])) } diff --git a/modules/timeutil/executable.go b/modules/timeutil/executable.go deleted file mode 100644 index 57ae8b2a9d..0000000000 --- a/modules/timeutil/executable.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package timeutil - -import ( - "os" - "path/filepath" - "sync" - "time" - - "code.gitea.io/gitea/modules/log" -) - -var ( - executablModTime = time.Now() - executablModTimeOnce sync.Once -) - -// GetExecutableModTime get executable file modified time of current process. -func GetExecutableModTime() time.Time { - executablModTimeOnce.Do(func() { - exePath, err := os.Executable() - if err != nil { - log.Error("os.Executable: %v", err) - return - } - - exePath, err = filepath.Abs(exePath) - if err != nil { - log.Error("filepath.Abs: %v", err) - return - } - - exePath, err = filepath.EvalSymlinks(exePath) - if err != nil { - log.Error("filepath.EvalSymlinks: %v", err) - return - } - - st, err := os.Stat(exePath) - if err != nil { - log.Error("os.Stat: %v", err) - return - } - - executablModTime = st.ModTime() - }) - return executablModTime -} diff --git a/modules/zstd/zstd_test.go b/modules/zstd/zstd_test.go index c3ca8e78f7..7fd30484ca 100644 --- a/modules/zstd/zstd_test.go +++ b/modules/zstd/zstd_test.go @@ -16,7 +16,7 @@ import ( ) func TestWriterReader(t *testing.T) { - testData := prepareTestData(t, 20_000_000) + testData := prepareTestData(t, 1_000_000) result := bytes.NewBuffer(nil) @@ -64,7 +64,7 @@ func TestWriterReader(t *testing.T) { } func TestSeekableWriterReader(t *testing.T) { - testData := prepareTestData(t, 20_000_000) + testData := prepareTestData(t, 2_000_000) result := bytes.NewBuffer(nil) @@ -109,7 +109,7 @@ func TestSeekableWriterReader(t *testing.T) { reader, err := NewSeekableReader(assertReader) require.NoError(t, err) - _, err = reader.Seek(10_000_000, io.SeekStart) + _, err = reader.Seek(1_000_000, io.SeekStart) require.NoError(t, err) data := make([]byte, 1000) @@ -117,7 +117,7 @@ func TestSeekableWriterReader(t *testing.T) { require.NoError(t, err) require.NoError(t, reader.Close()) - assert.Equal(t, testData[10_000_000:10_000_000+1000], data) + assert.Equal(t, testData[1_000_000:1_000_000+1000], data) // Should seek 3 times, // the first two times are for getting the index, diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f115dee247..56dcadd451 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -113,9 +113,11 @@ copy_type_unsupported=Dieser Dateityp kann nicht kopiert werden write=Verfassen preview=Vorschau loading=Laden… +files=Dateien error=Fehler error404=Die Seite, die Du versuchst aufzurufen, <strong>existiert nicht</strong> oder <strong>Du bist nicht berechtigt</strong>, diese anzusehen. +error503=Der Server konnte deine Anfrage nicht abschließen. Bitte versuche es später erneut. go_back=Zurück invalid_data=Ungültige Daten: %v @@ -128,6 +130,7 @@ pin=Anheften unpin=Loslösen artifacts=Artefakte +expired=Abgelaufen confirm_delete_artifact=Bist du sicher, dass du das Artefakt '%s' löschen möchtest? archived=Archiviert @@ -169,6 +172,10 @@ search=Suche ... type_tooltip=Suchmodus fuzzy=Ähnlich fuzzy_tooltip=Ergebnisse einbeziehen, die dem Suchbegriff ähnlich sind +words=Wörter +words_tooltip=Nur Suchbegriffe einbeziehen, die den Suchbegriffen exakt entsprechen +regexp=Regexp +regexp_tooltip=Nur Suchbegriffe einbeziehen, die dem Regexp exakt entsprechen exact=Exakt exact_tooltip=Nur Suchbegriffe einbeziehen, die dem exakten Suchbegriff entsprechen repo_kind=Repositories durchsuchen ... @@ -444,6 +451,7 @@ use_scratch_code=Einmalpasswort verwenden twofa_scratch_used=Du hast dein Einmalpasswort verwendet. Du wurdest zu den Einstellung der Zwei-Faktor-Authentifizierung umgeleitet, dort kannst du dein Gerät abmelden oder ein neues Einmalpasswort erzeugen. twofa_passcode_incorrect=Ungültige PIN. Wenn du dein Gerät verloren hast, verwende dein Einmalpasswort. twofa_scratch_token_incorrect=Das Einmalpasswort ist falsch. +twofa_required=Du musst die Zwei-Faktor-Authentifizierung einrichten, um Zugriff auf die Repositories zu erhalten, oder versuche dich erneut anzumelden. login_userpass=Anmelden login_openid=OpenID oauth_signup_tab=Neues Konto registrieren @@ -452,6 +460,7 @@ oauth_signup_submit=Konto vervollständigen oauth_signin_tab=Mit existierendem Konto verbinden oauth_signin_title=Anmelden um verbundenes Konto zu autorisieren oauth_signin_submit=Konto verbinden +oauth.signin.error.general=Beim Verarbeiten der Autorisierungsanfrage ist ein Fehler aufgetreten: %s. Wenn dieser Fehler weiterhin besteht, wende dich bitte an den Administrator. oauth.signin.error.access_denied=Die Autorisierungsanfrage wurde abgelehnt. oauth.signin.error.temporarily_unavailable=Autorisierung fehlgeschlagen, da der Authentifizierungsserver vorübergehend nicht verfügbar ist. Bitte versuch es später erneut. oauth_callback_unable_auto_reg=Automatische Registrierung ist aktiviert, aber der OAuth2-Provider %[1]s hat fehlende Felder zurückgegeben: %[2]s, kann den Account nicht automatisch erstellen. Bitte erstelle oder verbinde einen Account oder kontaktieren den Administrator. @@ -724,6 +733,8 @@ public_profile=Öffentliches Profil biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden) location_placeholder=Teile Deinen ungefähren Standort mit anderen profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet. +password_username_disabled=Du bist nicht berechtigt, den Benutzernamen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details. +password_full_name_disabled=Du bist nicht berechtigt, den vollständigen Namen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details. full_name=Vollständiger Name website=Webseite location=Standort @@ -918,6 +929,9 @@ permission_not_set=Nicht festgelegt permission_no_access=Kein Zugriff permission_read=Lesen permission_write=Lesen und Schreiben +permission_anonymous_read=Anonymes Lesen +permission_everyone_read=Alle lesen +permission_everyone_write=Alle schreiben access_token_desc=Ausgewählte Token-Berechtigungen beschränken die Authentifizierung auf die entsprechenden <a %s>API</a>-Routen. Lies die <a %s>Dokumentation</a> für mehr Informationen. at_least_one_permission=Du musst mindestens eine Berechtigung auswählen, um ein Token zu erstellen permissions_list=Berechtigungen: @@ -1020,6 +1034,9 @@ new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des owner=Besitzer owner_helper=Einige Organisationen könnten in der Dropdown-Liste nicht angezeigt werden, da die Anzahl an Repositories begrenzt ist. repo_name=Repository-Name +repo_name_profile_public_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem öffentlichen Organisationsprofil hinzufügen kannst, das für jeden sichtbar ist. Stelle sicher, dass es öffentlich ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen. +repo_name_profile_private_hint=.profile ist ein spezielles Repository, mit dem du die README.md zu deinem privaten Organisationsprofil hinzufügen kannst, das nur für Organisationsmitglieder sichtbar ist. Stelle sicher, dass es privat ist und initialisiere es mit einer README im Profilverzeichnis, um loszulegen. +repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern. Ein Repository namens ".profile" or ".profile-private" kann verwendet werden, um die README.md auf dem Benutzer- oder Organisationsprofil anzuzeigen. repo_size=Repository-Größe template=Template template_select=Vorlage auswählen @@ -1116,6 +1133,7 @@ blame.ignore_revs=Revisionen in <a href="%s">.git-blame-ignore-revs</a> werden i blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in <a href="%s">.git-blame-ignore-revs</a>. user_search_tooltip=Zeigt maximal 30 Benutzer +tree_path_not_found=Pfad %[1]s existiert nicht in %[2]s transfer.accept=Übertragung Akzeptieren transfer.accept_desc=`Übertragung nach "%s"` @@ -1126,6 +1144,7 @@ transfer.no_permission_to_reject=Du hast keine Berechtigung, diesen Transfer abz desc.private=Privat desc.public=Öffentlich +desc.public_access=Öffentlicher Zugriff desc.template=Template desc.internal=Intern desc.archived=Archiviert @@ -1209,6 +1228,7 @@ migrate.migrating_issues=Issues werden migriert migrate.migrating_pulls=Pull Requests werden migriert migrate.cancel_migrating_title=Migration abbrechen migrate.cancel_migrating_confirm=Möchtest du diese Migration abbrechen? +migrating_status=Migrationstatus mirror_from=Mirror von forked_from=geforkt von @@ -1233,6 +1253,7 @@ create_new_repo_command=Erstelle ein neues Repository von der Kommandozeile aus push_exist_repo=Bestehendes Repository via Kommandozeile pushen empty_message=Dieses Repository hat keinen Inhalt. broken_message=Die Git-Daten, die diesem Repository zugrunde liegen, können nicht gelesen werden. Kontaktiere den Administrator deiner Instanz oder lösche dieses Repository. +no_branch=Dieses Repository hat keine Branches. code=Code code.desc=Zugriff auf Quellcode, Dateien, Commits und Branches. @@ -1344,6 +1365,8 @@ editor.new_branch_name_desc=Neuer Branchname… editor.cancel=Abbrechen editor.filename_cannot_be_empty=Der Dateiname darf nicht leer sein. editor.filename_is_invalid=Ungültiger Dateiname: "%s". +editor.commit_email=Commit-E-Mail-Adresse +editor.invalid_commit_email=Die E-Mail-Adresse für den Commit ist ungültig. editor.branch_does_not_exist=Branch "%s" existiert nicht in diesem Repository. editor.branch_already_exists=Branch "%s" existiert bereits in diesem Repository. editor.directory_is_a_file=Der Verzeichnisname "%s" wird bereits als Dateiname in diesem Repository verwendet. @@ -1392,6 +1415,7 @@ commits.signed_by_untrusted_user_unmatched=Signiert von nicht vertrauenswürdige commits.gpg_key_id=GPG-Schlüssel-ID commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck commits.view_path=An diesem Punkt im Verlauf anzeigen +commits.view_file_diff=Änderungen an dieser Datei in diesem Commit anzeigen commit.operations=Operationen commit.revert=Zurücksetzen @@ -1452,6 +1476,8 @@ issues.filter_milestones=Meilenstein filtern issues.filter_projects=Projekt filtern issues.filter_labels=Label filtern issues.filter_reviewers=Reviewer filtern +issues.filter_no_results=Keine Ergebnisse +issues.filter_no_results_placeholder=Versuche, deine Suchfilter anzupassen. issues.new=Neues Issue issues.new.title_empty=Der Titel kann nicht leer sein issues.new.labels=Label @@ -1527,11 +1553,14 @@ issues.filter_project=Projekt issues.filter_project_all=Alle Projekte issues.filter_project_none=Kein Projekt issues.filter_assignee=Zuständig +issues.filter_assignee_no_assignee=Niemandem zugewiesen +issues.filter_assignee_any_assignee=Jemandem zugewiesen issues.filter_poster=Autor issues.filter_user_placeholder=Benutzer suchen issues.filter_user_no_select=Alle Benutzer issues.filter_type=Typ issues.filter_type.all_issues=Alle Issues +issues.filter_type.all_pull_requests=Alle Pull-Requests issues.filter_type.assigned_to_you=Dir zugewiesen issues.filter_type.created_by_you=Von dir erstellt issues.filter_type.mentioning_you=Hat dich erwähnt @@ -1623,12 +1652,15 @@ issues.save=Speichern issues.label_title=Labelname issues.label_description=Labelbeschreibung issues.label_color=Labelfarbe +issues.label_color_invalid=Ungültige Farbe issues.label_exclusive=Exklusiv issues.label_archive=Label archivieren issues.label_archived_filter=Archivierte Labels anzeigen issues.label_archive_tooltip=Archivierte Labels werden bei der Suche nach Label standardmäßig von den Vorschlägen ausgeschlossen. issues.label_exclusive_desc=Nenne das Label <code>Bereich/Element</code> um es gegenseitig ausschließend mit anderen <code>Bereich/</code> Labels zu machen. issues.label_exclusive_warning=Alle im Konflikt stehenden Labels werden beim Bearbeiten der Labels eines Issues oder eines Pull-Requests entfernt. +issues.label_exclusive_order=Sortierreihenfolge +issues.label_exclusive_order_tooltip=Exklusive Labels im gleichen Bereich werden nach dieser numerischen Reihenfolge sortiert. issues.label_count=%d Label issues.label_open_issues=%d offene Issues issues.label_edit=Bearbeiten @@ -1680,14 +1712,18 @@ issues.timetracker_timer_manually_add=Zeit hinzufügen issues.time_estimate_set=Geschätzte Zeit festlegen issues.time_estimate_display=Schätzung: %s +issues.change_time_estimate_at=Zeitschätzung geändert zu <b>%[1]s</b> %[2]s issues.remove_time_estimate_at=Zeitschätzung %s entfernt issues.time_estimate_invalid=Format der Zeitschätzung ist ungültig issues.start_tracking_history=hat die Zeiterfassung %s gestartet issues.tracker_auto_close=Der Timer wird automatisch gestoppt, wenn dieser Issue geschlossen wird issues.tracking_already_started=`Du hast die Zeiterfassung bereits in <a href="%s">diesem Issue</a> gestartet!` +issues.stop_tracking=Timer stoppen +issues.stop_tracking_history=hat für <b>%[1]s</b> %[2]s gearbeitet issues.cancel_tracking=Verwerfen issues.cancel_tracking_history=`hat die Zeiterfassung %s abgebrochen` issues.del_time=Diese Zeiterfassung löschen +issues.add_time_history=hat <b>%[1]s</b> %[2]s gearbeitete Zeit hinzugefügt issues.del_time_history=`hat %s gearbeitete Zeit gelöscht` issues.add_time_manually=Zeit manuell hinzufügen issues.add_time_hours=Stunden @@ -1847,6 +1883,7 @@ pulls.add_prefix=<strong>%s</strong> Präfix hinzufügen pulls.remove_prefix=<strong>%s</strong> Präfix entfernen pulls.data_broken=Dieser Pull-Requests ist kaputt, da Fork-Informationen gelöscht wurden. pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zum Ziel-Branch stehen. +pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken. pulls.is_ancestor=Dieser Branch ist bereits im Zielbranch enthalten. Es gibt nichts zu mergen. pulls.is_empty=Die Änderungen an diesem Branch sind bereits auf dem Zielbranch. Dies wird ein leerer Commit sein. pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich. @@ -1916,6 +1953,7 @@ pulls.outdated_with_base_branch=Dieser Branch enthält nicht die neusten Commits pulls.close=Pull-Request schließen pulls.closed_at=`hat diesen Pull-Request <a id="%[1]s" href="#%[1]s">%[2]s</a> geschlossen` pulls.reopened_at=`hat diesen Pull-Request <a id="%[1]s" href="#%[1]s">%[2]s</a> wieder geöffnet` +pulls.cmd_instruction_hint=Zeige Kommandozeilenanweisungen pulls.cmd_instruction_checkout_title=Checkout pulls.cmd_instruction_checkout_desc=Wechsle auf einen neuen Branch in deinem lokalen Repository und teste die Änderungen. pulls.cmd_instruction_merge_title=Mergen @@ -1944,6 +1982,7 @@ pulls.upstream_diverging_prompt_behind_1=Dieser Branch ist %[1]d Commit hinter % pulls.upstream_diverging_prompt_behind_n=Dieser Branch ist %[1]d Commits hinter %[2]s pulls.upstream_diverging_prompt_base_newer=Der Basis-Branch %s hat neue Änderungen pulls.upstream_diverging_merge=Fork synchronisieren +pulls.upstream_diverging_merge_confirm=Möchtest du „%[1]s“ in „%[2]s“ mergen? pull.deleted_branch=(gelöscht):%s pull.agit_documentation=Dokumentation zu AGit durchschauen @@ -2103,6 +2142,12 @@ contributors.contribution_type.deletions=Löschungen settings=Einstellungen settings.desc=In den Einstellungen kannst du die Einstellungen des Repositories anpassen settings.options=Repository +settings.public_access=Öffentlicher Zugriff +settings.public_access_desc=Konfiguriere die Zugriffsrechte öffentlicher Besucher, um die Standardwerte dieses Repositorys zu überschreiben. +settings.public_access.docs.not_set=Nicht gesetzt: keine zusätzliche öffentliche Zugriffsberechtigung. Die Berechtigung der Besucher folgt den Sichtbarkeits- und Mitgliedsberechtigungen des Repositorys. +settings.public_access.docs.anonymous_read=Anonymes Lesen: Nicht angemeldete Benutzer können mit Leseberechtigung auf die Einheit zugreifen. +settings.public_access.docs.everyone_read=Alle Lesen: Alle angemeldeten Benutzer können mit Leseberechtigung auf die Einheit zugreifen. Leseberechtigung für Issues/Pull-Request-Einheiten bedeutet auch, dass Benutzer neue Issues/Pull-Requests erstellen können. +settings.public_access.docs.everyone_write=Alle Schreiben: Alle angemeldeten Benutzer haben Schreibrechte auf die Einheit. Nur die Wiki-Einheit unterstützt diese Berechtigung. settings.collaboration=Mitarbeiter settings.collaboration.admin=Administrator settings.collaboration.write=Schreibrechte @@ -2316,6 +2361,8 @@ settings.event_fork=Fork settings.event_fork_desc=Repository geforkt. settings.event_wiki=Wiki settings.event_wiki_desc=Wiki-Seite erstellt, umbenannt, bearbeitet oder gelöscht. +settings.event_statuses=Status +settings.event_statuses_desc=Commit-Status von der API aktualisiert. settings.event_release=Release settings.event_release_desc=Release in einem Repository veröffentlicht, aktualisiert oder gelöscht. settings.event_push=Push @@ -2353,6 +2400,9 @@ settings.event_pull_request_review_request=Überprüfung des Pull-Requests angef settings.event_pull_request_review_request_desc=Überprüfung des Pull-Requests angefragt oder die Anfrage entfernt. settings.event_pull_request_approvals=Zustimmungen zum Pull-Request settings.event_pull_request_merge=Pull-Request-Merge +settings.event_header_workflow=Workflow-Ereignisse +settings.event_workflow_job=Workflow-Jobs +settings.event_workflow_job_desc=Gitea Actions Workflow Job in Warteschlange, wartend, in Bearbeitung oder abgeschlossen. settings.event_package=Paket settings.event_package_desc=Paket wurde in einem Repository erstellt oder gelöscht. settings.branch_filter=Branch-Filter @@ -2615,6 +2665,9 @@ diff.image.overlay=Überlagert diff.has_escaped=Diese Zeile enthält versteckte Unicode-Zeichen diff.show_file_tree=Dateibaum anzeigen diff.hide_file_tree=Dateibaum ausblenden +diff.submodule_added=Submodul %[1]s hinzugefügt bei %[2]s +diff.submodule_deleted=Submodul %[1]s gelöscht von %[2]s +diff.submodule_updated=Submodul %[1]s aktualisiert: %[2]s releases.desc=Behalte den Überblick über Versionen und Downloads. release.releases=Releases @@ -2685,6 +2738,7 @@ branch.restore_success=Branch "%s" wurde wiederhergestellt. branch.restore_failed=Wiederherstellung vom Branch "%s" gescheitert. branch.protected_deletion_failed=Branch "%s" ist geschützt und kann nicht gelöscht werden. branch.default_deletion_failed=Branch "%s" kann nicht gelöscht werden, da dieser Branch der Standard-Branch ist. +branch.default_branch_not_exist=Standardzweig „%s“ existiert nicht. branch.restore=Branch "%s" wiederherstellen branch.download=Branch "%s" herunterladen branch.rename=Branch "%s" umbenennen @@ -2699,6 +2753,8 @@ branch.create_branch_operation=Branch erstellen branch.new_branch=Neue Branch erstellen branch.new_branch_from=Neuen Branch von "%s" erstellen branch.renamed=Branch %s wurde in %s umbenannt. +branch.rename_default_or_protected_branch_error=Nur Administratoren können Standard- oder geschützte Branches umbenennen. +branch.rename_protected_branch_failed=Dieser Branch ist durch globale Schutzregeln geschützt. tag.create_tag=Tag %s erstellen tag.create_tag_operation=Tag erstellen @@ -2853,7 +2909,11 @@ teams.invite.title=Du wurdest eingeladen, dem Team <strong>%s</strong> in der Or teams.invite.by=Von %s eingeladen teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten. +view_as_role=Ansehen als: %s +view_as_public_hint=Du siehst die README als ein öffentlicher Benutzer. +view_as_member_hint=Du siehst die README als ein Mitglied dieser Organisation. +worktime=Arbeitszeit worktime.date_range_start=Startdatum worktime.date_range_end=Enddatum worktime.query=Abfrage @@ -3354,6 +3414,8 @@ monitor.previous=Letzte Ausführung monitor.execute_times=Ausführungen monitor.process=Laufende Prozesse monitor.stacktrace=Stacktraces +monitor.trace=Ablaufverfolgung +monitor.performance_logs=Leistungsprotokolle monitor.processes_count=%d Prozesse monitor.download_diagnosis_report=Diagnosebericht herunterladen monitor.desc=Beschreibung @@ -3528,6 +3590,7 @@ versions=Versionen versions.view_all=Alle anzeigen dependency.id=ID dependency.version=Version +search_in_external_registry=In %s suchen alpine.registry=Richte diese Registry ein, indem Du die URL in die <code>/etc/apk/repositories</code>-Datei hinzufügst: alpine.registry.key=Lade den öffentlichen RSA-Key der Registry in den <code>/etc/apk/keys/</code>-Ordner, um die Signatur zu überprüfen: alpine.registry.info=Wähle $branch und $repository aus der Liste unten. @@ -3557,6 +3620,8 @@ conda.install=Um das Paket mit Conda zu installieren, führe den folgenden Befeh container.details.type=Container-Image Typ container.details.platform=Plattform container.pull=Downloade das Container-Image aus der Kommandozeile: +container.images=Images +container.digest=Digest container.multi_arch=Betriebsystem / Architektur container.layers=Container-Image Ebenen container.labels=Labels @@ -3664,8 +3729,13 @@ none=Noch keine Secrets vorhanden. creation.description=Beschreibung creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen. +creation.description_placeholder=Gib eine Kurzbeschreibung ein (optional). +save_success=Das Secret "%s" wurde gespeichert. +save_failed=Secret konnte nicht gespeichert werden. +add_secret=Secret hinzufügen +edit_secret=Secret bearbeiten deletion=Secret entfernen deletion.description=Das Entfernen eines Secrets kann nicht rückgängig gemacht werden. Fortfahren? deletion.success=Das Secret wurde entfernt. @@ -3743,6 +3813,10 @@ runs.no_workflows.documentation=Weitere Informationen zu Gitea Actions findest d runs.no_runs=Der Workflow hat noch keine Ausführungen. runs.empty_commit_message=(leere Commit-Nachricht) runs.expire_log_message=Protokolle wurden geleert, weil sie zu alt waren. +runs.delete=Workflow-Ausführung löschen +runs.delete.description=Bist du sicher, dass du diese Workflow-Ausführung dauerhaft löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden. +runs.not_done=Diese Workflow-Ausführung ist noch nicht abgeschlossen. +runs.view_workflow_file=Workflow-Datei anzeigen workflow.disable=Workflow deaktivieren workflow.disable_success=Workflow '%s' erfolgreich deaktiviert. @@ -3754,6 +3828,7 @@ workflow.not_found=Workflow '%s' wurde nicht gefunden. workflow.run_success=Workflow '%s' erfolgreich ausgeführt. workflow.from_ref=Nutze Workflow von workflow.has_workflow_dispatch=Dieser Workflow hat einen workflow_dispatch Event-Trigger. +workflow.has_no_workflow_dispatch=Der Workflow '%s' hat keinen workflow_dispatch Event-Trigger. need_approval_desc=Um Workflows für den Pull-Request eines Forks auszuführen, ist eine Genehmigung erforderlich. @@ -3781,6 +3856,8 @@ deleted.display_name=Gelöschtes Projekt type-1.display_name=Individuelles Projekt type-2.display_name=Repository-Projekt type-3.display_name=Organisationsprojekt +enter_fullscreen=Vollbild +exit_fullscreen=Vollbild verlassen [git.filemode] changed_filemode=%[1]s → %[2]s diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 6e4528b056..6ab4d7800c 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -4,6 +4,7 @@ explore=ОглÑд help=Довідка logo=Логотип sign_in=Увійти +sign_in_with_provider=Увійдіть за допомогою %s sign_in_or=або sign_out=Вийти sign_up=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ @@ -29,24 +30,34 @@ more_items=Більше елементів username=Ім'Ñ ÐºÑ€Ð¸Ñтувача email=ÐдреÑа електронної пошти password=Пароль -access_token=Токен ДоÑтупу -re_type=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ +access_token=Токен доÑтупу +re_type=Підтвердити пароль captcha=CAPTCHA -twofa=Двофакторна Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ +twofa=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ twofa_scratch=Двофакторний одноразовий пароль passcode=Код доÑтупу webauthn_insert_key=Ð’Ñтавте ключ безпеки +webauthn_sign_in=ÐатиÑніть кнопку на вашому ключі безпеки. Якщо ваш ключ без фізичної кнопки, поновно вÑтавте ключ. +webauthn_press_button=Будь лаÑка, натиÑніть кнопку на вашому ключі безпеки… +webauthn_use_twofa=ВикориÑтовуйте двофакторний код із Вашого телефона +webauthn_error=Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ ваш ключ безпеки. +webauthn_unsupported_browser=Ваш браузер наразі не підтримує WebAuthn. +webauthn_error_unknown=СталаÑÑ Ð½ÐµÐ²Ñ–Ð´Ð¾Ð¼Ð° помилка. Будь лаÑка, Ñпробуйте ще раз. +webauthn_error_insecure=`WebAuthn підтримує тільки безпечні з’єднаннÑ. Ð”Ð»Ñ Ñ‚ÐµÑÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ HTTP, ви можете ÑкориÑтатиÑÑ "localhost" або "127.0.0.1"` +webauthn_error_unable_to_process=Сервер не зміг обробити ваш запит. +webauthn_error_duplicated=Ключ безпеки не підходить Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту. ПереконайтеÑÑ, що ключ ще не зареєÑтровано. +webauthn_error_empty=Ви повинні вÑтановити назву Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ключа. +webauthn_error_timeout=Ð§Ð°Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð½Ð¾, перш ніж ваш ключ було прочитано. Перезавантажте Ñторінку та Ñпробуйте ще раз. webauthn_reload=Оновити -repository=Репозиторій +repository=Сховище organization=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ mirror=Дзеркало issue_milestone=Етап -new_repo=Ðовий репозиторій +new_repo=Ðове Ñховище new_migrate=Ðова Ð¼Ñ–Ð³Ñ€Ð°Ñ†Ñ–Ñ new_mirror=Ðове дзеркало -new_fork=Ðовий репозиторій - ÐºÐ¾Ð¿Ñ–Ñ new_org=Ðова Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ new_project=Ðовий проєкт new_project_column=Ðовий Ñтовпець @@ -59,7 +70,7 @@ your_starred=Обрані your_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ all=УÑÑ– -sources=ВлаÑні +sources=Джерела mirrors=Дзеркала collaborative=Спільні forks=Форки @@ -71,6 +82,7 @@ milestones=Етапи ok=OK cancel=Відмінити +retry=Повторіть Ñпробу rerun=ПерезапуÑтити rerun_all=ПерезапуÑтити вÑÑ– Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ save=Зберегти @@ -102,12 +114,13 @@ loading=ЗавантаженнÑ… files=Файли error=Помилка -error404=Сторінка, до Ñкої ви намагаєтеÑÑ Ð·Ð²ÐµÑ€Ð½ÑƒÑ‚Ð¸ÑÑ Ð°Ð±Ð¾ до <strong>, не Ñ–Ñнує</strong> або <strong>Ви не маєте права</strong> на Ñ—Ñ— переглÑд. +error404=Сторінка, Ñку ви намагаєтеÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸, <strong>не Ñ–Ñнує</strong> або <strong>ви не маєте права</strong> на Ñ—Ñ— переглÑд. error503=Сервер не зміг виконати ваш запит. Будь лаÑка, Ñпробуйте пізніше. go_back=Ðазад invalid_data=ÐедійÑні дані: %v never=Ðіколи +unknown=Ðевідомо rss_feed=Стрічка RSS @@ -118,14 +131,15 @@ artifacts=Ðртефакти expired=ПроÑтрочено confirm_delete_artifact=Справді видалити артефакт '%s' ? -archived=Ðрхівовані +archived=Ðрхівовано -concept_code_repository=Репозиторій +concept_code_repository=Сховище concept_user_organization=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ show_timestamps=Показувати чаÑові мітки show_log_seconds=Показувати Ñекунди show_full_screen=Показати на веÑÑŒ екран +download_logs=Завантажити журнали confirm_delete_selected=Підтверджуєте Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… вибраних елементів? @@ -134,15 +148,36 @@ value=Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ filter=Фільтр filter.clear=ОчиÑтити фільтр -filter.is_archived=Ðрхівовані +filter.is_archived=Ðрхівовано +filter.not_archived=Ðе архівовано +filter.is_fork=Відгалужено +filter.not_fork=Ðе відгалужено +filter.is_mirror=Віддзеркалено +filter.not_mirror=Ðе віддзеркалено filter.is_template=Шаблон +filter.not_template=Ðе шаблон filter.public=Публічний filter.private=Приватний +no_results_found=Ðічого не знайдено. [search] +search=Пошук... +type_tooltip=Тип пошуку +fuzzy=Ðеточний +regexp=РегулÑрний вираз +user_kind=Пошук кориÑтувачів... +org_kind=Пошук організацій... +team_kind=Пошук команд... +code_kind=Пошук коду... +code_search_unavailable=Пошук коду наразі недоÑтупний. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту. +code_search_by_git_grep=Поточні результати пошуку коду надаютьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾ÑŽ "git grep". Результати можуть бути кращими, Ñкщо адмініÑтратор Ñайту увімкне індекÑатор Ñховища. +project_kind=Пошук проєктів... +branch_kind=Пошук гілок... +commit_kind=Пошук комітів... no_results=Ðе знайдено жодного збігу. issue_kind=Пошук задач... +pull_kind=Пошук запитів на злиттÑ... keyword_search_unavailable=Пошук за ключовими Ñловами наразі недоÑтупний. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту. [aria] @@ -158,6 +193,7 @@ more=Більше [editor] buttons.heading.tooltip=Додати заголовок +buttons.bold.tooltip=Додати грубий текÑÑ‚ buttons.italic.tooltip=Додати курÑивний текÑÑ‚ buttons.quote.tooltip=Цитувати текÑÑ‚ buttons.code.tooltip=Додати код @@ -188,113 +224,111 @@ app_desc=Зручний влаÑний ÑÐµÑ€Ð²Ñ–Ñ Ñ…Ð¾Ñтингу репозРinstall=Легко вÑтановити platform=ПлатформонезалежніÑть lightweight=ÐевибагливіÑть -lightweight_desc=Gitea має низькі вимоги до реÑурÑів та може працювати на недорогому Raspberry Pi. Збережіть Ñвою машину енергію! license=Відкритий вихідний код [install] install=Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ installing_desc=Ð’ÑтановленнÑ, будь лаÑка, зачекайте... title=Початкова ÐºÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ -docker_helper=Якщо ви запуÑкаєте Gitea вÑередині Docker, будь лаÑка уважно прочитайте <a target="_blank" rel="noopener" href="%s">документацію</a> перед тим, Ñк щоÑÑŒ змінити на цій Ñторінці. -require_db_desc=Gitea вимагає MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB (протокол MySQL). +docker_helper=Якщо ви запуÑкаєте Gitea у Docker, будь лаÑка, прочитайте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a> перед тим, Ñк змінювати будь-Ñкі налаштуваннÑ. +require_db_desc=Gitea потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB (протокол MySQL). db_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних db_type=Тип бази даних host=ХоÑÑ‚ user=Ім'Ñ ÐºÑ€Ð¸Ñтувача password=Пароль -db_name=Ім'Ñ Ð±Ð°Ð·Ð¸ даних +db_name=Ðазва бази даних db_schema=Схема -db_schema_helper=Залиште пуÑтим Ð´Ð»Ñ Ð±Ð°Ð·Ð¸ даних за замовчуваннÑм ("публічна"). +db_schema_helper=Залиште пуÑтим Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð¾Ð²Ð¾Ñ— Ñхеми бази даних ("публічна"). ssl_mode=SSL path=ШлÑÑ… -sqlite_helper=ШлÑÑ… до файлу Ð´Ð»Ñ Ð±Ð°Ð·Ð¸ даних SQLite3.<br>Введіть абÑолютний шлÑÑ…, Ñкщо ви запуÑкаєте GÑ–tea Ñк ÑервіÑ. +sqlite_helper=ШлÑÑ… до файлу бази даних SQLite3.<br>Введіть абÑолютний шлÑÑ…, Ñкщо ви запуÑкаєте GÑ–tea Ñк ÑервіÑ. reinstall_error=Ви намагаєтеÑÑ Ð²Ñтановити в наÑвну базу даних Gitea reinstall_confirm_message=Повторне вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð² наÑвну базу даних Gitea може Ñпричинити багато проблем. Ð’ більшоÑті випадків, ви повинні викориÑтовувати Ñвій наÑвний "app.ini" Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку Gitea. Якщо ви знаєте, що робите, Ñпробуйте наÑтупне: -reinstall_confirm_check_1=Дані зашифровані з викориÑтаннÑм SECRET_KEY з app.ini можуть бути втрачені: кориÑтувачі не зможуть увійти з 2FA/OTP Ñ– дзеркала можуть працювати некоректно. Ð’Ñтановлюючи цей прапорець, ви підтверджуєте, що в поточному файлі app.ini вказано правильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ SECRET_KEY. -reinstall_confirm_check_2=Репозиторії та Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ¾Ð±Ñ…Ñ–Ð´Ð½Ð¾ повторно Ñинхронізувати. Ð’Ñтановлюючи цей прапорець, ви підтверджуєте, що ви Ñинхронізуватимете хуки репозиторіїв та authorized_keys вручну. Ви підтверджуєте, що Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ Ñ– дзеркала Ñ” правильними. -reinstall_confirm_check_3=Ви підтверджуєте, що повніÑтю впевнені в тому, що Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ екземплÑра Gitea вказано правильне Ñ€Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ app.ini та екземплÑÑ€ Ñлід вÑтановити повторно. Ви підтверджуєте, що уÑвідомлюєте вищенаведені ризики. +reinstall_confirm_check_1=Дані, зашифровані за допомогою SECRET_KEY в app.ini, можуть бути втрачені: кориÑтувачі не зможуть увійти за допомогою 2FA/OTP, а дзеркала можуть працювати некоректно. Ð’Ñтановивши цей прапорець, ви підтверджуєте, що поточний файл app.ini міÑтить правильний SECRET_KEY. +reinstall_confirm_check_2=Можливо, потрібно буде повторно Ñинхронізувати Ñховища та налаштуваннÑ. Ð’Ñтановивши цей прапорець, ви підтверджуєте, що будете реÑинхронізувати хуки Ð´Ð»Ñ Ñховищ Ñ– файл authorized_keys вручну. Ви підтверджуєте, що забезпечите правильніÑть налаштувань Ñховища Ñ– дзеркала. +reinstall_confirm_check_3=Ви підтверджуєте, що абÑолютно впевнені, що Gitea працює з правильним розташуваннÑм файлу app.ini, Ñ– що вам потрібно перевÑтановити програму. Ви підтверджуєте, що уÑвідомлюєте вищевказані ризики. err_empty_db_path=ШлÑÑ… до файлу бази даних SQLite3 не може бути порожнім. -no_admin_and_disable_registration=Ви не можете вимкнути реєÑтрацію до ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора. +no_admin_and_disable_registration=Ви не можете вимкнути реєÑтрацію без ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора. err_empty_admin_password=Пароль адмініÑтратора не може бути порожнім. err_empty_admin_email=Електронна адреÑа адмініÑтратора не може бути порожньою. -err_admin_name_is_reserved=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача-адмініÑтратора - ім'Ñ Ð·Ð°Ñ€ÐµÐ·ÐµÑ€Ð²Ð¾Ð²Ð°Ð½Ðµ -err_admin_name_pattern_not_allowed=Ім'Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора недійÑне, це ім'Ñ Ð¿Ñ–Ð´Ð¿Ð°Ð´Ð°Ñ” під зарезервований шаблон -err_admin_name_is_invalid=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача-адмініÑтратора +err_admin_name_is_reserved=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора - ім'Ñ Ð·Ð°Ñ€ÐµÐ·ÐµÑ€Ð²Ð¾Ð²Ð°Ð½Ð¾ +err_admin_name_pattern_not_allowed=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора недійÑне, воно відповідає зарезервованому шаблону +err_admin_name_is_invalid=ÐедійÑне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача адмініÑтратора general_title=Загальні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ app_name=Ðазва Ñайту app_name_helper=Тут ви можете ввеÑти назву Ñвоєї компанії. -repo_path=Кореневий шлÑÑ… Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ -repo_path_helper=Ð’ÑÑ– вилучені Git репозиторії будуть збережені в цей каталог. -lfs_path=Кореневої шлÑÑ… Git LFS -lfs_path_helper=У цій папці будуть зберігатиÑÑ Ñ„Ð°Ð¹Ð»Ð¸ Git LFS. Залиште порожнім, щоб вимкнути LFS. -run_user=ЗапуÑк від імені КориÑтувача +repo_path=Кореневий шлÑÑ… Ñховища +repo_path_helper=До цього каталогу буде збережено віддалені Ñховища Git. +lfs_path=Кореневий шлÑÑ… Git LFS +lfs_path_helper=У цій теці будуть зберігатиÑÑ Ñ„Ð°Ð¹Ð»Ð¸ Git LFS. Залиште порожнім, щоб вимкнути. +run_user=Виконати Ñк domain=Домен Ñервера -domain_helper=Домен або адреÑа хоÑта Ñервера. +domain_helper=Домен або хоÑÑ‚-адреÑа Ñервера. ssh_port=Порт SSH Ñервера ssh_port_helper=Ðомер порту, Ñкий викориÑтовує SSH Ñервер. Залиште порожнім, щоб вимкнути SSH. http_port=Gitea HTTP порт -http_port_helper=Ðомер порту, Ñкий буде проÑлуховуватиÑÑ Giteas веб-Ñервером. +http_port_helper=Ðомер порту, Ñкий буде проÑлуховуватиÑÑ Ñервером Giteas. app_url=Базова URL-адреÑа Gitea -app_url_helper=Базова адреÑа Ð´Ð»Ñ HTTP(S) ÐºÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· URL та повідомлень електронної пошти. -log_root_path=ШлÑÑ… до лог файлу -log_root_path_helper=Файли журналу будуть запиÑані в цей каталог. - -optional_title=Додаткові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -email_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Email -smtp_addr=SMTP хоÑÑ‚ -smtp_port=SMTP порт -smtp_from=ВідправлÑти Email від імені +app_url_helper=Базова адреÑа Ð´Ð»Ñ URL-Ð°Ð´Ñ€ÐµÑ ÐºÐ»Ð¾Ð½Ñ–Ð² HTTP(S) та Ñповіщень електронною поштою. +log_root_path=ШлÑÑ… до журналу +log_root_path_helper=Файли журналу будуть запиÑані в цю теку. + +optional_title=Ðеобов'Ñзкові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ +email_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти +smtp_addr=Сервер SMTP +smtp_port=Порт SMTP smtp_from_invalid=ÐдреÑа "ÐадіÑлати лиÑта Ñк" недійÑна -smtp_from_helper=Електронна пошта Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð² GÑ–tea. Введіть звичайну електронну адреÑу або викориÑтовуйте формат: "Ім'Ñ" <email@example.com>. -mailer_user=SMTP Ім'Ñ ÐºÑ€Ð¸Ñтувача -mailer_password=SMTP Пароль -register_confirm=Потрібно підтвердити електронну пошту Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації +smtp_from_helper=ÐдреÑа електронної пошти, Ñку буде викориÑтовувати Gitea. Введіть звичайну адреÑу електронної пошти або викориÑтовуйте формат «Ім'Ñ» <email@example.com>. +mailer_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача SMTP +mailer_password=Пароль SMTP +register_confirm=Вимагати Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації mail_notify=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою -server_service_title=Сервер Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… Ñлужб +server_service_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñервера Ñ– Ñторонніх ÑервіÑів offline_mode=Увімкнути локальний режим -offline_mode_popup=Відключити Ñторонні мережі доÑтавки контенту Ñ– обÑлуговувати вÑÑ– реÑурÑи локально. +offline_mode_popup=Вимкнути Ñторонні мережі доÑтавки контенту та обÑлуговувати вÑÑ– реÑурÑи локально. disable_gravatar=Вимкнути Gravatar -disable_gravatar_popup=Відключити Gravatar Ñ– Ñторонні джерела аватарів. Якщо кориÑтувач не завантажить аватар локально то за замовчуваннÑм буде викориÑтовуватиÑÑ Ñтандартний аватар. -federated_avatar_lookup=Увімкнути федеративні аватари -federated_avatar_lookup_popup=Увімкнути зовнішний Ðватар за допомогою Libravatar. -disable_registration=Вимкнути ÑамоÑтійну реєÑтрацію -disable_registration_popup=Вимкнути ÑамоÑтійну реєÑтрацію кориÑтувачів, тільки адмініÑтратор може Ñтворювати нові облікові запиÑи. +disable_gravatar_popup=Вимкнути Gravatar та Ñторонні джерела аватарок. Якщо кориÑтувач локально не завантажить аватар, буде викориÑтовуватиÑÑ Ñ‚Ð¸Ð¿Ð¾Ð²Ð¸Ð¹ аватар. +federated_avatar_lookup=Увімкнути зовнішні аватари +federated_avatar_lookup_popup=Увімкнути пошук об'єднаних аватарів за допомогою Libravatar. +disable_registration=Вимкнути реєÑтрацію +disable_registration_popup=Вимкнути реєÑтрацію кориÑтувачів, тільки адмініÑтратор може Ñтворювати нові облікові запиÑи. allow_only_external_registration_popup=Дозволити реєÑтрацію тільки через Ñторонні ÑервіÑи -openid_signin=Увімкнути реєÑтрацію за допомогою OpenID -openid_signin_popup=Увімкнути вхід за допомогою OpenID. +openid_signin=Увімкнути вхід за допомогою OpenID +openid_signin_popup=Увімкнути вхід кориÑтувачів за допомогою OpenID. openid_signup=Увімкнути ÑамоÑтійну реєÑтрацію за допомогою OpenID openid_signup_popup=Увімкнути ÑамореєÑтрацію кориÑтувачів на оÑнові OpenID. -enable_captcha=Увімкнути CAPTCHA при реєÑтрації -enable_captcha_popup=Вимагати перевірку CAPTCHA при ÑамоÑтійній реєÑтрації кориÑтувача. +enable_captcha=Увімкнути CAPTCHA Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації +enable_captcha_popup=Вимагати CAPTCHA Ð´Ð»Ñ ÑамореєÑтрації кориÑтувачів. require_sign_in_view=Вимагати авторизації Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Ñторінок admin_setting_desc=Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора необов'Ñзково. Перший зареєÑтрований кориÑтувач автоматично Ñтає адмініÑтратором. admin_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора admin_name=Ім'Ñ ÐºÑ€Ð¸Ñтувача ÐдмініÑтратора admin_password=Пароль -confirm_password=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ +confirm_password=Підтвердити пароль admin_email=ÐдреÑа електронної пошти -install_btn_confirm=Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Gitea -test_git_failed=Ðе в змозі перевірити 'git' команду: %v +install_btn_confirm=Ð’Ñтановити Gitea +test_git_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ команду 'git': %v sqlite3_not_available=Ð¦Ñ Ð²ÐµÑ€ÑÑ–Ñ Gitea не підтримує SQLite3. Будь лаÑка, завантажте офіційну бінарну верÑÑ–ÑŽ з %s (не верÑÑ–ÑŽ gobuild). -invalid_db_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних Ñ” некоректними: %v -invalid_db_table=Ð¢Ð°Ð±Ð»Ð¸Ñ†Ñ '%s' бази даних Ñ” недійÑною: %v -invalid_repo_path=Помилковий шлÑÑ… до ÐºÐ¾Ñ€ÐµÐ½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ: %v -invalid_app_data_path=Ðекоректний шлÑÑ… до даних програми: %v -run_user_not_match=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'run as' не Ñ” поточним ім'Ñм кориÑтувача: %s -> %s -internal_token_failed=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ внутрішній токен: %v -secret_key_failed=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ Ñекретний ключ: %v -save_config_failed=Ðе в змозі зберегти конфігурацію: %v -invalid_admin_setting=ÐеприпуÑтимі Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора: %v -invalid_log_root_path=ÐеприпуÑтимий шлÑÑ… Ð´Ð»Ñ Ð»Ð¾Ð³Ñ–Ð²: %v -default_keep_email_private=Приховати адреÑу електронної пошти за замовчуваннÑм -default_keep_email_private_popup=Приховати адреÑу електронної пошти нових облікових запиÑів за замовчуваннÑм. -default_allow_create_organization=Дозволити ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ð¹ за замовчуваннÑм -default_allow_create_organization_popup=Дозволити новим обліковим запиÑам кориÑтувачів Ñтворювати організації за замовчуваннÑм. -default_enable_timetracking=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу за замовчуваннÑм -default_enable_timetracking_popup=Включити відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… репозиторіїв за замовчуваннÑм. +invalid_db_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±Ð°Ð·Ð¸ даних недійÑні: %v +invalid_db_table=База даних таблиці "%s" Ñ” недійÑною: %v +invalid_repo_path=Кореневий шлÑÑ… до Ñховища невірний: %v +invalid_app_data_path=ШлÑÑ… до даних додатка невірний: %v +run_user_not_match=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача “Виконати Ñк†не Ñ” поточним ім'Ñм кориÑтувача: %s -> %s +internal_token_failed=Ðе вдалоÑÑ Ñтворити внутрішній токен: %v +secret_key_failed=Ðе вдалоÑÑ Ñтворити Ñекретний ключ: %v +save_config_failed=Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ конфігурацію: %v +invalid_admin_setting=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора Ñ” недійÑним: %v +invalid_log_root_path=Ðеправильний шлÑÑ… до журналу: %v +default_keep_email_private=Типово приховувати адреÑи електронної пошти +default_keep_email_private_popup=Типово приховувати адреÑи електронної пошти нових облікових запиÑів. +default_allow_create_organization=За замовчуваннÑм дозволити ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ð¹ +default_allow_create_organization_popup=За замовчуваннÑм дозволити новим кориÑтувачам Ñтворювати організації. +default_enable_timetracking=За замовчуваннÑм увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу +default_enable_timetracking_popup=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу Ð´Ð»Ñ Ð½Ð¾Ð²Ð¸Ñ… Ñховищ за замовчуваннÑм. no_reply_address=Прихований поштовий домен -no_reply_address_helper=Доменне ім'Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів із прихованою електронною адреÑою. Ðаприклад, ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'joe' буде входити в Git Ñк 'joe@noreply.example.org', Ñкщо Ð´Ð»Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ домену електронної пошти вÑтановлено 'noreply.example.org'. +no_reply_address_helper=Доменне ім'Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів із прихованою електронною адреÑою. Ðаприклад, ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'Joe' буде зображатиÑÑ Ð² Git Ñк 'joe@noreply.example.org', Ñкщо прихований домен електронної пошти вÑтановлено 'noreply.example.org'. password_algorithm=Ðлгоритм Ñ…ÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ invalid_password_algorithm=ÐедійÑний хеш-алгоритм Ð¿Ð°Ñ€Ð¾Ð»Ñ enable_update_checker=Увімкнути перевірку оновлень @@ -303,34 +337,36 @@ env_config_keys_prompt=ÐаÑтупні змінні Ñередовища так config_write_file_prompt=Ці параметри будуть запиÑані в: %s [home] -uname_holder=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або Ел. пошта +nav_menu=Меню навігації +uname_holder=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або адреÑа електронної пошти password_holder=Пароль switch_dashboard_context=Переключити контекÑÑ‚ панелі ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ -my_repos=Репозиторії -show_more_repos=Показати більше репозиторіїв… -collaborative_repos=Спільні репозиторії +my_repos=Сховища +show_more_repos=Показати більше Ñховищ… +collaborative_repos=Спільні Ñховища my_orgs=Мої організації my_mirrors=Мої дзеркала view_home=ПереглÑнути %s filter=Інші фільтри -filter_by_team_repositories=Фільтрувати за репозиторіÑми команд +filter_by_team_repositories=Фільтрувати за Ñховищами команд feed_of=`Стрічка "%s"` show_archived=Ðрхівовані -show_both_archived_unarchived=Показано архівовані Ñ– не архівовані +show_both_archived_unarchived=Показано архівовані Ñ– неархівовані show_only_archived=Показано тільки архівовані -show_only_unarchived=Показано тільки не архівовані +show_only_unarchived=Показано тільки неархівовані show_private=Приватні show_both_private_public=Показано публічні та приватні show_only_private=Показано тільки приватні show_only_public=Показано тільки публічні -issues.in_your_repos=Ð’ ваших репозиторіÑÑ… +issues.in_your_repos=У ваших Ñховищах +guide_title=Жодної активноÑті [explore] -repos=Репозиторії +repos=Сховища users=КориÑтувачі organizations=Організації code=Код @@ -338,98 +374,100 @@ code_last_indexed_at=ОÑтанні індекÑовані %s [auth] create_new_account=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу -disable_register_prompt=Вибачте, можливіÑть реєÑтрації відключена. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту. +disable_register_prompt=РеєÑтрацію вимкнено. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту. disable_register_mail=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÑ”Ñтрації електронною поштою вимкнено. remember_me=Запам’Ñтати цей приÑтрій forgot_password_title=Забув пароль forgot_password=Забули пароль? -must_change_password=Оновіть Ñвій пароль -allow_password_change=Вимагати в кориÑтувача змінити пароль (рекомендуєтьÑÑ) -reset_password_mail_sent_prompt=Електронний лиÑÑ‚ із підтвердженнÑм надіÑлано <b>%s</b>. Перевірте папку 'Вхідні' в межах наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. +must_change_password=Оновити пароль +allow_password_change=Вимагати від кориÑтувача змінити пароль (рекомендовано) +reset_password_mail_sent_prompt=Ðа адреÑу <b>%s</b> було надіÑлано лиÑÑ‚ із підтвердженнÑм. Будь лаÑка, перевірте Ñвою поштову Ñкриньку протÑгом наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. active_your_account=Ðктивувати обліковий Ð·Ð°Ð¿Ð¸Ñ account_activated=Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð¾ -prohibit_login=Вхід заборонений -resent_limit_prompt=Вибачте, ви вже запроÑили активацію по електронній пошті нещодавно. Будь лаÑка, зачекайте 3 хвилини, а потім Ñпробуйте ще раз. -has_unconfirmed_mail=Привіт %s, у Ð²Ð°Ñ Ñ” непідтверджена електронна адреÑа (<b>%s </b>). Якщо ви не отримали електронний лиÑÑ‚ із підтвердженнÑм або вам потрібно надіÑлати новий, натиÑніть на кнопку нижче. -resend_mail=ÐатиÑніть тут, щоб виÑлати лиÑÑ‚ активації знову -email_not_associate=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° пошта не пов'Ñзана ні з одним обліковим запиÑом. -send_reset_mail=ÐадіÑлати електронний лиÑÑ‚ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу +prohibit_login=Вхід заборонено +resent_limit_prompt=Ви вже надÑилали запит на активацію нещодавно. Зачекайте 3 хвилини Ñ– Ñпробуйте ще раз. +has_unconfirmed_mail= +Привіт %s, у Ð²Ð°Ñ Ð½ÐµÐ¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð° адреÑа електронної пошти (<b>%s</b>). Якщо ви не отримали лиÑта з підтвердженнÑм або вам потрібно надіÑлати новий, будь лаÑка, натиÑніть кнопку нижче. +resend_mail=ÐатиÑніть тут, щоб повторно надіÑлати лиÑÑ‚ з активацією +email_not_associate=Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти не пов'Ñзана з жодним обліковим запиÑом. +send_reset_mail=ÐадіÑлати лиÑÑ‚ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу reset_password=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу -invalid_code=Цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або закінчивÑÑ. +invalid_code=Ваш код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або його термін дії закінчивÑÑ. reset_password_helper=Відновити обліковий Ð·Ð°Ð¿Ð¸Ñ password_too_short=Довжина Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð½Ðµ може бути меншою за %d Ñимволів. -non_local_account=Ðелокальні акаунти не можуть змінити пароль через Gitea. +non_local_account=Ðелокальні кориÑтувачі не можуть оновити Ñвій пароль через Ð²ÐµÐ±Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Gitea. verify=Підтвердити scratch_code=Одноразовий пароль -use_scratch_code=ВикориÑтовувати одноразовий пароль -twofa_scratch_used=Ви викориÑтовували одноразовий пароль. Ви були перенаправлені на Ñторінку налаштувань Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ñ–Ñ— нового коду або Ð²Ñ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð²ÑƒÑ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації. -twofa_passcode_incorrect=Ваш пароль Ñ” невірним. Якщо ви втратили приÑтрій, викориÑтовуйте ваш одноразовий пароль. +use_scratch_code=СкориÑтатиÑÑŒ одноразовим паролем +twofa_scratch_used=Ви ÑкориÑталиÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ паролем. Ð’Ð°Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¾ на Ñторінку налаштувань двофакторної автентифікації, щоб видалити приÑтрій або згенерувати новий одноразовий пароль. +twofa_passcode_incorrect=Ваш пароль неправильний. Якщо ви загубили Ñвій приÑтрій, ÑкориÑтайтеÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ паролем. twofa_scratch_token_incorrect=Ðевірний одноразовий пароль. login_userpass=Увійти login_openid=OpenID oauth_signup_tab=ЗареєÑтрувати обліковий Ð·Ð°Ð¿Ð¸Ñ -oauth_signup_title=Повний новий обліковий Ð·Ð°Ð¿Ð¸Ñ -oauth_signup_submit=Повний обліковий Ð·Ð°Ð¿Ð¸Ñ -oauth_signin_tab=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñ–Ñнуючий обліковий Ð·Ð°Ð¿Ð¸Ñ +oauth_signup_title=Завершити реєÑтрацію облікового запиÑу +oauth_signup_submit=Поповнити обліковий Ð·Ð°Ð¿Ð¸Ñ +oauth_signin_tab=Прив'Ñзати до Ñ–Ñнуючого облікового запиÑу oauth_signin_title=Увійдіть щоб авторизувати пов'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ oauth_signin_submit=Прив'Ñзати обліковий Ð·Ð°Ð¿Ð¸Ñ openid_connect_submit=Під’єднатиÑÑ openid_connect_title=ПідключитиÑÑ Ð´Ð¾ Ñ–Ñнуючого облікового запиÑу -openid_connect_desc=Вибраний OpenID URI невідомий. Пов'Ñжіть його з новим обліковим запиÑом тут. +openid_connect_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом. openid_register_title=Створити новий обліковий Ð·Ð°Ð¿Ð¸Ñ -openid_register_desc=Вибраний OpenID URI невідомий. Пов'Ñжіть йогоз новим обліковим запиÑом тут. +openid_register_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом. disable_forgot_password_mail=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу вимкнено, оÑкільки не налаштована електронна пошта. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту. -disable_forgot_password_mail_admin=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу доÑтупне лише піÑÐ»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти. Будь лаÑка, налаштуйте ел. пошту Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. -email_domain_blacklisted=З вказаним email реєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð°. +disable_forgot_password_mail_admin=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу доÑтупне лише за наÑвноÑті електронної пошти. Будь лаÑка, налаштуйте електронну пошту, щоб увімкнути Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. +email_domain_blacklisted=Ви не можете зареєÑтруватиÑÑ Ð· адреÑою електронної пошти. authorize_application=Ðвторизувати програму authorize_redirect_notice=Ð’Ð°Ñ Ð±ÑƒÐ´Ðµ переадреÑовано до %s, Ñкщо ви авторизуєте цю програму. authorize_application_created_by=Ð¦Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð° Ñтворена %s. -authorize_application_description=Якщо ви надаÑте цей доÑтуп, то він матиме доÑтуп до вÑÑ–Ñ… ваших даних облікового запиÑу, включаючи приватні репозиторії та організації. -authorize_title=Ðвторизуйвати "%s" Ð´Ð»Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу? +authorize_application_description=Якщо ви авторизуєте цю програму, їй буде надано дозвіл на Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ñієї інформації вашого облікового запиÑу, включно з приватними Ñховищами та організаціÑми. +authorize_title=Ðвторизувати "%s" Ð´Ð»Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу? authorization_failed=Помилка авторизації -sspi_auth_failed=Помилка SSPI-автентифікації +sspi_auth_failed=Помилка автентифікації SSPI password_pwned_err=Ðе вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ запит до HaveIBeenPwed [mail] view_it_on=ПереглÑнути на %s -link_not_working_do_paste=Ðе працює? Спробуйте Ñкопіювати та вÑтавити його в Ñвій браузер. +reply=або надішліть відповідь безпоÑередньо на цей електронний лиÑÑ‚ +link_not_working_do_paste=Ðе працює? Спробуйте Ñкопіювати та вÑтавити його у браузер. hi_user_x=Привіт <b>%s</b>, activate_account=Будь лаÑка, активуйте ваш обліковий Ð·Ð°Ð¿Ð¸Ñ activate_account.title=%s, будь лаÑка, активуйте Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ activate_account.text_1=Привіт, <b>%[1]s</b>, дÑкуємо за реєÑтрацію на %[2]s! -activate_account.text_2=Перейдіть за цим поÑиланнÑм, щоб активувати ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð² <b>%s</b>: +activate_account.text_2=Будь лаÑка, перейдіть за наÑтупним поÑиланнÑм, щоб активувати Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ñ€Ð¾Ñ‚Ñгом <b>%s</b>: -activate_email=Підтвердить вашу адреÑу електронної пошти -activate_email.text=Перейдіть за цим поÑиланнÑм, щоб підтвердити вашу електронну адреÑу в <b>%s</b>: +activate_email=Підтвердіть адреÑу електронної пошти +activate_email.title=%s, будь лаÑка, підтвердіть вашу адреÑу електронної пошти +activate_email.text=Будь лаÑка, перейдіть за наÑтупним поÑиланнÑм протÑгом <b>%s</b>, щоб підтвердити Ñвою електронну адреÑу: register_notify.title=%[1]s, лаÑкаво проÑимо до %[2]s -register_notify.text_1=це ваша е-пошта Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÑ”Ñтрації Ð´Ð»Ñ %s! +register_notify.text_1=це лиÑÑ‚ з підтвердженнÑм реєÑтрації на %s! register_notify.text_2=Тепер ви можете увійти Ñк: %s. -register_notify.text_3=Якщо цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð±ÑƒÐ»Ð¾ Ñтворено Ð´Ð»Ñ Ð²Ð°Ñ, будь лаÑка, Ñпочатку <a href="%s">вÑтановіть Ñвій пароль</a>. +register_notify.text_3=Якщо цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено Ð´Ð»Ñ Ð²Ð°Ñ, будь лаÑка, Ñпершу <a href="%s">вÑтановіть пароль</a>. -reset_password=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ облікового запиÑу -reset_password.title=%s, ви відправили запит на Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу -reset_password.text=Перейдіть за цим поÑиланнÑм, щоб відновити ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð² <b>%s</b>: +reset_password=Відновити обліковий Ð·Ð°Ð¿Ð¸Ñ +reset_password.title=%s, ви надіÑлали запит на Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу +reset_password.text=Щоб відновити обліковий запиÑ, перейдіть за наÑтупним поÑиланнÑм протÑгом <b>%s</b>: register_success=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ ÑƒÑпішна -issue_assigned.pull=@%[1]s призначив вам запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[2]s в репозиторії %[3]s. -issue_assigned.issue=@%[1]s призначив вам задачу %[2]s у репозиторії %[3]s. +issue_assigned.pull=@%[1]s призначив вам запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[2]s в Ñховищі %[3]s. +issue_assigned.issue=@%[1]s призначив вам задачу %[2]s в Ñховищі %[3]s. issue.x_mentioned_you=<b>@%s</b> згадав ваÑ: -issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s. -issue.action.push_1=<b>@%[1]s</b> надіÑлав %[3]d коміти %[2]s -issue.action.push_n=<b>@%[1]s</b> відправив %[3]d коміти до %[2]s -issue.action.close=<b>@%[1]s</b> закрито #%[2]d. +issue.action.push_1=<b>@%[1]s</b> надіÑлав %[3]d коміт в %[2]s +issue.action.push_n=<b>@%[1]s</b> надіÑлав %[3]d коміти до %[2]s +issue.action.close=<b>@%[1]s</b> закрив #%[2]d. issue.action.reopen=<b>@%[1]s</b> заново відкрив #%[2]d. issue.action.merge=<b>@%[1]s</b> об'єднав #%[2]d до %[3]s. -issue.action.approve=<b>@%[1]s</b> затвердили цей запит на злиттÑ. -issue.action.reject=<b>@%[1]s</b> запитують зміни на цей запит на злиттÑ. -issue.action.review=<b>@%[1]s</b> прокоментували цей запит на злиттÑ. -issue.action.review_dismissed=<b>@%[1]s</b> відхилено оÑтанній відгук від %[2]s Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ. -issue.action.ready_for_review=<b>@%[1]s</b> позначили цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñк готовий до розглÑду. -issue.action.new=<b>@%[1]s</b> Ñтворили #%[2]d. +issue.action.approve=<b>@%[1]s</b> затвердив цей запит на злиттÑ. +issue.action.reject=<b>@%[1]s</b> запроÑив зміни у цьому запиті на злиттÑ. +issue.action.review=<b>@%[1]s</b> прокоментував цей запит на злиттÑ. +issue.action.review_dismissed=<b>@%[1]s</b> відхилив оÑтанній відгук від %[2]s на цей запит на злиттÑ. +issue.action.ready_for_review=<b>@%[1]s</b> позначив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñк готовий до розглÑду. +issue.action.new=<b>@%[1]s</b> Ñтворив #%[2]d. issue.in_tree_path=Ð’ %s: release.new.subject=%s в %s випущено @@ -440,37 +478,39 @@ release.downloads=ЗвантаженнÑ: release.download.zip=Вихідний код (ZIP) release.download.targz=Вихідний код (TAR.GZ) -repo.transfer.subject_to=%s бажає передати"%s" в %s -repo.transfer.subject_to_you=%s бажає передати"%s" вам +repo.transfer.subject_to=%s хоче перенеÑти "%s" в %s +repo.transfer.subject_to_you=%s хоче передати"%s" вам repo.transfer.to_you=вам repo.transfer.body=Щоб прийнÑти або відхилити перейдіть до %s або проÑто ігноруйте. repo.collaborator.added.subject=%s додав Ð²Ð°Ñ Ð´Ð¾ %s -repo.collaborator.added.text=Ви були додані в ÑкоÑті Ñпівавтора репозиторію: +repo.collaborator.added.text=Ð’Ð°Ñ Ð´Ð¾Ð´Ð°Ð»Ð¸ Ñк Ñпівавтора до Ñховища: team_invite.subject=%[1]s запрошує Ð²Ð°Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑ Ð´Ð¾ організації %[2]s +team_invite.text_1=%[1]s запрошує Ð²Ð°Ñ Ð´Ð¾ команди %[2]s в організації %[3]s. +team_invite.text_2=Перейдіть за поÑиланнÑм, щоб приєднатиÑÑ Ð´Ð¾ команди: [modal] yes=Так no=ÐÑ– confirm=Підтвердити cancel=Відмінити -modify=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ +modify=Оновити [form] UserName=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача -RepoName=Ðазва репозиторію +RepoName=Ðазва Ñховища Email=ÐдреÑа електронної пошти Password=Пароль -Retype=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ -SSHTitle=Iм'Ñ SSH ключа +Retype=Підтвердити пароль +SSHTitle=Ðазва ключа SSH HttpsUrl=ÐдреÑа HTTPS PayloadUrl=URL обробника TeamName=Ðазва команди AuthName=Ðазва авторизації -AdminEmail=Email адмініÑтратора +AdminEmail=ÐдреÑа електронної пошти адмініÑтратора -NewBranchName=Ім'Ñ Ð½Ð¾Ð²Ð¾Ñ— гілки +NewBranchName=Ðазва нової гілки CommitSummary=Резюме коміту CommitMessage=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ CommitChoice=Вибір коміта @@ -536,6 +576,7 @@ auth_failed=Помилка автентифікації: %v target_branch_not_exist=Цільової гілки не Ñ–Ñнує. +target_ref_not_exist=Цільове поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ Ñ–Ñнує %s admin_cannot_delete_self=Ви не можете видалити Ñебе, допоки ви адмініÑтратор. Будь лаÑка, Ñпочатку видаліть права адмініÑтратора. @@ -572,6 +613,10 @@ block.title=Заблокувати кориÑтувача block.info=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не дозволÑÑ” йому взаємодіÑти зі Ñховищами, наприклад, відкривати або коментувати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð°Ð±Ð¾ задачі. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. block.info_2=Ñлідкують за вашим обліковим запиÑом block.info_3=надÑилати вам ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ @згадавши ваше ім'Ñ +block.note=Примітка +block.note.title=Ðеобов’Ñзкова примітка: +block.list=Заблоковані кориÑтувачі +block.list.none=Ви не заблокували жодного кориÑтувача. [settings] profile=Профіль @@ -586,6 +631,7 @@ applications=Додатки orgs=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми repos=Репозиторії delete=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ +twofa=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (TOTP) account_link=Прив'Ñзані облікові запиÑи organization=Організації @@ -603,6 +649,7 @@ continue=Продовжити cancel=Відмінити language=Мова ui=Тема +hidden_comment_types=Приховані типи коментарів comment_type_group_reference=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ comment_type_group_label=Мітка comment_type_group_milestone=Етап @@ -763,6 +810,7 @@ permission_write=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ– Ð·Ð°Ð¿Ð¸Ñ permission_anonymous_read=Ðнонімне Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ permission_everyone_read=УÑÑ– читають permission_everyone_write=УÑÑ– пишуть +permissions_list=Дозволи: manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð°Ð¼Ð¸ OAuth2 edit_oauth2_application=Редагувати програму OAuth2 @@ -824,6 +872,7 @@ email_notifications.enable=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ email email_notifications.onmention=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ email тільки коли згадують email_notifications.disable=Вимкнути email ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ email_notifications.submit=Ðалаштувати параметри email +email_notifications.andyourown=І ваші влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ visibility=ВидиміÑть кориÑтувача visibility.public=Публічний @@ -995,6 +1044,9 @@ migrate.migrating_labels=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð¼Ñ–Ñ‚Ð¾Ðº migrate.migrating_releases=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² migrate.migrating_issues=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð´Ð°Ñ‡ migrate.migrating_pulls=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +migrate.cancel_migrating_title=СкаÑувати міграцію +migrate.cancel_migrating_confirm=Ви хочете ÑкаÑувати міграцію? +migrating_status=CÑ‚Ð°Ñ‚ÑƒÑ Ð¼Ñ–Ð³Ñ€Ð°Ñ†Ñ–Ñ— mirror_from=дзеркало forked_from=форк від @@ -1008,10 +1060,12 @@ watch=Слідкувати unstar=Видалити із обраних star=Ð’ обрані fork=Форк +action.blocked_user=Ðеможливо виконати дію, оÑкільки ви заблоковані влаÑником Ñховища. download_archive=Скачати репозиторій quick_guide=Короткий поÑібник clone_this_repo=Кнонувати цей репозиторій +cite_this_repo=ПоÑлатиÑÑ Ð½Ð° це Ñховище create_new_repo_command=Створити новий репозиторій з командного Ñ€Ñдка push_exist_repo=Опублікувати Ñ–Ñнуючий репозиторій з командного Ñ€Ñдка empty_message=Цей репозиторій порожній. @@ -1028,6 +1082,7 @@ tags=Теги issues=Задачі pulls=Запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ projects=Проєкти +actions=Дії labels=Мітки org_labels_desc=Мітки Ñ€Ñ–Ð²Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— можуть викориÑтовуватиÑÑ <strong>в уÑÑ–Ñ… репозиторіÑÑ…</strong> цієї організації org_labels_desc_manage=керувати @@ -1047,6 +1102,12 @@ file_view_rendered=ПереглÑнути відрендерено file_view_raw=ПереглÑд Raw file_permalink=ПоÑтійне поÑÐ¸Ð»Ð°Ð½Ð½Ñ file_too_large=Цей файл завеликий щоб бути показаним. +file_is_empty=Файл порожній. +code_preview_line_from_to=Ð Ñдки від %[1]d до %[2]d в %[3]s +code_preview_line_in=Ð Ñдок %[1]d в %[2]s +invisible_runes_header=`Цей файл міÑтить невидимі Ñимволи Юнікоду` +ambiguous_runes_header=`Цей файл міÑтить неоднозначні Ñимволи Юнікоду` +invisible_runes_line=`Цей Ñ€Ñдок міÑтить невидимі Ñимволи Юнікоду` escape_control_characters=Екранувати unescape_control_characters=Відмінити ÐµÐºÑ€Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ @@ -1102,11 +1163,15 @@ editor.propose_file_change=Запропонувати зміну файлу editor.new_branch_name_desc=Ім'Ñ Ð½Ð¾Ð²Ð¾Ñ— гілки… editor.cancel=Відмінити editor.filename_cannot_be_empty=Ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ не може бути порожнім. +editor.invalid_commit_email=ÐдреÑа електронної пошти Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ недійÑна. editor.file_changed_while_editing=ЗміÑÑ‚ файлу змінивÑÑ Ð· моменту початку редагуваннÑ. <a target="_blank" rel="noopener" href="%s"> ÐатиÑніть тут </a>, щоб переглÑнути що було змінено, або <strong>закомітьте зміни ще раз</strong>, щоб перепиÑати Ñ—Ñ…. editor.commit_empty_file_header=Закомітити порожній файл editor.commit_empty_file_text=Файл, в комміті порожній. Продовжити? editor.no_changes_to_show=Ðема змін Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ. +editor.fail_to_update_file=Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸/Ñтворити файл "%s". editor.fail_to_update_file_summary=Помилка: +editor.push_rejected_no_message=Зміну відхилено Ñервером без повідомленнÑ. Будь лаÑка, перевірте Git-хуки. +editor.push_rejected=Зміну відхилено Ñервером. Будь лаÑка, перевірте Git-хуки. editor.push_rejected_summary=Повне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ відмову: editor.add_subdir=Додати каталог… editor.upload_file_is_locked=Файл "%s" заблоковано %s. @@ -1130,10 +1195,14 @@ commits.signed_by_untrusted_user=ПідпиÑаний недовіреним кРcommits.signed_by_untrusted_user_unmatched=ПідпиÑаний недовіреним кориÑтувачем, Ñкий не відповідає комітеру commits.gpg_key_id=Ідентифікатор GPG ключа commits.ssh_key_fingerprint=Відбиток ключа SSH +commits.view_file_diff=ПереглÑнути зміни до цього файлу в цьому коміті +commit.revert-content=Виберіть гілку, до Ñкої хочете повернутиÑÑ: commitstatus.error=Помилка +commitstatus.failure=Ðевдача commitstatus.pending=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ +commitstatus.success=УÑпіх ext_issues=ДоÑтуп до зовнішніх задач ext_issues.desc=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню ÑиÑтему відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡. @@ -1144,19 +1213,23 @@ projects.create=Створити проєкт projects.title=Ðазва projects.new=Ðовий проєкт projects.new_subheader=Координуйте, відÑтежуйте та оновлюйте інформацію про виконувану роботу в одному міÑці, аби проєкти залишалиÑÑ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€Ð¸Ð¼Ð¸ та за розкладом. +projects.create_success=Проєкт "%s" Ñтворено. projects.deletion=Видалити проєкт projects.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ видалÑÑ” його з уÑÑ–Ñ… пов'Ñзаних задач. Продовжити? projects.deletion_success=Проєкт видалено. projects.edit=Редагувати проєкти projects.edit_subheader=Проєкти організовують задачі та відÑтежують прогреÑ. projects.modify=Оновити проєкт +projects.edit_success=Проєкт "%s" оновлено. projects.type.none=ВідÑутній projects.type.basic_kanban=Спрощений канбан projects.type.bug_triage=Ð¡Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð¼Ð¸Ð»Ð¾Ðº projects.template.desc=Шаблон проєкту projects.template.desc_helper=Оберіть шаблон проєкту, аби почати +projects.column.edit=Редагувати Ñтовпець projects.column.edit_title=Ðазва projects.column.new_title=Ðазва +projects.column.new_submit=Створити Ñтовпець projects.column.new=Ðовий Ñтовпець projects.column.set_default=Ð’Ñтановити типово projects.column.delete=Видалити Ñтовпець @@ -1197,6 +1270,9 @@ issues.choose.get_started=Початок роботи issues.choose.open_external_link=Відкрити issues.choose.blank=Типово issues.choose.blank_about=Створити задачу із шаблону за замовчуваннÑм. +issues.choose.ignore_invalid_templates=ÐедійÑні шаблони проігноровано +issues.choose.invalid_templates=Знайдено %v недійÑний(Ñ…) шаблон(ів) +issues.choose.invalid_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– міÑтить помилки: issues.no_ref=Ðе вказана гілка або тег issues.create=Створити задачу issues.new_label=Ðова мітка @@ -1250,7 +1326,6 @@ issues.filter_type.all_pull_requests=УÑÑ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ issues.filter_type.assigned_to_you=Призначене вам issues.filter_type.created_by_you=Створено вами issues.filter_type.mentioning_you=Ð’Ð°Ñ Ð·Ð³Ð°Ð´Ð°Ð½Ð¾ -issues.filter_type.review_requested=Відгук запитано issues.filter_type.reviewed_by_you=Перевірено вами issues.filter_sort=Сортувати issues.filter_sort.latest=Ðайновіші @@ -1317,6 +1392,7 @@ issues.save=Зберегти issues.label_title=Ðазва мітки issues.label_description=ÐžÐ¿Ð¸Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ issues.label_color=Колір мітки +issues.label_exclusive_order=ПорÑдок ÑÐ¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ issues.label_count=%d міток issues.label_open_issues=%d відкритих задач issues.label_edit=Редагувати @@ -1335,6 +1411,7 @@ issues.attachment.download=`ÐатиÑніть щоб завантажити "%s issues.subscribe=ПідпиÑатиÑÑ issues.unsubscribe=ВідпиÑатиÑÑ issues.unpin=Відкріпити +issues.max_pinned=Ви не можете прикріпити більше задач issues.lock=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.unlock=Ð Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.lock_duplicate=Задача не може бути заблокованим двічі. @@ -1356,6 +1433,7 @@ issues.comment_on_locked=Ви не можете коментувати заблРissues.delete=Видалити issues.tracker=ВідÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу +issues.timetracker_timer_start=ЗапуÑтити таймер issues.timetracker_timer_stop=Зупинити таймер issues.timetracker_timer_discard=Скинути таймер issues.timetracker_timer_manually_add=Додати Ñ‡Ð°Ñ @@ -1363,8 +1441,10 @@ issues.timetracker_timer_manually_add=Додати Ñ‡Ð°Ñ issues.tracker_auto_close=Таймер буде автоматично зупинено, коли Ñ†Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° буде закрита issues.tracking_already_started=`Ви вже почали відÑтежувати Ñ‡Ð°Ñ Ð´Ð»Ñ <a href="%s">іншої задачі</a>!` issues.stop_tracking=Зупинити таймер +issues.cancel_tracking=Скинути issues.del_time=Видалити цей журнал чаÑу issues.del_time_history=`видалив витрачений Ñ‡Ð°Ñ %s` +issues.add_time_manually=Вручну додати Ñ‡Ð°Ñ issues.add_time_hours=Години issues.add_time_minutes=Хвилини issues.add_time_sum_to_small=Ð§Ð°Ñ Ð½Ðµ введено. @@ -1389,6 +1469,11 @@ issues.due_date_remove=видалив(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s issues.due_date_overdue=ПроÑтрочено issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами допуÑтимого діапазону. Будь лаÑка викориÑтовуйте формат 'yyyy-mm-dd'. issues.dependency.title=ЗалежноÑті +issues.dependency.issue_no_dependencies=ЗалежноÑтей не вÑтановлено. +issues.dependency.pr_no_dependencies=ЗалежноÑтей не вÑтановлено. +issues.dependency.no_permission_1=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ %d залежноÑті +issues.dependency.no_permission_n=Ви не маєте дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ %d залежноÑтей +issues.dependency.no_permission.can_remove=Ви не маєте дозволу на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ†Ñ–Ñ”Ñ— залежноÑті, але можете видалити Ñ—Ñ—. issues.dependency.add=Додати залежніÑть… issues.dependency.cancel=Відмінити issues.dependency.remove=Видалити @@ -1416,6 +1501,7 @@ issues.dependency.add_error_dep_not_same_repo=Обидві задачі пови issues.review.self.approval=Ви не можете Ñхвалити влаÑний пулл-реквеÑÑ‚. issues.review.self.rejection=Ви не можете надіÑлати запит на зміну на влаÑний пулл-реквеÑÑ‚. issues.review.approve=зміни затверджено %s +issues.review.comment=рецензовано %s issues.review.dismissed=відхилено відгук %s %s issues.review.dismissed_label=Відхилено issues.review.left_comment=додав коментар @@ -1426,9 +1512,13 @@ issues.review.add_review_request=попроÑив рецензію від %s %s issues.review.remove_review_request=видалив запит на рецензію до %s %s issues.review.remove_review_request_self=відмовивÑÑ Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ñ‚Ð¸ %s issues.review.pending=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ +issues.review.pending.tooltip=Цей коментар наразі не видно іншим кориÑтувачам. Щоб відправити Ñвій коментар, виберіть "%s" -> "%s/%s/%s" у верхній чаÑтині Ñторінки. issues.review.review=Рецензії issues.review.reviewers=Рецензенти issues.review.outdated=ЗаÑтарілі +issues.review.outdated_description=ВміÑÑ‚ змінивÑÑ Ð· моменту ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ +issues.review.option.show_outdated_comments=Показати заÑтарілі коментарі +issues.review.option.hide_outdated_comments=Приховати заÑтарілі коментарі issues.review.show_outdated=Показати заÑтарілі issues.review.hide_outdated=Приховати заÑтарілі issues.review.show_resolved=Показати вирішене @@ -1437,6 +1527,7 @@ issues.review.resolve_conversation=Завершити Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.review.un_resolve_conversation=Поновити Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.review.resolved_by=позначив Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ð¼ issues.review.commented=Коментар +issues.review.official=Затверджено issues.assignee.error=Додано не вÑÑ–Ñ… виконавців через непередбачену помилку. issues.reference_issue.body=Тіло issues.content_history.deleted=видалено @@ -1445,19 +1536,25 @@ issues.content_history.created=Ñтворено issues.content_history.delete_from_history=Видалити з Ñ–Ñторії issues.content_history.delete_from_history_confirm=Видалити з Ñ–Ñторії? issues.content_history.options=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ +issues.reference_link=ПоÑиланнÑ: %s compare.compare_base=оÑнова compare.compare_head=порівнÑти pulls.desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° оглÑд коду. pulls.new=Ðовий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.view=ПереглÑнути запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ pulls.compare_changes=Ðовий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ pulls.compare_changes_desc=ПорівнÑти дві гілки Ñ– Ñтворити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½. +pulls.has_viewed_file=ПереглÑдів +pulls.expand_files=Розгорнути вÑÑ– файли +pulls.collapse_files=Згорнути вÑÑ– файли pulls.compare_base=злити в pulls.compare_compare=pull з pulls.switch_comparison_type=Перемкнути виглÑд порівнÑÐ½Ð½Ñ pulls.switch_head_and_base=ПомінÑти міÑцÑми оÑновну та базову гілку pulls.filter_branch=Фільтр по гілці +pulls.show_all_commits=Показати вÑÑ– коміти pulls.nothing_to_compare=Ці гілки однакові. Ðемає необхідноÑті Ñтворювати запитів на злиттÑ. pulls.nothing_to_compare_and_allow_empty_pr=Одинакові гілки. Цей PR буде порожнім. pulls.has_pull_request=`Запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ñ†Ð¸Ñ… гілок вже Ñ–Ñнує: <a href="%[1]s">%[2]s#%[3]d</a>` @@ -1471,7 +1568,9 @@ pulls.tab_files=Змінені файли pulls.reopen_to_merge=Будь лаÑка перевідкрийте цей запит щоб здіÑнити операцію злиттÑ. pulls.cant_reopen_deleted_branch=Цей запит не можна повторно відкрити, оÑкільки гілку видалено. pulls.merged=Злито +pulls.closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾ pulls.manually_merged=Ручне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.merged_info_text=Гілку %s тепер можна видалити. pulls.is_closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ»Ð¾ закрито. pulls.title_wip_desc=`<a href="#">Почніть заголовок з <strong>%s</strong></a> щоб запобігти випадковому злиттю запитів.` pulls.cannot_merge_work_in_progress=Цей пулл-реквеÑÑ‚ позначений Ñк прийнÑтий в опрацюваннÑ. @@ -1480,6 +1579,7 @@ pulls.add_prefix=Додати Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong> pulls.remove_prefix=Видалити Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong> pulls.data_broken=ЗміÑÑ‚ цього запиту було порушено внаÑлідок Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— Форком. Цей запит Ñ‚ÑгнетьÑÑ Ñ‡ÐµÑ€ÐµÐ· відÑутніÑть інформації про вилученнÑ. pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою. +pulls.is_checking=Перевірка конфліктів Ð·Ð»Ð¸Ñ‚Ñ‚Ñ ... pulls.required_status_check_failed=ДеÑкі необхідні перевірки виконані з помилками. pulls.required_status_check_missing=Декілька з необхідних перевірок відÑутні. pulls.required_status_check_administrator=Як адмініÑтратор ви вÑе одно можете об'єднати цей запит на злиттÑ. @@ -1525,46 +1625,70 @@ pulls.status_checks_failure=Декілька перевірок не були у pulls.status_checks_error=Декілька перевірок завершилиÑÑ Ð· помилками pulls.status_checks_requested=Обов'Ñзково pulls.status_checks_details=Подробиці +pulls.status_checks_hide_all=Приховати вÑÑ– перевірки +pulls.status_checks_show_all=Показати вÑÑ– перевірки pulls.update_branch=Оновити гілку шлÑхом Ð·Ð»Ð¸Ñ‚Ñ‚Ñ pulls.update_branch_rebase=Оновити гілку перебазуваннÑм pulls.update_branch_success=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ пройшло уÑпішно pulls.update_not_allowed=Ви не можете оновити гілку pulls.outdated_with_base_branch=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° заÑтаріла відноÑно базової гілки +pulls.close=Закрити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ pulls.closed_at=`закрив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.reopened_at=`повторно відкрив цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a id="%[1]s" href="#%[1]s">%[2]s</a>` +pulls.cmd_instruction_hint=ПереглÑнути інÑтрукції командного Ñ€Ñдка +pulls.cmd_instruction_merge_title=Об'єднати +pulls.cmd_instruction_merge_desc=Об'єднати зміни Ñ– оновити на Gitea. +pulls.clear_merge_message=ОчиÑтити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.auto_merge_button_when_succeed=(Якщо перевірки уÑпішні) +pulls.auto_merge_when_succeed=Ðвтоматичне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ–ÑÐ»Ñ ÑƒÑпішного Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… перевірок +pulls.auto_merge_cancel_schedule=СкаÑувати автоматичне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.auto_merge_not_scheduled=Ðвтоматичне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту не заплановано. +pulls.delete.title=Видалити цей запит на злиттÑ? +pulls.upstream_diverging_merge_confirm=Хочете об’єднати "%[1]s" з "%[2]s"? +pull.deleted_branch=(видалена):%s +pull.agit_documentation=ПереглÑнути документацію про AGit milestones.new=Ðовий етап milestones.closed=Закрито %s +milestones.update_ago=Оновлено %s milestones.no_due_date=Ðемає дати Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ milestones.open=Відкрити milestones.close=Закрити +milestones.completeness=<strong>%d%%</strong> завершено milestones.create=Створити етап milestones.title=Заголовок milestones.desc=ÐžÐ¿Ð¸Ñ milestones.due_date=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ (опціонально) milestones.clear=ОчиÑтити milestones.invalid_due_date_format=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¼Ð°Ñ” бути в форматі 'рррр-мм-дд'. +milestones.create_success=Етап "%s" Ñтворено. milestones.edit=Редагувати етап milestones.edit_subheader=Створюйте етапи Ð´Ð»Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— ваших задач. milestones.cancel=Відмінити milestones.modify=Оновити етап +milestones.edit_success=Етап '%s' оновлено. milestones.deletion=Видалити етап milestones.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ призведе до його Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð· уÑÑ–Ñ… пов'Ñзаних задач. Продовжити? milestones.deletion_success=Етап уÑпішно видалено. milestones.filter_sort.name=Ðазва -milestones.filter_sort.least_complete=Менш повне -milestones.filter_sort.most_complete=Більш повне +milestones.filter_sort.earliest_due_data=Ðайраніший Ñтрок +milestones.filter_sort.latest_due_date=ОÑтанній Ñтрок milestones.filter_sort.most_issues=Ðайбільш задач milestones.filter_sort.least_issues=Ðайменш задач +signing.will_sign=Цей коміт буде підпиÑано ключем "%s". +signing.wont_sign.nokey=Ðемає ключа Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту. +signing.wont_sign.never=Коміти ніколи не підпиÑуютьÑÑ. +signing.wont_sign.always=Коміти завжди підпиÑуютьÑÑ. +ext_wiki=ДоÑтуп до зовнішньої вікі ext_wiki.desc=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню вікі. wiki=Вікі @@ -1575,6 +1699,8 @@ wiki.create_first_page=Створити першу Ñторінку wiki.page=Сторінка wiki.filter_page=Фільтр Ñторінок wiki.new_page=Сторінка +wiki.page_title=Заголовок Ñторінки +wiki.page_content=ЗміÑÑ‚ Ñторінки wiki.default_commit_message=Ðапишіть примітку про Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— Ñторінки (необов'Ñзково). wiki.save_page=Зберегти Ñторінку wiki.last_commit_info=%s редагував цю Ñторінку %s @@ -1584,12 +1710,16 @@ wiki.file_revision=Ревізії Ñторінки wiki.wiki_page_revisions=Ревізії вікі Ñторінок wiki.back_to_wiki=ПовернутиÑÑŒ на Ñторінку Вікі wiki.delete_page_button=Видалити Ñторінку +wiki.delete_page_notice_1=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÑ–-Ñторінки "%s" не може бути ÑкаÑовано. Продовжити? wiki.page_already_exists=Вікі-Ñторінка з таким Ñамим ім'Ñм вже Ñ–Ñнує. +wiki.reserved_page=Ðазва Ñторінки вікі "%s" зарезервована. wiki.pages=Сторінки wiki.last_updated=ОÑтанні Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %s wiki.page_name_desc=Введіть назву вікі-Ñторінки. ДеÑкі із Ñпеціальних імен: 'Home', '_Sidebar' та '_Footer'. activity=ÐктивніÑть +activity.navbar.pulse=ÐŸÑƒÐ»ÑŒÑ +activity.navbar.code_frequency=ЧаÑтота коду activity.period.filter_label=Період: activity.period.daily=1 день activity.period.halfweekly=3 дні @@ -1655,7 +1785,9 @@ activity.git_stats_and_deletions=та activity.git_stats_deletion_1=%d видалений activity.git_stats_deletion_n=%d видалені +contributors.contribution_type.filter_label=Тип внеÑку: contributors.contribution_type.commits=Коміти +contributors.contribution_type.deletions=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.desc=У налаштуваннÑÑ… ви можете змінювати різні параметри цього Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ @@ -1671,6 +1803,7 @@ settings.hooks=Веб-хуки settings.githooks=Git хуки settings.basic_settings=Базові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.mirror_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð»Ð° +settings.mirror_settings.docs.doc_link_title=Як віддзеркалити Ñховища? settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій settings.mirror_settings.direction=ÐапрÑмок settings.mirror_settings.direction.pull=Pull @@ -1683,7 +1816,10 @@ settings.mirror_settings.push_mirror.add=Додати Push дзеркало settings.sync_mirror=Синхронізувати зараз settings.site=Веб-Ñайт settings.update_settings=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ +settings.update_mirror_settings=Оновити параметри дзеркала +settings.branches.switch_default_branch=Змінити типову гілку settings.branches.update_default_branch=Оновити гілку за замовчуваннÑм +settings.branches.add_new_rule=Додати нове правило settings.advanced_settings=Додаткові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.wiki_desc=Увімкнути репозиторії Вікі settings.use_internal_wiki=ВикориÑтовувати вбудовані Вікі @@ -1702,6 +1838,9 @@ settings.tracker_url_format_error=Ðеправильний формат URL-ад settings.tracker_issue_style=Формат номеру Ð´Ð»Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ñ— ÑиÑтеми обліку задач settings.tracker_issue_style.numeric=Цифровий settings.tracker_issue_style.alphanumeric=Буквено-цифровий +settings.tracker_issue_style.regexp=РегулÑрний вираз +settings.tracker_issue_style.regexp_pattern=Шаблон регулÑрного виразу +settings.tracker_issue_style.regexp_pattern_desc=Першу захоплену групу буде викориÑтано заміÑть <code>{index}</code>. settings.tracker_url_format_desc=ВикориÑтовуйте шаблони <code>{user}</code>, <code>{repo}</code> та <code>{index}</code> Ð´Ð»Ñ Ñ–Ð¼ÐµÐ½Ñ– кориÑтувача, репозиторію та номеру задічі. settings.enable_timetracker=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу settings.allow_only_contributors_to_track_time=Враховувати тільки учаÑників розробки в підрахунку чаÑу @@ -1709,9 +1848,19 @@ settings.pulls_desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² репРsettings.pulls.ignore_whitespace=Ігнорувати пробіл у конфліктах settings.pulls.enable_autodetect_manual_merge=Увімкнути Ð°Ð²Ñ‚Ð¾Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¾Ð³Ð¾ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ (Примітка: у деÑких оÑобливий випадках можуть виникнуть помилки) settings.pulls.default_delete_branch_after_merge=ВидалÑти гілку запиту злиттÑ, коли його прийнÑто +settings.releases_desc=Увімкнути релізи Ñховища +settings.projects_desc=Увімкнути проєкти +settings.projects_mode_repo=Тільки проєкти Ñховища +settings.projects_mode_owner=Тільки проєкти кориÑтувачів або організацій settings.projects_mode_all=Ð’ÑÑ– проєкти settings.admin_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора settings.admin_enable_health_check=Включити перевірки працездатноÑті репозиторію (git fsck) +settings.admin_code_indexer=ІндекÑатор коду +settings.admin_stats_indexer=ІндекÑатор ÑтатиÑтики коду +settings.admin_indexer_commit_sha=ОÑтанній індекÑований SHA +settings.admin_indexer_unindexed=Ðе індекÑовано +settings.reindex_button=Додати до черги на реіндекÑацію +settings.reindex_requested=Запит на реіндекÑацію settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити задачу за допомогою коміта, зробленого не в головній гілці settings.danger_zone=Ðебезпечна зона settings.new_owner_has_same_repo=Ðовий влаÑник вже має репозиторій з такою назвою. Будь лаÑка, виберіть інше ім'Ñ. @@ -1822,6 +1971,8 @@ settings.event_delete_desc=Гілку або мітку було видаленРsettings.event_fork=Форк settings.event_fork_desc=Репозиторій було форкнуто. settings.event_wiki=Вікі +settings.event_statuses=СтатуÑи +settings.event_statuses_desc=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ оновлено з API. settings.event_release=Реліз settings.event_release_desc=Реліз опублікований, оновлений або видалений з репозиторіÑ. settings.event_push=Push @@ -1866,6 +2017,21 @@ settings.hook_type=Тип хука settings.slack_token=Токен settings.slack_domain=Домен settings.slack_channel=Канал +settings.web_hook_name_gitea=Gitea +settings.web_hook_name_gogs=Gogs +settings.web_hook_name_slack=Slack +settings.web_hook_name_discord=Discord +settings.web_hook_name_dingtalk=DingTalk +settings.web_hook_name_telegram=Telegram +settings.web_hook_name_matrix=Matrix +settings.web_hook_name_msteams=Microsoft Teams +settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite +settings.web_hook_name_feishu=Feishu +settings.web_hook_name_larksuite=Lark Suite +settings.web_hook_name_wechatwork=WeCom (Wechat Work) +settings.web_hook_name_packagist=Packagist +settings.packagist_username=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Packagist +settings.packagist_api_token=Токен API settings.deploy_keys=Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.add_deploy_key=Додати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.deploy_key_desc=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупні тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ. Це не те ж Ñаме що Ñ– SSH-ключі аккаунта. @@ -1881,8 +2047,11 @@ settings.deploy_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° розгортк settings.deploy_key_deletion_success=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ видалено. settings.branches=Гілки settings.protected_branch=ЗахиÑÑ‚ гілки +settings.protected_branch.save_rule=Зберегти правило +settings.protected_branch.delete_rule=Видалити правило settings.protected_branch_can_push=Дозволити push? settings.protected_branch_can_push_yes=Ви можете виконувати push +settings.branch_protection=ЗахиÑÑ‚ гілки '<b>%s</b>' settings.protect_this_branch=ЗахиÑтити цю гілку settings.protect_this_branch_desc=Запобігає видаленню гілки та обмежує Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð² ній push та злиттÑ. settings.protect_disable_push=Заборонити Push @@ -1890,7 +2059,11 @@ settings.protect_disable_push_desc=Ð”Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки буде Ð·Ð°Ð±Ð¾Ñ settings.protect_enable_push=Дозволити Push settings.protect_enable_push_desc=Будь-хто із правом запиÑу зможе виконувати push Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки (за виключеннÑм force push). settings.protect_check_status_contexts=Увімкнути перевірку Ñтану +settings.protect_status_check_patterns=Шаблони перевірки Ñтану: settings.protect_check_status_contexts_list=Перевірки ÑтатуÑу знайдено Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–ÑŽ за минулий тиждень +settings.protect_status_check_matched=Збіг +settings.protect_invalid_status_check_pattern=ÐедійÑний шаблон перевірки Ñтану: "%s". +settings.protect_no_valid_status_check_patterns=Ðемає дійÑних шаблонів перевірки Ñтану. settings.protect_required_approvals=Ðеобхідно ÑхваленнÑ: settings.dismiss_stale_approvals=Відхилити заÑтарілі Ð¿Ð¾Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ settings.dismiss_stale_approvals_desc=Коли нові коміти що змінюють вміÑÑ‚ пулл-запиту відправлÑютьÑÑ Ð² гілку, Ñтарі Ð¿Ð¾Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´ÑƒÑ‚ÑŒ відхилені. @@ -1924,7 +2097,6 @@ settings.chat_id=Чат ID settings.matrix.homeserver_url=URL домашньої Ñторінки settings.matrix.room_id=Ðомер кімнати settings.matrix.message_type=Тип Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ -settings.archive.button=Ðрхівний репозиторій settings.archive.header=Відправити репозиторій в архів settings.archive.success=Репозиторію уÑпішно приÑвоєно ÑÑ‚Ð°Ñ‚ÑƒÑ Ð°Ñ€Ñ…Ñ–Ð²Ð½Ð¾Ð³Ð¾. settings.archive.error=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñпробі архівувати репозиторій. Докладнішу інформацію див. у журналі. @@ -2012,6 +2184,11 @@ diff.protected=Захищений diff.image.side_by_side=Пліч-о-пліч diff.image.swipe=Свайп diff.image.overlay=Оверлей +diff.show_file_tree=Показати дерево файлів +diff.hide_file_tree=Сховати дерево файлів +diff.submodule_added=Підмодуль %[1]s додано в %[2]s +diff.submodule_deleted=Підмодуль %[1]s видалено з %[2]s +diff.submodule_updated=Підмодуль %[1]s оновлено: %[2]s releases.desc=ВідÑлідковувати верÑÑ–Ñ— проєкту Ñ– завантаженнÑ. release.releases=Релізи @@ -2031,6 +2208,9 @@ release.edit_subheader=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² допоможе ва release.tag_name=Ðазва тегу release.target=Ціль release.tag_helper=Виберіть Ñ–Ñнуючий тег або Ñтворіть новий. +release.title=Ðазва релізу +release.title_empty=Заголовок не може бути порожнім. +release.message=Опишіть цей реліз release.prerelease_desc=Позначити Ñк пре-реліз release.prerelease_helper=Позначте цей випуÑк непридатним Ð´Ð»Ñ ÐŸÐ ÐžÐ” викориÑтаннÑ. release.cancel=Відмінити @@ -2051,33 +2231,57 @@ release.downloads=Завантажити release.download_count=ЗавантаженнÑ: %s release.add_tag_msg=ВикориÑтовуйте заголовок Ñ– зміÑÑ‚ релізу Ñк Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñк тег повідомленнÑ. release.add_tag=Створити тільки мітку +release.releases_for=Релізи Ð´Ð»Ñ %s branch.name=Ім'Ñ Ð³Ñ–Ð»ÐºÐ¸ +branch.already_exists=Гілка з назвою "%s" вже Ñ–Ñнує. branch.delete_head=Видалити +branch.delete=`Видалити гілку "%s"` branch.delete_html=Видалити гілку +branch.deletion_success=Гілку "%s" видалено. +branch.deletion_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ гілку "%s". branch.create_branch=Створити гілку %s +branch.create_from=`з "%s"` +branch.create_success=Створено гілку "%s". branch.deleted_by=Видалено %s +branch.restore_success=Гілку "%s" відновлено. +branch.restore_failed=Ðе вдалоÑÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ гілку "%s". +branch.protected_deletion_failed=Гілка "%s" захищена. Її неможливо видалити. +branch.restore=`Відновити гілку "%s"` +branch.download=`Завантажити гілку "%s"` +branch.rename=`Перейменувати гілку "%s"` branch.included_desc=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° Ñ” чаÑтиною типової гілки branch.included=Включено branch.create_new_branch=Створити гілку з гілки: branch.confirm_create_branch=Створити гілку +branch.rename_branch_to=Перейменувати "%s" на: branch.confirm_rename_branch=Перейменувати гілку branch.create_branch_operation=Створити гілку branch.new_branch=Створити нову гілку +branch.new_branch_from=`Створити нову гілку з "%s"` branch.renamed=Гілку %s перейменовано на %s. +branch.rename_default_or_protected_branch_error=Лише адмініÑтратори можуть перейменовувати типові або захищені гілки. tag.create_tag=Створити тег %s topic.manage_topics=Керувати тематичними мітками topic.done=Готово +topic.count_prompt=Ви не можете вибрати більше ніж 25 тем +topic.format_prompt=Теми мають починатиÑÑ Ð· літери або цифри, можуть міÑтити дефіÑи ('-') Ñ– крапки ('.'), мати довжину до 35 Ñимволів. Літери повинні бути малими. +find_file.go_to_file=Перейти до файлу error.csv.too_large=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він завеликий. error.csv.unexpected=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він міÑтить неочікуваний Ñимвол в Ñ€Ñдку %d Ñ– Ñтовпці %d. error.csv.invalid_field_count=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він має неправильну кількіÑть полів у Ñ€Ñдку %d. [graphs] +component_loading=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ %s... +component_loading_failed=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ %s +component_failed_to_load=СталаÑÑŒ непередбачена помилка. +code_frequency.what=чаÑтота коду +contributors.what=внеÑки [org] org_name_holder=Ðазва організації @@ -2102,11 +2306,13 @@ team_permission_desc=Права доÑтупу team_unit_desc=Дозволити доÑтуп до розділів репозиторію team_unit_disabled=(Вимкнено) +form.name_reserved=Ðазву організації "%s" зарезервовано. form.create_org_not_allowed=Вам не дозволено Ñтворювати організації. settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.options=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ settings.full_name=Повне ім'Ñ +settings.email=Контактна адреÑа електронної пошти settings.website=Веб-Ñайт settings.location=Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ settings.permission=Дозволи @@ -2152,8 +2358,10 @@ teams.leave.detail=Покинути %s? teams.can_create_org_repo=Створити репозиторії teams.can_create_org_repo_helper=УчаÑники можуть Ñтворювати нові репозиторії в організації. Ðвтор отримає доÑтуп адмініÑтратора до нового репозиторію. teams.none_access=Ðемає доÑтупу -teams.read_access=Прочитані +teams.general_access=Загальний доÑтуп +teams.read_access=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ teams.read_access_helper=УчаÑники можуть переглÑдати та клонувати репозиторії команд. +teams.write_access=Ð—Ð°Ð¿Ð¸Ñ teams.write_access_helper=УчаÑники можуть читати Ñ– виконувати push в репозиторії команд. teams.admin_access=ДоÑтуп адмініÑтратора teams.admin_access_helper=УчаÑники можуть виконувати pull, push в репозиторії команд Ñ– додавати Ñпівавторів в команду. @@ -2164,6 +2372,7 @@ teams.members=УчаÑники команди teams.update_settings=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ teams.delete_team=Видалити команду teams.add_team_member=Додати учаÑника команди +teams.invite_team_member=ЗапроÑити до %s teams.delete_team_title=Видалити команду teams.delete_team_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ ÑкаÑовує доÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð´Ð»Ñ Ñ—Ñ— учаÑників. Продовжити? teams.delete_team_success=Команду було видалено. @@ -2179,6 +2388,7 @@ teams.add_all_repos_desc=Це додаÑть вÑÑ– репозиторії орРteams.add_duplicate_users=КориÑтувач уже Ñ” членом команди. teams.repos.none=Ð”Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ немає доÑтупних репозиторіїв. teams.members.none=Ðемає членів в цій команді. +teams.members.blocked_user=Ðе вдаєтьÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ кориÑтувача, оÑкільки він заблокований організацією. teams.specific_repositories=Конкретні репозиторії teams.specific_repositories_helper=УчаÑники матимуть доÑтуп лише до репозиторіїв, Ñкі були Ñвно додані до команди. Вибір цього пункту <strong>не призводить</strong> до автоматичного Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ—Ð², доданих з <i>Ð’ÑÑ– репозиторії</i>. teams.all_repositories=Ð’ÑÑ– репозиторії @@ -2186,15 +2396,30 @@ teams.all_repositories_helper=Команда має доÑтуп до вÑÑ–Ñ… Ñ teams.all_repositories_read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ПереглÑд</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати та клонувати Ñ—Ñ…. teams.all_repositories_write_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ЗапиÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати та виконувати push в репозиторіÑÑ…. teams.all_repositories_admin_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ÐдмініÑтруваннÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати, виконувати push та додавати Ñпівробітників. +teams.invite.title=Ð’Ð°Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñили приєднатиÑÑ Ð´Ð¾ команди <strong>%s</strong> в організації <strong>%s</strong>. +teams.invite.description=Будь лаÑка, натиÑніть на кнопку нижче, щоб приєднатиÑÑ Ð´Ð¾ команди. +view_as_role=ПереглÑнути Ñк: %s +view_as_public_hint=Ви переглÑдаєте README Ñк публічний кориÑтувач. +view_as_member_hint=Ви переглÑдаєте README Ñк член цієї організації. +worktime=Ð§Ð°Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ +worktime.date_range_start=Дата початку +worktime.date_range_end=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ +worktime.query=Запит +worktime.time=Ð§Ð°Ñ +worktime.by_milestones=За етапами [admin] +maintenance=Технічне обÑÐ»ÑƒÐ³Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ dashboard=Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ +self_check=Самоперевірка users=Облікові запиÑи кориÑтувачів organizations=Організації +assets=РеÑурÑи коду repositories=Репозиторії hooks=Веб-хуки +integrations=Інтеграції authentication=Джерела автентифікації emails=Електронні адреÑи КориÑтувача config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ @@ -2205,6 +2430,7 @@ monitor=Моніторинг first_page=Перша last_page=ОÑÑ‚Ð°Ð½Ð½Ñ total=Разом: %d +settings=ÐдмініÑтративні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ dashboard.statistic=ПідÑумок dashboard.system_status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑиÑтеми @@ -2271,6 +2497,10 @@ dashboard.total_gc_time=Загальна пауза збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ dashboard.total_gc_pause=Загальна пауза збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC) dashboard.last_gc_pause=ОÑÑ‚Ð°Ð½Ð½Ñ Ð¿Ð°ÑƒÐ·Ð° збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC) dashboard.gc_times=КількіÑть запуÑків збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC) +dashboard.update_checker=Перевірка оновлень +dashboard.sync_branch.started=Розпочато Ñинхронізацію гілок +dashboard.rebuild_issue_indexer=Перебудувати індекÑатор задач +dashboard.sync_repo_licenses=Синхронізувати ліцензії Ñховища users.user_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¸Ð¼Ð¸ запиÑами кориÑтувачів users.new_account=Створити обліковий Ð·Ð°Ð¿Ð¸Ñ @@ -2279,12 +2509,15 @@ users.full_name=Повне ім'Ñ users.activated=Ðктивовано users.admin=ÐдмініÑтратор users.restricted=Обмежено +users.reserved=Зарезервовано +users.bot=Бот users.2fa=2FA users.repos=Репозиторії users.created=Створено users.last_login=ОÑтанній вхід users.never_login=Ðіколи не входив users.send_register_notify=ÐадіÑлати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ реєÑтрацію кориÑтувача +users.new_success=Обліковий Ð·Ð°Ð¿Ð¸Ñ "%s" Ñтворено. users.edit=Редагувати users.auth_source=Джерело автентифікації users.local=Локальні @@ -2304,8 +2537,10 @@ users.allow_import_local=Може імпортувати локальні реп users.allow_create_organization=Може Ñтворювати організацій users.update_profile=Оновити обліковий Ð·Ð°Ð¿Ð¸Ñ users.delete_account=Видалити цей обліковий Ð·Ð°Ð¿Ð¸Ñ +users.cannot_delete_self=Ви не можете видалити Ñебе users.still_own_repo=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще володіє одним або кількома репозиторіÑми, Ñпочатку вам потрібно видалити або передати Ñ—Ñ…. users.still_has_org=Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще Ñ” учаÑником однієї або декількох організацій. Ð”Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ, покиньте або видаліть організації. +users.purge=Видалити кориÑтувача users.deletion_success=Обліковий Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача було видалено. users.reset_2fa=Скинути 2FA users.list_status_filter.menu_text=Фільтр @@ -2320,6 +2555,7 @@ users.list_status_filter.is_prohibit_login=Вхід заборонено users.list_status_filter.not_prohibit_login=Вхід дозволено users.list_status_filter.is_2fa_enabled=2FA увімкнена users.list_status_filter.not_2fa_enabled=2FA вимкнена +users.details=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ кориÑтувача emails.email_manage_panel=Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾ÑŽ кориÑтувача emails.primary=Головний @@ -2332,6 +2568,10 @@ emails.updated=Електронну пошту оновлено emails.not_updated=Ðе вдалоÑÑŒ оновити адреÑу електронної пошти: %v emails.duplicate_active=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа вже активна Ð´Ð»Ñ Ñ–Ð½ÑˆÐ¾Ð³Ð¾ кориÑтувача. emails.change_email_header=Редагувати влаÑтивоÑті електронної пошти +emails.change_email_text=Ви впевнені, що хочете оновити адреÑу електронної пошти? +emails.delete_desc=Ви впевнені, що бажаєте видалити цю електронну адреÑу? +emails.deletion_success=Електронну адреÑу видалено. +emails.delete_primary_email_error=Ви не можете видалити оÑновну електронну адреÑу. orgs.org_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми orgs.name=Ðазва @@ -2347,12 +2587,20 @@ repos.name=Ðазва repos.private=Приватний repos.issues=Задачі repos.size=Розмір +repos.lfs_size=Розмір LFS +packages.total_size=Загальний розмір: %s +packages.unreferenced_size=Розмір без поÑилань: %s +packages.cleanup=ОчиÑтити проÑтрочені дані +packages.cleanup.success=ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñтрочених даних завершено packages.owner=ВлаÑник +packages.creator=Ðвтор packages.name=Ðазва +packages.version=ВерÑÑ–Ñ packages.type=Тип -packages.repository=Репозиторій +packages.repository=Сховище packages.size=Розмір +packages.published=Опубліковано defaulthooks=Веб-хуки за замовчуваннÑм defaulthooks.add_webhook=Додати веб-хук за замовчуваннÑм @@ -2385,6 +2633,7 @@ auths.attribute_name=Ðтрибут імені auths.attribute_surname=Ðтрибут Surname auths.attribute_mail=Ðтрибут Email auths.attribute_ssh_public_key=Ðтрибут Відкритий SSH ключ +auths.attribute_avatar=ВлаÑтивоÑті аватару auths.attributes_in_bind=ВитÑгувати атрибути в контекÑті Bind DN auths.allow_deactivate_all=Дозволити порожньому результату пошуку відключити вÑÑ–Ñ… кориÑтувачів auths.use_paged_search=ВикориÑтовувати поÑторінковий пошук @@ -2396,6 +2645,7 @@ auths.restricted_filter_helper=Залиште пуÑтим, щоб не вÑта auths.group_search_base=Пошукова база груп DN auths.group_attribute_list_users=Ðтрибут групи зі ÑпиÑком кориÑтувачів auths.user_attribute_in_group=Ðтрибути кориÑтувача в групі +auths.enable_ldap_groups=Увімкнути групи LDAP auths.ms_ad_sa=Ðтрибути пошуку MS AD auths.smtp_auth=Тип автентифікації SMTP auths.smtphost=SMTP хоÑÑ‚ @@ -2438,9 +2688,15 @@ auths.tips=Поради auths.tips.oauth2.general=OAuth2 Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ auths.tip.oauth2_provider=ПоÑтачальник OAuth2 auths.tip.nextcloud=`ЗареєÑтруйте нового Ñпоживача OAuth у вашому екземплÑрі за допомогою наÑтупного меню "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -> Безпека -> клієнт OAuth 2.0"` +auths.tip.dropbox=Створити новий додаток на %s +auths.tip.gitlab_new=ЗареєÑтрувати новий додаток на %s +auths.tip.google_plus=Отримайте облікові дані клієнта OAuth2 з конÑолі Google API за адреÑою %s +auths.tip.discord=ЗареєÑтрувати новий додаток на %s +auths.tip.gitea=ЗареєÑтруйте новий додаток OAuth2. ПоÑібник можна знайти за поÑиланнÑм %s auths.tip.mastodon=Введіть URL Ñпеціального екземплÑра Ð´Ð»Ñ ÐµÐºÐ·ÐµÐ¼Ð¿Ð»Ñра mastodon, Ñкий ви хочете автентифікувати за допомогою (або викориÑтовувати за замовчуваннÑм) auths.edit=Редагувати джерело автентифікації auths.activated=Ð¦Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð° +auths.new_success=Ðвтентифікацію "%s" додано. auths.update_success=Параметри аутентифікації оновлені. auths.update=Оновити джерело автентифікації auths.delete=Видалити джерело автентифікації @@ -2448,6 +2704,7 @@ auths.delete_auth_title=Видалити джерело автентифікац auths.delete_auth_desc=Це джерело аутентифікації буде видалене, ви впевнені, що ви хочете продовжити? auths.still_in_used=Ð¦Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° ÑправжноÑті доÑÑ– викориÑтовуєтьÑÑ Ð´ÐµÑкими кориÑтувачами. Видаліть або змініть Ð´Ð»Ñ Ñ†Ð¸Ñ… кориÑтувачів тип входу в ÑиÑтему. auths.deletion_success=Канал аутентифікації уÑпішно знищений. +auths.login_source_exist=Джерело автентифікації "%s" вже Ñ–Ñнує. auths.login_source_of_type_exist=Джерело автентифікації такого типу вже наÑвне. config.server_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ñервера @@ -2462,6 +2719,7 @@ config.disable_router_log=Вимкнути Ð»Ð¾Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ð¾ÑƒÑ‚ÐµÑ€Ñƒ config.run_user=ЗапуÑк від імені КориÑтувача config.run_mode=Режим Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ config.git_version=ВерÑÑ–Ñ Git +config.app_data_path=ШлÑÑ… до даних додатка config.repo_root_path=Кореневий шлÑÑ… Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ config.lfs_root_path=Кореневої шлÑÑ… LFS config.log_file_root_path=ШлÑÑ… до лог файлу @@ -2487,7 +2745,7 @@ config.db_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð±Ð°Ð·Ð¸ даних config.db_type=Тип config.db_host=ХоÑÑ‚ config.db_name=Ім'Ñ -config.db_user=Ім'Ñ ÐºÑ€Ð¸Ñтувача +config.db_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача config.db_schema=Схема config.db_ssl_mode=SSL config.db_path=ШлÑÑ… @@ -2519,9 +2777,13 @@ config.queue_length=Довжина черги config.deliver_timeout=Затримка доÑтавки config.skip_tls_verify=ПропуÑтити перевірку TLS +config.mailer_config=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ config.mailer_enabled=Увімкнено +config.mailer_enable_helo=Увімкнути HELO config.mailer_name=Ім'Ñ -config.mailer_smtp_port=SMTP порт +config.mailer_protocol=Протокол +config.mailer_smtp_addr=ÐдреÑа SMTP +config.mailer_smtp_port=Порт SMTP config.mailer_user=КориÑтувач config.mailer_use_sendmail=ВикориÑтовувати Sendmail config.mailer_sendmail_path=ШлÑÑ… до Sendmail @@ -2529,6 +2791,9 @@ config.mailer_sendmail_args=Додаткові аргументи до Sendmail config.mailer_sendmail_timeout=Тайм-аут Sendmail config.test_email_placeholder=ÐдреÑа електронної пошти (наприклад, test@example.com) config.send_test_mail=Відправити теÑтового лиÑта +config.send_test_mail_submit=ÐадіÑлати +config.test_mail_failed=Ðе вдалоÑÑ Ð½Ð°Ð´Ñ–Ñлати теÑтовий лиÑÑ‚ "%s": %v +config.test_mail_sent=ТеÑтовий лиÑÑ‚ надіÑлано "%s". config.oauth_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ OAuth config.oauth_enabled=Увімкнено @@ -2538,6 +2803,9 @@ config.cache_adapter=Ðдаптер кешу config.cache_interval=Інтервал ÐºÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ config.cache_conn=ÐŸÑ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ кешу config.cache_item_ttl=Ð§Ð°Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… кешу +config.cache_test=Перевірити кеш +config.cache_test_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ кеш: %v. +config.cache_test_slow=Перевірка кешу уÑпішна, але відповідь повільна: %s. config.session_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ ÑеÑÑ–Ñ— config.session_provider=Провайдер ÑеÑÑ–Ñ— @@ -2566,11 +2834,14 @@ config.git_pull_timeout=Тайм-аут операції Pull config.git_gc_timeout=Тайм-аут операції збирача ÑÐ¼Ñ–Ñ‚Ñ‚Ñ (GC) config.log_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ñƒ +config.logger_name_fmt=Журнал: %s config.disabled_logger=Вимкнено config.access_log_mode=Режим доÑтупу до журналу config.xorm_log_sql=Журнал SQL +config.set_setting_failed=Ðе вдалоÑÑ Ð²Ñтановити параметр %s +monitor.stats=СтатиÑтика monitor.cron=Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ cron monitor.name=Ім'Ñ @@ -2579,10 +2850,13 @@ monitor.next=ÐаÑтупного разу monitor.previous=Попереднього разу monitor.execute_times=КількіÑть виконань monitor.process=Запущені процеÑи +monitor.download_diagnosis_report=Завантажити звіт діагноÑтики monitor.desc=ÐžÐ¿Ð¸Ñ monitor.start=Ð§Ð°Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ monitor.execute_time=Ð§Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ +monitor.last_execution_result=Результат monitor.process.cancel=Зупинити Ð¿Ñ€Ð¾Ñ†ÐµÑ +monitor.process.cancel_desc=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑу може призвеÑти до втрати даних monitor.process.children=Дочірні процеÑи monitor.queues=Черги @@ -2598,6 +2872,8 @@ monitor.queue.settings.maxnumberworkers.placeholder=Поточний %[1]d monitor.queue.settings.maxnumberworkers.error=МакÑимальна кількіÑть робочих потоків має бути чиÑлом monitor.queue.settings.submit=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ monitor.queue.settings.changed=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾ +monitor.queue.settings.remove_all_items=Видалити вÑе +monitor.queue.settings.remove_all_items_done=УÑÑ– елементи черги видалено. notices.system_notice_list=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑиÑтеми notices.view_detail_header=ПереглÑнути деталі Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ @@ -2613,11 +2889,13 @@ notices.desc=ÐžÐ¿Ð¸Ñ notices.op=Оп. notices.delete_success=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑиÑтеми були видалені. +self_check.no_problem_found=Ðаразі проблем не виÑвлено. +self_check.startup_warnings=ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð·Ð°Ð¿ÑƒÑку: [action] create_repo=Ñтворив(ла) репозиторій <a href="%s">%s</a> rename_repo=репозиторій перейменовано з <code>%[1]s</code> на <a href="%[2]s">%[3]s</a> -commit_repo=надіÑлав зміни (push) до <a href="%[2]s">%[3]s</a> о <a href="%[1]s">%[4]s</a> +commit_repo=надіÑлав зміни (push) до <a href="%[2]s">%[3]s</a> в <a href="%[1]s">%[4]s</a> create_issue=`відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>` close_issue=`закрив задачу <a href="%[1]s">%[3]s#%[2]s</a>` reopen_issue=`повторно відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>` @@ -2659,7 +2937,6 @@ seconds=%d Ñекунди minutes=%d хвилини hours=%d години days=%d дні -weeks=%d тижднів months=%d міÑÑці years=%d роки raw_seconds=Ñекунди @@ -2667,6 +2944,7 @@ raw_minutes=хвилини [dropzone] default_message=ПеретÑгніть файли або натиÑніть тут, щоб завантажити. +invalid_input_type=Ðеможливо завантажити файли цього типу. file_too_big=Розмір файлу ({{filesize}} MB), що більше ніж макÑимальний розмір: ({{maxFilesize}} MB). remove_file=Видалити файл @@ -2697,14 +2975,90 @@ error.no_unit_allowed_repo=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодног error.unit_not_allowed=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього репозиториÑ. [packages] +no_metadata=Ðемає метаданих. filter.type=Тип +filter.type.all=Ð’ÑÑ– +filter.no_result=Ваш фільтр не дав результатів. +filter.container.tagged=З міткою +filter.container.untagged=Без мітки +installation=Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ +requirements=Вимоги +dependencies=ЗалежноÑті +keywords=Ключові Ñлова +details=Подробиці details.author=Ðвтор +details.project_site=Сторінка проєкту +details.repository_site=Сторінка Ñховища +details.documentation_site=Сторінка документації +details.license=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ +assets=РеÑурÑи +versions=ВерÑÑ–Ñ— +versions.view_all=ПереглÑнути вÑе +dependency.version=ВерÑÑ–Ñ +search_in_external_registry=Шукати в %s +alpine.registry=Ðалаштуйте цей реєÑтр, додавши URL у ваш файл <code>/etc/apk/repositories</code>: +alpine.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище alpine.repository.branches=Гілки alpine.repository.repositories=Репозиторії +alpine.repository.architectures=Ðрхітектури +arch.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище arch.repository.repositories=Репозиторії +arch.repository.architectures=Ðрхітектури +composer.dependencies=ЗалежноÑті conan.details.repository=Репозиторій +conan.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +container.details.type=Тип Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +container.details.platform=Платформа +container.pull=Завантажити образ з командного Ñ€Ñдка: +container.images=Образи +container.multi_arch=ОС / Ðрхітектура +container.labels=Мітки +container.labels.key=Ключ container.labels.value=Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ +cran.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>Rprofile.site</code>: +debian.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +debian.registry.info=Оберіть $distribution та $component зі ÑпиÑку нижче. +debian.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище +debian.repository.distributions=ДиÑтрибутиви +debian.repository.components=Компоненти +debian.repository.architectures=Ðрхітектури +helm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +maven.registry=Ðалаштуйте цей реєÑтр у файлі <code>pom.xml</code> вашого проєкту: +maven.install2=Виконати з командного Ñ€Ñдка: +maven.download=Щоб завантажити залежніÑть, виконайте в командному Ñ€Ñдку: +nuget.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +npm.dependencies=ЗалежноÑті +npm.dependencies.optional=ÐеобовʼÑзкові залежноÑті +pypi.requires=Потрібен Python +rpm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +rpm.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище +rpm.repository.architectures=Ðрхітектури +rubygems.install2=або додайте до Gemfile: +rubygems.required.ruby=Вимагає верÑÑ–ÑŽ Ruby +rubygems.required.rubygems=Вимагає верÑÑ–ÑŽ RubyGem +swift.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +vagrant.install=Щоб додати Ð±Ð¾ÐºÑ Vagrant, виконайте наÑтупну команду: +settings.link.select=Обрати Ñховище +settings.link.button=Оновити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñховище +owner.settings.cargo.title=Ð†Ð½Ð´ÐµÐºÑ Ñ€ÐµÑ”Ñтру Cargo +owner.settings.cargo.initialize=Ініціалізувати Ñ–Ð½Ð´ÐµÐºÑ +owner.settings.cargo.initialize.description=Ð”Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ñ€ÐµÑ”Ñтру Cargo потрібне Ñпеціальне Ñховище індекÑів Git. ВикориÑÑ‚Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ параметра дозволить (повторно) Ñтворити та автоматично налаштувати Ñховище. +owner.settings.cargo.initialize.error=Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v +owner.settings.cargo.initialize.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно Ñтворено. +owner.settings.cargo.rebuild=Перебудувати Ñ–Ð½Ð´ÐµÐºÑ +owner.settings.cargo.rebuild.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ±ÑƒÐ´ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v +owner.settings.cargo.rebuild.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно перебудовано. +owner.settings.cleanuprules.add=Додати правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ +owner.settings.cleanuprules.edit=Редагувати правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ +owner.settings.cleanuprules.none=Правила Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ñутні. Будь лаÑка, звернітьÑÑ Ð´Ð¾ документації. +owner.settings.cleanuprules.preview=Попередній переглÑд правила Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ +owner.settings.cleanuprules.preview.none=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð½Ðµ відповідає жодному пакету. owner.settings.cleanuprules.enabled=Увімкнено +owner.settings.cleanuprules.keep.count=Залишити найновіші +owner.settings.cleanuprules.remove.title=ВерÑÑ–Ñ—, Ñкі відповідають цим правилам, видалÑютьÑÑ, Ñкщо правило вище не вимагає Ñ—Ñ… збереженнÑ. +owner.settings.cleanuprules.success.update=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾. +owner.settings.cleanuprules.success.delete=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾. +owner.settings.chef.title=РеєÑтр Chef [secrets] @@ -2714,26 +3068,83 @@ creation.description=ÐžÐ¿Ð¸Ñ [actions] +actions=Дії +unit.desc=Керувати діÑми +status.success=УÑпіх +status.failure=Ðевдача +status.cancelled=СкаÑовано +status.skipped=Пропущено +status.blocked=Заблоковано +runners.status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ runners.name=Ðазва runners.owner_type=Тип runners.description=ÐžÐ¿Ð¸Ñ +runners.labels=Мітки +runners.task_list.no_tasks=Ðаразі завдань немає. runners.task_list.run=ЗапуÑтити +runners.task_list.status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ runners.task_list.repository=Репозиторій runners.task_list.commit=Коміт +runners.task_list.done_at=Завершено о +runners.status.idle=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ runners.status.active=Ðктивний +runners.version=ВерÑÑ–Ñ +runners.reset_registration_token=Скинути реєÑтраційний токен +runs.all_workflows=Ð’ÑÑ– робочі процеÑи runs.commit=Коміт - - - - +runs.pushed_by=завантажено +runs.no_job=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ міÑтити принаймні одну задачу +runs.status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ +runs.status_no_select=Ð’ÑÑ– ÑтатуÑи +runs.no_workflows.documentation=Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації про Gitea Дії, переглÑньте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a>. +runs.empty_commit_message=(порожнє Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ) +runs.view_workflow_file=ПереглÑд файлу робочого процеÑу + +workflow.disable=Вимкнути робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ +workflow.disable_success=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' уÑпішно вимкнено. +workflow.enable=Увімкнути робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ +workflow.enable_success=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' уÑпішно ввімкнено. +workflow.run=ЗапуÑтити робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ +workflow.not_found=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ '%s' не знайдено. +workflow.from_ref=ВикориÑтати робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð· + + +variables=Змінні +variables.management=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¸Ð¼Ð¸ +variables.creation=Додати змінну +variables.none=Ðаразі немає змінних. +variables.deletion=Видалити змінну +variables.deletion.description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ— Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване. Продовжити? +variables.description=Змінні будуть передані певним діÑм Ñ– не можуть бути прочитані інакше. +variables.id_not_exist=Змінної з ідентифікатором %d не Ñ–Ñнує. +variables.edit=Редагувати змінну +variables.deletion.failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ змінну. +variables.deletion.success=Змінну видалено. +variables.creation.failed=Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ змінну. +variables.creation.success=Змінну "%s" додано. +variables.update.failed=Ðе вдалоÑÑ Ð²Ñ–Ð´Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ñ‚Ð¸ змінну. +variables.update.success=Змінну відредаговано. + +logs.always_auto_scroll=Завжди автоматично прокручувати журнали +logs.always_expand_running=Завжди розгортати поточні журнали [projects] +deleted.display_name=Видалений проєкт +type-1.display_name=Індивідуальний проєкт +type-3.display_name=Проєкт організації +enter_fullscreen=Повноекранний режим +exit_fullscreen=Вийти з повноекранного режиму [git.filemode] +changed_filemode=%[1]s → %[2]s ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +directory=Тека +normal_file=Звичайний файл +executable_file=Виконуваний файл symbolic_link=Символічне поÑÐ¸Ð»Ð°Ð½Ð½Ñ +submodule=Підмодуль diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index d35814d82a..1a07c2f4c0 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -4,14 +4,14 @@ explore=探索 help=帮助 logo=å¾½æ ‡ sign_in=登录 -sign_in_with_provider=使用 %s 登录 +sign_in_with_provider=使用「%sã€ç™»å½• sign_in_or=或 sign_out=退出 sign_up=注册 link_account=链接账户 register=注册 -version=当å‰ç‰ˆæœ¬ -powered_by=Powered by %s +version=版本 +powered_by=ç”± %s 强力驱动 page=é¡µé¢ template=æ¨¡æ¿ language=è¯è¨€é€‰é¡¹ @@ -28,7 +28,7 @@ return_to_gitea=返回 Gitea more_items=更多选项 username=用户å -email=电åé‚®ä»¶åœ°å€ +email=é‚®ç®±åœ°å€ password=密ç access_token=访问令牌(Access Token) re_type=确认密ç @@ -42,7 +42,7 @@ webauthn_sign_in=按下安全密钥上的按钮。如果安全密钥没有按钮 webauthn_press_button=请按下安全密钥上的按钮… webauthn_use_twofa=使用æ¥è‡ªæ‰‹æœºä¸çš„两æ¥éªŒè¯ç webauthn_error=æ— æ³•è¯»å–安全密钥。 -webauthn_unsupported_browser=ä½ çš„æµè§ˆå™¨ç›®å‰ä¸æ”¯æŒ WebAuthn。 +webauthn_unsupported_browser=您的æµè§ˆå™¨ç›®å‰ä¸æ”¯æŒ WebAuthn。 webauthn_error_unknown=å‘生未知错误。请é‡è¯•。 webauthn_error_insecure=WebAuthn 仅支æŒå®‰å…¨è¿žæŽ¥ã€‚如果è¦åœ¨ HTTP å议上进行测试,请使用 "localhost" 或 "127.0.0.1" ä½œä¸ºè®¿é—®æ¥æº webauthn_error_unable_to_process=æœåŠ¡å™¨æ— æ³•å¤„ç†æ‚¨çš„请求。 @@ -58,11 +58,11 @@ issue_milestone=里程碑 new_repo=创建仓库 new_migrate=è¿ç§»å¤–部仓库 new_mirror=åˆ›å»ºæ–°çš„é•œåƒ -new_fork=新的仓库Fork +new_fork=派生新仓库 new_org=创建组织 new_project=创建项目 new_project_column=创建列 -manage_org=ç®¡ç†æˆ‘的组织 +manage_org=管ç†ç»„织 admin_panel=管ç†åŽå° account_settings=叿ˆ·è®¾ç½® settings=设置 @@ -78,7 +78,7 @@ forks=派生 activities=最近活动 pull_requests=åˆå¹¶è¯·æ±‚ -issues=å·¥å•ç®¡ç† +issues=å·¥å• milestones=里程碑 ok=确定 @@ -91,7 +91,7 @@ add=æ·»åŠ add_all=æ·»åŠ æ‰€æœ‰ remove=移除 remove_all=移除所有 -remove_label_str=`åˆ é™¤æ ‡ç¾ "%s"` +remove_label_str=åˆ é™¤æ ‡ç¾ã€Œ%s〠edit=编辑 view=查看 test=测试 @@ -129,9 +129,9 @@ rss_feed=RSS è®¢é˜…æº pin=固定 unpin=å–æ¶ˆç½®é¡¶ -artifacts=åˆ¶å“ +artifacts=产物 expired=已过期 -confirm_delete_artifact=您确定è¦åˆ 除制å“'%s'å—? +confirm_delete_artifact=您确定è¦åˆ 除产物「%sã€å—? archived=已归档 @@ -165,7 +165,7 @@ filter.public=公开 filter.private=ç§æœ‰ no_results_found=未找到结果 -internal_error_skipped=å‘生内部错误,但已被跳过: %s +internal_error_skipped=å‘生内部错误,但已跳过: %s [search] search=æœç´¢... @@ -184,14 +184,14 @@ org_kind=æœç´¢ç»„织... team_kind=æœç´¢å›¢é˜Ÿ... code_kind=æœç´¢ä»£ç ... code_search_unavailable=ä»£ç æœç´¢å½“å‰ä¸å¯ç”¨ã€‚请与网站管ç†å‘˜è”系。 -code_search_by_git_grep=当å‰ä»£ç æœç´¢ç»“果由“git grepâ€æä¾›ã€‚å¦‚æžœç«™ç‚¹ç®¡ç†å‘˜å¯ç”¨ä»“库索引器,å¯èƒ½ä¼šæœ‰æ›´å¥½çš„结果。 +code_search_by_git_grep=当å‰ä»£ç æœç´¢ç»“果由「git grepã€æä¾›ã€‚å¦‚æžœç«™ç‚¹ç®¡ç†å‘˜å¯ç”¨ä»“库索引器,å¯èƒ½ä¼šæœ‰æ›´å¥½çš„结果。 package_kind=æœç´¢è½¯ä»¶åŒ…... project_kind=æœç´¢é¡¹ç›®... branch_kind=æœç´¢åˆ†æ”¯... tag_kind=æœç´¢æ ‡ç¾... -tag_tooltip=æœç´¢åŒ¹é…çš„æ ‡ç¾ã€‚使用“%â€æ¥åŒ¹é…任何åºåˆ—çš„æ•°å— +tag_tooltip=æœç´¢åŒ¹é…çš„æ ‡ç¾ã€‚使用「%ã€æ¥åŒ¹é…任何åºåˆ—的数å—。 commit_kind=æœç´¢æäº¤è®°å½•... -runner_kind=æœç´¢runners... +runner_kind=æœç´¢è¿è¡Œå™¨... no_results=未找到匹é…结果 issue_kind=æœç´¢å·¥å•... pull_kind=æœç´¢åˆå¹¶è¯·æ±‚... @@ -268,14 +268,14 @@ ssl_mode=SSL path=æ•°æ®åº“文件路径 sqlite_helper=SQLite3 æ•°æ®åº“的文件路径。<br>如果以æœåŠ¡çš„æ–¹å¼è¿è¡Œ Gitea,请输入ç»å¯¹è·¯å¾„。 reinstall_error=您æ£åœ¨å°è¯•å®‰è£…åˆ°ä¸€ä¸ªå·²ç»æœ‰ Gitea æ•°æ®çš„æ•°æ®åº“ä¸ -reinstall_confirm_message=使用现有的 Gitea æ•°æ®åº“釿–°å®‰è£…å¯èƒ½ä¼šå¯¼è‡´å¤šä¸ªé—®é¢˜ã€‚åœ¨å¤§å¤šæ•°æƒ…å†µä¸‹ï¼Œä½ åº”è¯¥ä½¿ç”¨ä½ çŽ°æœ‰çš„ “app.ini†æ¥è¿è¡Œ Giteaã€‚å¦‚æžœä½ çŸ¥é“自己在åšä»€ä¹ˆï¼Œè¯·ç¡®è®¤ä»¥ä¸‹å†…容: +reinstall_confirm_message=使用现有的 Gitea æ•°æ®åº“釿–°å®‰è£…å¯èƒ½ä¼šå¯¼è‡´å¤šä¸ªé—®é¢˜ã€‚在大多数情况下,您应该使用您现有的「app.iniã€æ¥è¿è¡Œ Gitea。如果您知é“自己在åšä»€ä¹ˆï¼Œè¯·ç¡®è®¤ä»¥ä¸‹å†…容: reinstall_confirm_check_1=使用 app.ini ä¸ SECRET KEY åŠ å¯†çš„æ•°æ®å¯èƒ½ä¼šä¸¢å¤±ï¼šç”¨æˆ·å¯èƒ½æ— 法使用 2FA/OTP 登录,仓库镜åƒå¯èƒ½æ— 法æ£å¸¸å·¥ä½œã€‚å‹¾é€‰æ¤æ¡†ï¼Œè¡¨ç¤ºæ‚¨ç¡®è®¤å½“å‰ app.ini æ–‡ä»¶åŒ…å«æ£ç¡®çš„ SECRET KEY。 reinstall_confirm_check_2=代ç 仓库和设置å¯èƒ½éœ€è¦é‡æ–°åŒæ¥ã€‚å‹¾é€‰æ¤æ¡†ï¼Œè¡¨ç¤ºæ‚¨ç¡®è®¤å°†æ‰‹åЍ釿–°åŒæ¥ä»“库和 SSH authorized_keys 的钩å。您确认您将确ä¿ä»£ç 仓库和镜åƒè®¾ç½®æ˜¯æ£ç¡®çš„。 -reinstall_confirm_check_3=ä½ ç¡®è®¤ä½ ç»å¯¹è‚¯å®šè¿™ä¸ª Gitea 在æ£ç¡®çš„ app.ini ä½ç½®ä¸Šè¿è¡Œï¼Œè€Œä¸”ä½ ç¡®å®šä½ å¿…é¡»é‡æ–°å®‰è£…ã€‚ä½ ç¡®è®¤ä½ çŸ¥æ™“ä¸Šè¿°é£Žé™©ã€‚ +reinstall_confirm_check_3=您确认您ç»å¯¹è‚¯å®šè¿™ä¸ª Gitea 在æ£ç¡®çš„ app.ini ä½ç½®ä¸Šè¿è¡Œï¼Œè€Œä¸”æ‚¨ç¡®å®šæ‚¨å¿…é¡»é‡æ–°å®‰è£…。您确认您知晓上述风险。 err_empty_db_path=SQLite æ•°æ®åº“文件路径ä¸èƒ½ä¸ºç©ºã€‚ no_admin_and_disable_registration=您ä¸èƒ½å¤Ÿåœ¨æœªåˆ›å»ºç®¡ç†å‘˜ç”¨æˆ·çš„æƒ…å†µä¸‹ç¦æ¢æ³¨å†Œã€‚ err_empty_admin_password=管ç†å‘˜å¯†ç ä¸èƒ½ä¸ºç©ºã€‚ -err_empty_admin_email=管ç†å‘˜ç”µå邮件ä¸èƒ½ä¸ºç©ºã€‚ +err_empty_admin_email=管ç†å‘˜é‚®ç®±ä¸èƒ½ä¸ºç©ºã€‚ err_admin_name_is_reserved=管ç†å‘˜ç”¨æˆ·åæ— æ•ˆï¼Œç”¨æˆ·åæ˜¯ä¿ç•™çš„ err_admin_name_pattern_not_allowed=管ç†å‘˜ç”¨æˆ·åæ— æ•ˆï¼Œç”¨æˆ·åæ˜¯ä¿ç•™å— err_admin_name_is_invalid=管ç†å‘˜ç”¨æˆ·åæ— æ•ˆ @@ -296,7 +296,7 @@ ssh_port_helper=SSH æœåŠ¡å™¨çš„ç«¯å£å·ï¼Œä¸ºç©ºåˆ™ç¦ç”¨å®ƒã€‚ http_port=HTTP æœåŠ¡ç«¯å£ http_port_helper=Giteas web æœåС噍将侦å¬çš„端å£å·ã€‚ app_url=基础URL -app_url_helper=用于 HTTP (S) 克隆和电å邮件通知的基本地å€ã€‚ +app_url_helper=用于 HTTP (S) 克隆和邮件通知的基本地å€ã€‚ log_root_path=日志路径 log_root_path_helper=日志文件将写入æ¤ç›®å½•。 @@ -304,12 +304,12 @@ optional_title=å¯é€‰è®¾ç½® email_title=电å邮箱设置 smtp_addr=SMTP ä¸»æœºåœ°å€ smtp_port=SMTP ç«¯å£ -smtp_from=电å邮件å‘件人 -smtp_from_invalid=`"å‘é€ç”µå邮件为"åœ°å€æ— 效` -smtp_from_helper=请输入一个用于 Gitea 的电å邮件地å€ï¼Œæˆ–è€…ä½¿ç”¨å®Œæ•´æ ¼å¼ï¼š"åç§°" <email@example.com> +smtp_from=邮件å‘件人 +smtp_from_invalid=「邮件å‘件人ã€åœ°å€æ— 效 +smtp_from_helper=请输入一个用于 Gitea 的邮箱地å€ï¼Œæˆ–è€…ä½¿ç”¨å®Œæ•´æ ¼å¼ï¼šã€Œåç§°ã€<email@example.com>。 mailer_user=SMTP 用户å mailer_password=SMTP 密ç -register_confirm=需è¦å‘电å邮件确认注册 +register_confirm=需è¦é‚®ä»¶ç¡®è®¤æ³¨å†Œ mail_notify=å¯ç”¨é‚®ä»¶é€šçŸ¥æé†’ server_service_title=æœåŠ¡å™¨å’Œç¬¬ä¸‰æ–¹æœåŠ¡è®¾ç½® offline_mode=å¯ç”¨æœ¬åœ°æ¨¡å¼ @@ -334,12 +334,12 @@ admin_title=管ç†å‘˜å¸å·è®¾ç½® admin_name=管ç†å‘˜ç”¨æˆ·å admin_password=管ç†å‘˜å¯†ç confirm_password=确认密ç -admin_email=电åé‚®ä»¶åœ°å€ +admin_email=é‚®ç®±åœ°å€ install_btn_confirm=ç«‹å³å®‰è£… test_git_failed=æ— æ³•è¯†åˆ« 'git' 命令:%v sqlite3_not_available=您所使用的å‘è¡Œç‰ˆä¸æ”¯æŒ SQLite3,请从 %s ä¸‹è½½å®˜æ–¹æž„å»ºç‰ˆï¼Œè€Œä¸æ˜¯ gobuild 版本。 invalid_db_setting=æ•°æ®åº“è®¾ç½®æ— æ•ˆ: %v -invalid_db_table=æ•°æ®åº“表 '%s' æ— æ•ˆï¼š %v +invalid_db_table=æ•°æ®åº“表「%sã€æ— 效:%v invalid_repo_path=ä»“åº“æ ¹ç›®å½•è®¾ç½®æ— æ•ˆï¼š%v invalid_app_data_path=应用数æ®è·¯å¾„æ— æ•ˆï¼š %v run_user_not_match=è¿è¡Œç”¨æˆ·å䏿˜¯å½“å‰çš„用户å:%s -> %s @@ -348,14 +348,14 @@ secret_key_failed=生æˆå¯†é’¥å¤±è´¥ï¼š %v save_config_failed=应用é…ç½®ä¿å˜å¤±è´¥ï¼š%v invalid_admin_setting=管ç†å‘˜å¸æˆ·è®¾ç½®æ— 效: %v invalid_log_root_path=æ—¥å¿—è·¯å¾„æ— æ•ˆ: %v -default_keep_email_private=默认情况下éšè—电åé‚®ä»¶åœ°å€ -default_keep_email_private_popup=默认情况下, éšè—æ–°ç”¨æˆ·å¸æˆ·çš„电å邮件地å€ã€‚ +default_keep_email_private=默认情况下éšè—é‚®ç®±åœ°å€ +default_keep_email_private_popup=默认情况下,éšè—æ–°ç”¨æˆ·å¸æˆ·çš„邮箱地å€ã€‚ default_allow_create_organization=默认情况下å…许创建组织 default_allow_create_organization_popup=默认情况下, å…è®¸æ–°ç”¨æˆ·å¸æˆ·åˆ›å»ºç»„织。 default_enable_timetracking=默认情况下å¯ç”¨æ—¶é—´è·Ÿè¸ª default_enable_timetracking_popup=默认情况下å¯ç”¨æ–°ä»“库的时间跟踪。 -no_reply_address=éšè—电å邮件 -no_reply_address_helper=具有éšè—电å邮件地å€çš„用户的域å。例如, 用户å "joe" 将以 "joe@noreply.example.org" 的身份登录到 Git ä¸. 如果éšè—的电å邮件域设置为 "noreply.example.org"。 +no_reply_address=éšè—邮件域 +no_reply_address_helper=具有éšè—邮箱地å€çš„用户的域å。例如,如果éšè—邮箱域å设置为「noreply.example.orgã€ï¼Œé‚£ä¹ˆç”¨æˆ·å「joeã€åœ¨ Git ä¸å°†æ˜¾ç¤ºä¸ºã€Œjoe@noreply.example.orgã€ã€‚ password_algorithm=密ç 哈希算法 invalid_password_algorithm=æ— æ•ˆçš„å¯†ç 哈希算法 password_algorithm_helper=è®¾ç½®å¯†ç æ•£åˆ—算法。算法有ä¸åŒçš„è¦æ±‚和强度。 argon2 算法相当安全,但使用大é‡å†…å˜ï¼Œå› æ¤å¯èƒ½ä¸é€‚åˆå°åž‹ç³»ç»Ÿã€‚ @@ -378,7 +378,7 @@ my_mirrors=æˆ‘çš„é•œåƒ view_home=访问 %s filter=其他过滤器 filter_by_team_repositories=按团队仓库ç›é€‰ -feed_of=`"%s"çš„æº` +feed_of=「%sã€çš„æº show_archived=已归档 show_both_archived_unarchived=显示已归档和未归档的 @@ -414,7 +414,7 @@ create_new_account=注册å¸å· already_have_account=已有账å·ï¼Ÿ sign_in_now=ç«‹å³ç™»å½• disable_register_prompt=对ä¸èµ·ï¼Œæ³¨å†ŒåŠŸèƒ½å·²è¢«å…³é—。请è”系网站管ç†å‘˜ã€‚ -disable_register_mail=å·²ç¦ç”¨æ³¨å†Œçš„电å邮件确认。 +disable_register_mail=å·²ç¦ç”¨æ³¨å†Œé‚®ä»¶ç¡®è®¤ã€‚ manual_activation_only=请è”系您的站点管ç†å‘˜æ¥å®Œæˆæ¿€æ´»ã€‚ remember_me=è®°ä½æ¤è®¾å¤‡ remember_me.compromised=登录令牌ä¸å†æœ‰æ•ˆï¼Œå› 为它å¯èƒ½è¡¨æ˜Žå¸æˆ·å·²è¢«ç ´åã€‚è¯·æ£€æŸ¥æ‚¨çš„å¸æˆ·æ˜¯å¦æœ‰å¼‚常活动。 @@ -423,34 +423,34 @@ forgot_password=忘记密ç ? need_account=需è¦ä¸€ä¸ªå¸æˆ·? sign_up_now=还没账å·ï¼Ÿé©¬ä¸Šæ³¨å†Œã€‚ sign_up_successful=叿ˆ·åˆ›å»ºæˆåŠŸã€‚æ¬¢è¿Žï¼ -confirmation_mail_sent_prompt_ex=䏀尿–°çš„确认邮件已ç»å‘é€åˆ° <b>%s</b>请在下一个 %s 䏿£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±ä»¥å®Œæˆæ³¨å†Œè¿‡ç¨‹ã€‚ 如果您的注册电å邮件地å€ä¸æ£ç¡®ï¼Œæ‚¨å¯ä»¥é‡æ–°ç™»å½•并更改它。 +confirmation_mail_sent_prompt_ex=䏀尿–°çš„确认邮件已ç»å‘é€åˆ° <b>%s</b>。请在下一个 %s 䏿£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±ä»¥å®Œæˆæ³¨å†Œæµç¨‹ã€‚ 如果您的注册邮箱地å€ä¸æ£ç¡®ï¼Œæ‚¨å¯ä»¥é‡æ–°ç™»å½•并更改它。 must_change_password=更新您的密ç allow_password_change=è¦æ±‚用户更改密ç (推è) -reset_password_mail_sent_prompt=确认电å邮件已被å‘é€åˆ° <b>%s</b>。请您在 %s 内检查您的收件箱 ,完æˆå¯†ç é‡ç½®è¿‡ç¨‹ã€‚ +reset_password_mail_sent_prompt=确认邮件已被å‘é€åˆ° <b>%s</b>。请您在 %s 内检查您的收件箱 ,完æˆå¯†ç é‡ç½®æµç¨‹ã€‚ active_your_account=æ¿€æ´»æ‚¨çš„å¸æˆ· account_activated=叿ˆ·å·²æ¿€æ´» prohibit_login=ç¦æ¢ç™»å½• prohibit_login_desc=æ‚¨çš„å¸æˆ·è¢«ç¦æ¢ç™»å½•,请与网站管ç†å‘˜è”系。 resent_limit_prompt=您请求å‘逿¿€æ´»é‚®ä»¶è¿‡äºŽé¢‘ç¹ï¼Œè¯·ç‰å¾… 3 分钟åŽå†è¯•ï¼ has_unconfirmed_mail=%s 您好,系统检测到您有一å°å‘é€è‡³ <b>%s</b> 但未被确认的邮件。如果您未收到激活邮件,或需è¦é‡æ–°å‘é€ï¼Œè¯·å•击下方的按钮。 -change_unconfirmed_mail_address=如果您的注册电å邮件地å€ä¸æ£ç¡®ï¼Œæ‚¨å¯ä»¥åœ¨æ¤æ›´æ”¹å¹¶é‡æ–°å‘逿–°çš„确认电å邮件。 +change_unconfirmed_mail_address=如果您的注册邮箱地å€ä¸æ£ç¡®ï¼Œæ‚¨å¯ä»¥åœ¨æ¤æ›´æ”¹å¹¶é‡æ–°å‘逿–°çš„确认邮件。 resend_mail=å•击æ¤å¤„釿–°å‘é€ç¡®è®¤é‚®ä»¶ email_not_associate=æ‚¨è¾“å…¥çš„é‚®ç®±åœ°å€æœªè¢«å…³è”到任何å¸å·ï¼ send_reset_mail=å‘é€è´¦æˆ·æ¢å¤é‚®ä»¶ reset_password=账户æ¢å¤ invalid_code=æ¤ç¡®è®¤å¯†é’¥æ— 效或已过期。 -invalid_code_forgot_password=ä½ çš„ç¡®è®¤ç æ— 效或者已过期,点击 <a href="%s">这里</a> 开始新的会è¯ã€‚ +invalid_code_forgot_password=æ‚¨çš„ç¡®è®¤ç æ— 效或已过期,点击 <a href="%s">这里</a> 开始新的会è¯ã€‚ invalid_password=您的密ç 与用于创建账户的密ç ä¸åŒ¹é…。 reset_password_helper=æ¢å¤è´¦æˆ· reset_password_wrong_user=您以 %s 登录,但æ¢å¤è´¦å·é“¾æŽ¥æ˜¯ç”¨äºŽ %s。 password_too_short=密ç 长度ä¸èƒ½å°‘于 %d ä½ã€‚ -non_local_account=éžæœ¬åœ°å¸æˆ·ä¸èƒ½é€šè¿‡ Gitea çš„ web ç•Œé¢æ›´æ”¹å¯†ç 。 +non_local_account=éžæœ¬åœ°å¸æˆ·ä¸èƒ½é€šè¿‡ Gitea çš„ Web ç•Œé¢æ›´æ”¹å¯†ç 。 verify=éªŒè¯ scratch_code=验è¯å£ä»¤ use_scratch_code=使用验è¯å£ä»¤ -twofa_scratch_used=ä½ å·²ç»ä½¿ç”¨äº†ä½ 的验è¯å£ä»¤ã€‚ä½ å°†ä¼šè½¬åˆ°ä¸¤æ¥éªŒè¯è®¾ç½®é¡µé¢ä»¥ä¾¿ç§»é™¤ä½ çš„æ³¨å†Œè®¾å¤‡æˆ–è€…é‡æ–°ç”Ÿæˆæ–°çš„验è¯å£ä»¤ã€‚ -twofa_passcode_incorrect=ä½ çš„éªŒè¯ç 䏿£ç¡®ã€‚å¦‚æžœä½ ä¸¢å¤±äº†ä½ çš„è®¾å¤‡ï¼Œè¯·ä½¿ç”¨ä½ çš„éªŒè¯å£ä»¤ã€‚ -twofa_scratch_token_incorrect=ä½ çš„éªŒè¯å£ä»¤ä¸æ£ç¡®ã€‚ +twofa_scratch_used=您已ç»ä½¿ç”¨äº†æ‚¨çš„验è¯å£ä»¤ã€‚您将会转到两æ¥éªŒè¯è®¾ç½®é¡µé¢ä»¥ä¾¿ç§»é™¤æ‚¨çš„æ³¨å†Œè®¾å¤‡æˆ–è€…é‡æ–°ç”Ÿæˆæ–°çš„验è¯å£ä»¤ã€‚ +twofa_passcode_incorrect=您的验è¯ç 䏿£ç¡®ã€‚如果您丢失了您的设备,请使用您的验è¯å£ä»¤ã€‚ +twofa_scratch_token_incorrect=您的验è¯å£ä»¤ä¸æ£ç¡®ã€‚ twofa_required=您必须设置两æ¥éªŒè¯æ¥è®¿é—®ä»“库,或者å°è¯•釿–°ç™»å½•。 login_userpass=登录 login_openid=OpenID @@ -470,14 +470,14 @@ openid_connect_desc=所选的 OpenID URI 未知。在这里关è”ä¸€ä¸ªæ–°å¸æˆ· openid_register_title=åˆ›å»ºæ–°å¸æˆ· openid_register_desc=所选的 OpenID URI 未知。在这里关è”ä¸€ä¸ªæ–°å¸æˆ·ã€‚ openid_signin_desc=输入您的OpenID地å€ã€‚例如:alice.openid.example.org 或 https://openid.example.org/alice. -disable_forgot_password_mail=由于未设置电åé‚®ä»¶ï¼Œå¸æˆ·æ¢å¤è¢«ç¦ç”¨ã€‚ 请è”系您的站点管ç†å‘˜ã€‚ -disable_forgot_password_mail_admin=叿ˆ·æ¢å¤ä»…在设置电å邮件åŽå¯ç”¨ã€‚ 请设置电å邮件以å¯ç”¨å¸æˆ·æ¢å¤ã€‚ -email_domain_blacklisted=您ä¸èƒ½ä½¿ç”¨æ‚¨çš„电åé‚®ä»¶åœ°å€æ³¨å†Œã€‚ +disable_forgot_password_mail=ç”±äºŽæœªè®¾ç½®é‚®ç®±ï¼Œå¸æˆ·æ¢å¤è¢«ç¦ç”¨ã€‚ 请è”系您的站点管ç†å‘˜ã€‚ +disable_forgot_password_mail_admin=叿ˆ·æ¢å¤ä»…在设置邮箱åŽå¯ç”¨ã€‚ 请设置邮箱以å¯ç”¨å¸æˆ·æ¢å¤ã€‚ +email_domain_blacklisted=您ä¸èƒ½ä½¿ç”¨æ‚¨çš„é‚®ç®±åœ°å€æ³¨å†Œã€‚ authorize_application=åº”ç”¨æŽˆæƒ authorize_redirect_notice=å¦‚æžœæ‚¨æŽˆæƒæ¤åº”用,您将会被é‡å®šå‘到 %s。 -authorize_application_created_by=æ¤åº”用由%s创建。 +authorize_application_created_by=æ¤åº”用由 %s 创建。 authorize_application_description=如果您å…许,它将能够读å–å’Œä¿®æ”¹æ‚¨çš„æ‰€æœ‰å¸æˆ·ä¿¡æ¯ï¼ŒåŒ…括ç§äººä»“库和组织。 -authorize_application_with_scopes=范围: %s +authorize_application_with_scopes=范围:%s authorize_title=æŽˆæƒ %s è®¿é—®æ‚¨çš„å¸æˆ·ï¼Ÿ authorization_failed=授æƒå¤±è´¥ authorization_failed_desc=å› ä¸ºæ£€æµ‹åˆ°æ— æ•ˆè¯·æ±‚ï¼ŒæŽˆæƒå¤±è´¥ã€‚请å°è¯•è”系您授æƒåº”用的管ç†å‘˜ã€‚ @@ -501,17 +501,17 @@ activate_account.text_2=请在 <b>%s</b> 时间内,点击以下链接激活您 activate_email=è¯·éªŒè¯æ‚¨çš„é‚®ç®±åœ°å€ activate_email.title=%sï¼Œè¯·éªŒè¯æ‚¨çš„邮箱 -activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验è¯ä½ 的电å邮件地å€ï¼š +activate_email.text=请在 <b>%s</b> æ—¶é—´å†…ï¼Œç‚¹å‡»ä»¥ä¸‹é“¾æŽ¥ï¼Œä»¥éªŒè¯æ‚¨çš„邮箱地å€ï¼š register_notify=欢迎æ¥åˆ° %s register_notify.title=%[1]s,欢迎æ¥åˆ° %[2]s -register_notify.text_1=这是您的 %s 注册确认电å邮件 ï¼ +register_notify.text_1=这是您的 %s 注册确认邮件 ï¼ register_notify.text_2=您现在å¯ä»¥ä»¥ç”¨æˆ·å %s 登录。 register_notify.text_3=如果æ¤è´¦æˆ·å·²ä¸ºæ‚¨åˆ›å»ºï¼Œè¯·å…ˆ <a href="%s">设置您的密ç </a>。 reset_password=æ¢å¤æ‚¨çš„账户 reset_password.title=%s,您已请求æ¢å¤æ‚¨çš„叿ˆ· -reset_password.text=请在 <b>%s</b> 时间内,点击以下链接,æ¢å¤ä½ 的账户: +reset_password.text=请在 <b>%s</b> 时间内,点击以下链接,以æ¢å¤æ‚¨çš„账户: register_success=注册æˆåŠŸ @@ -533,26 +533,26 @@ issue.action.ready_for_review=<b>@%[1]s</b> æ ‡è®°æ¤åˆå¹¶è¯·æ±‚已评审通过 issue.action.new=<b>@%[1]s</b> 创建了 #%[2]d. issue.in_tree_path=在 %s ä¸ï¼š -release.new.subject=%[2]s ä¸çš„ %[1]s å‘布了 +release.new.subject=%[2]s ä¸çš„ %[1]s å·²å‘布 release.new.text=<b>@%[1]s</b> 于 %[3]s å‘布了 %[2]s -release.title=æ ‡é¢˜ï¼š %s +release.title=æ ‡é¢˜ï¼š%s release.note=注释: release.downloads=下载: -release.download.zip=æºä»£ç (ZIP) -release.download.targz=æºä»£ç (TAR.GZ) +release.download.zip=æºä»£ç (ZIP) +release.download.targz=æºä»£ç (TAR.GZ) -repo.transfer.subject_to=%s 想è¦å°† "%s" 转让给 %s -repo.transfer.subject_to_you=%s 想è¦å°† "%s" è½¬è®©ç»™ä½ -repo.transfer.to_you=ä½ +repo.transfer.subject_to=%s 想è¦å°†ã€Œ%sã€è½¬ç§»ç»™ %s +repo.transfer.subject_to_you=%s 想è¦å°†ã€Œ%sã€è½¬ç§»ç»™æ‚¨ +repo.transfer.to_you=您 repo.transfer.body=访问 %s ä»¥æŽ¥å—æˆ–æ‹’ç»è½¬ç§»ï¼Œäº¦å¯å¿½ç•¥æ¤é‚®ä»¶ã€‚ -repo.collaborator.added.subject=%s æŠŠä½ æ·»åŠ åˆ°äº† %s -repo.collaborator.added.text=æ‚¨å·²è¢«æ·»åŠ ä¸ºä»£ç 库的å作者: +repo.collaborator.added.subject=%s æŠŠæ‚¨æ·»åŠ åˆ°äº† %s +repo.collaborator.added.text=æ‚¨å·²è¢«æ·»åŠ ä¸ºä»“åº“çš„å作者: team_invite.subject=%[1]s é‚€è¯·æ‚¨åŠ å…¥ç»„ç»‡ %[2]s team_invite.text_1=%[1]s é‚€è¯·æ‚¨åŠ å…¥ç»„ç»‡ %[3]s ä¸çš„团队 %[2]s。 team_invite.text_2=请点击下é¢çš„é“¾æŽ¥åŠ å…¥å›¢é˜Ÿï¼š -team_invite.text_3=注æ„:这是å‘é€ç»™ %[1]s 的邀请。如果您未曾收到过æ¤ç±»é‚€è¯·ï¼Œè¯·å¿½ç•¥è¿™å°ç”µå邮件。 +team_invite.text_3=注æ„:æ¤é‚€è¯·æ˜¯å‘é€ç»™ %[1]s 的。如果您未预期收到æ¤é‚€è¯·ï¼Œè¯·å¿½ç•¥è¿™å°é‚®ä»¶ã€‚ [modal] yes=确认æ“作 @@ -592,9 +592,9 @@ size_error=长度必须为 %s。 min_size_error=长度最å°ä¸º %s 个å—符。 max_size_error=长度最大为 %s 个å—符。 email_error=䏿˜¯ä¸€ä¸ªæœ‰æ•ˆçš„邮箱地å€ã€‚ -url_error=`'%s' 䏿˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ URL。` -include_error=`必须包å«åå—符串 "%s"。` -glob_pattern_error=`åŒ¹é…æ¨¡å¼æ— 效:%s.` +url_error=`「%sã€ä¸æ˜¯ä¸€ä¸ªæœ‰æ•ˆçš„ URL。` +include_error=`必须包å«åå—符串「%sã€ã€‚` +glob_pattern_error=`匹é…è¡¨è¾¾å¼æ— 效:%s.` regex_pattern_error=`æ£åˆ™è¡¨è¾¾å¼æ— 效:%s.` username_error=` åªèƒ½åŒ…å«å—æ¯æ•°å—å—符('0-9','a-z','A-Z'), ç ´æŠ˜å· ('-'), 下划线 ('_') 和点 ('.'). ä¸èƒ½ä»¥éžå—æ¯æ•°å—å—符开头或结尾,并且ä¸å…许连ç»çš„éžå—æ¯æ•°å—å—符。` invalid_group_team_map_error=`æ˜ å°„æ— æ•ˆï¼š %s` @@ -603,26 +603,26 @@ captcha_incorrect=验è¯ç 䏿£ç¡®ã€‚ password_not_match=密ç ä¸åŒ¹é…。 lang_select_error=从列表ä¸é€‰å‡ºè¯è¨€ -username_been_taken=用户å已被使用。 +username_been_taken=用户å已使用。 username_change_not_local_user=éžæœ¬åœ°ç”¨æˆ·ä¸å…许更改用户å。 -change_username_disabled=更改用户å已被ç¦ç”¨ã€‚ +change_username_disabled=更改用户åå·²ç¦ç”¨ã€‚ change_full_name_disabled=更改用户全åå·²ç¦ç”¨ username_has_not_been_changed=ç”¨æˆ·åæœªæ›´æ”¹ -repo_name_been_taken=仓库å称已被使用。 -repository_force_private=â€œå¼ºåˆ¶ç§æœ‰â€å·²å¯ç”¨ï¼šç§æœ‰ä»“库ä¸èƒ½è¢«å…¬å¼€ã€‚ +repo_name_been_taken=仓库å称已使用。 +repository_force_private=ã€Œå¼ºåˆ¶ç§æœ‰ã€å·²å¯ç”¨ï¼šç§æœ‰ä»“库ä¸èƒ½è¢«å…¬å¼€ã€‚ repository_files_already_exist=æ¤ä»“库已å˜åœ¨æ–‡ä»¶ã€‚请è”系系统管ç†å‘˜ã€‚ repository_files_already_exist.adopt=æ¤ä»“库已å˜åœ¨æ–‡ä»¶ï¼Œåªèƒ½è¢«æ”¶å½•。 repository_files_already_exist.delete=æ¤ä»“库已å˜åœ¨æ–‡ä»¶ï¼Œå¿…é¡»å…ˆåˆ é™¤ä»–ä»¬ã€‚ repository_files_already_exist.adopt_or_delete=æ¤ä»“库已å˜åœ¨æ–‡ä»¶ï¼Œè¦ä¹ˆåˆ 除他们,è¦ä¹ˆæ”¶å½•他们。 visit_rate_limit=远程访问达到速度é™åˆ¶ã€‚ 2fa_auth_required=远程访问需è¦åŒé‡éªŒè¯ã€‚ -org_name_been_taken=组织å称已被使用。 -team_name_been_taken=团队å称已被使用。 +org_name_been_taken=组织å称已使用。 +team_name_been_taken=团队å称已使用。 team_no_units_error=至少选择一项仓库å•元。 -email_been_used=该电å邮件地å€å·²åœ¨ä½¿ç”¨ä¸ã€‚ +email_been_used=该邮箱地å€å·²åœ¨ä½¿ç”¨ä¸ã€‚ email_invalid=æ¤é‚®ç®±åœ°å€æ— 效。 email_domain_is_not_allowed=用户 <b>%s</b> 与EMAIL_DOMAIN_ALLOWLIT 或 EMAIL_DOMAIN_BLOCKLIT 冲çªã€‚è¯·ç¡®ä¿æ‚¨çš„æ“ä½œæ˜¯é¢„æœŸçš„ã€‚ -openid_been_used=OpenID åœ°å€ "%s" 已被使用。 +openid_been_used=OpenID 地å€ã€Œ%sã€å·²è¢«ä½¿ç”¨ã€‚ username_password_incorrect=ç”¨æˆ·åæˆ–密ç 䏿£ç¡®ã€‚ password_complexity=å¯†ç æœªè¾¾åˆ°å¤æ‚ç¨‹åº¦è¦æ±‚: password_lowercase_one=至少一个å°å†™å—符 @@ -637,14 +637,14 @@ unset_password=登录用户没有设置密ç 。 unsupported_login_type=æ¤ç™»å½•ç±»åž‹ä¸æ”¯æŒæ‰‹åŠ¨åˆ é™¤å¸æˆ·ã€‚ user_not_exist=该用户ä¸å˜åœ¨ team_not_exist=团队ä¸å˜åœ¨ -last_org_owner=您ä¸èƒ½ä»Ž "所有者" 团队ä¸åˆ 除最åŽä¸€ä¸ªç”¨æˆ·ã€‚组织ä¸å¿…须至少有一个所有者。 -cannot_add_org_to_team=组织ä¸èƒ½è¢«åŠ å…¥åˆ°å›¢é˜Ÿä¸ã€‚ +last_org_owner=您ä¸èƒ½ä»Žã€Œæ‰€æœ‰è€…ã€å›¢é˜Ÿä¸åˆ 除最åŽä¸€ä¸ªç”¨æˆ·ã€‚组织ä¸å¿…须至少有一个所有者。 +cannot_add_org_to_team=组织ä¸èƒ½åŠ å…¥åˆ°å›¢é˜Ÿä¸ã€‚ duplicate_invite_to_team=æ¤ç”¨æˆ·å·²è¢«é‚€è¯·ä¸ºå›¢é˜Ÿæˆå‘˜ã€‚ organization_leave_success=您已æˆåŠŸç¦»å¼€ç»„ç»‡ %s。 -invalid_ssh_key=æ— æ³•éªŒè¯æ‚¨çš„ SSH 密钥: %s -invalid_gpg_key=æ— æ³•éªŒè¯æ‚¨çš„ GPG 密钥: %s -invalid_ssh_principal=æ— æ•ˆçš„è§„åˆ™ï¼š %s +invalid_ssh_key=æ— æ³•éªŒè¯æ‚¨çš„ SSH 密钥:%s +invalid_gpg_key=æ— æ³•éªŒè¯æ‚¨çš„ GPG 密钥:%s +invalid_ssh_principal=æ— æ•ˆçš„è§„åˆ™ï¼š%s must_use_public_key=您æä¾›çš„密钥是ç§é’¥ã€‚ä¸è¦åœ¨ä»»ä½•åœ°æ–¹ä¸Šä¼ æ‚¨çš„ç§é’¥ï¼Œè¯·æ”¹ç”¨æ‚¨çš„公钥。 unable_verify_ssh_key=æ— æ³•éªŒè¯ SSH å¯†é’¥ï¼Œè¯·ä»”ç»†æ£€æŸ¥æ˜¯å¦æœ‰é”™è¯¯ã€‚ auth_failed=授æƒéªŒè¯å¤±è´¥ï¼š%v @@ -677,27 +677,27 @@ follow=关注 unfollow=å–æ¶ˆå…³æ³¨ user_bio=简历 disabled_public_activity=该用户已éšè—活动记录。 -email_visibility.limited=所有已认è¯ç”¨æˆ·å‡å¯çœ‹åˆ°æ‚¨çš„电åé‚®ä»¶åœ°å€ -email_visibility.private=åªæœ‰ä½ 本人和管ç†å‘˜å¯ä»¥çœ‹åˆ°ä½ 的电åé‚®ä»¶åœ°å€ +email_visibility.limited=所有已认è¯ç”¨æˆ·å‡å¯çœ‹åˆ°æ‚¨çš„é‚®ç®±åœ°å€ +email_visibility.private=åªæœ‰æ‚¨æœ¬äººå’Œç®¡ç†å‘˜å¯ä»¥çœ‹åˆ°æ‚¨çš„é‚®ç®±åœ°å€ show_on_map=在地图上显示这个ä½ç½® settings=用户设置 -form.name_reserved=用户å "%s" 被ä¿ç•™ã€‚ -form.name_pattern_not_allowed=用户åä¸ä¸å…许使用 "%s" æ ¼å¼ã€‚ -form.name_chars_not_allowed=用户å "%s" åŒ…å«æ— 效å—符。 +form.name_reserved=用户å「%sã€è¢«ä¿ç•™ã€‚ +form.name_pattern_not_allowed=用户åä¸ä¸å…许使用「%sã€æ ¼å¼ã€‚ +form.name_chars_not_allowed=用户å「%sã€åŒ…嫿— 效å—符。 block.block=å±è”½ block.block.user=å±è”½ç”¨æˆ· block.block.org=å±è”½ç”¨æˆ·è®¿é—®ç»„织 -block.block.failure=å±è”½ç”¨æˆ·å¤±è´¥: %s +block.block.failure=å±è”½ç”¨æˆ·å¤±è´¥ï¼š%s block.unblock=å–æ¶ˆå±è”½ -block.unblock.failure=å±è”½ç”¨æˆ·å¤±è´¥: %s +block.unblock.failure=å–æ¶ˆå±è”½ç”¨æˆ·å¤±è´¥ï¼š%s block.blocked=您已å±è”½æ¤ç”¨æˆ·ã€‚ block.title=å±è”½ä¸€ä¸ªç”¨æˆ· block.info=å±è”½ç”¨æˆ·ä¼šé˜»æ¢ä»–们与仓库进行交互,例如打开或评论åˆå¹¶è¯·æ±‚或出现问题。了解更多关于å±è”½ç”¨æˆ·çš„ä¿¡æ¯ã€‚ block.info_1=阻æ¢ç”¨æˆ·åœ¨æ‚¨çš„叿ˆ·å’Œä»“库ä¸è¿›è¡Œä»¥ä¸‹æ“作: -block.info_2=å…³æ³¨ä½ çš„è´¦å· -block.info_3=通过@æåŠæ‚¨çš„用户å呿‚¨å‘é€é€šçŸ¥ +block.info_2=å…³æ³¨æ‚¨çš„è´¦å· +block.info_3=通过 @ æåŠæ‚¨çš„用户å呿‚¨å‘é€é€šçŸ¥ block.info_4=邀请您作为åä½œè€…åˆ°ä»–ä»¬çš„ä»“åº“ä¸ block.info_5=在仓库ä¸ç‚¹èµžã€æ´¾ç”Ÿæˆ–关注 block.info_6=æ‰“å¼€å’Œè¯„è®ºå·¥å•æˆ–åˆå¹¶è¯·æ±‚ @@ -730,18 +730,18 @@ uid=UID webauthn=两æ¥éªŒè¯ï¼ˆå®‰å…¨å¯†é’¥ï¼‰ public_profile=å…¬å¼€ä¿¡æ¯ -biography_placeholder=å‘Šè¯‰æˆ‘ä»¬ä¸€ç‚¹æ‚¨è‡ªå·±ï¼ (您å¯ä»¥ä½¿ç”¨Markdown) -location_placeholder=ä¸Žä»–äººåˆ†äº«ä½ çš„å¤§æ¦‚ä½ç½® -profile_desc=控制您的个人资料对其他用户的显示方å¼ã€‚您的主è¦ç”µå邮件地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤å’ŒåŸºäºŽç½‘页界é¢çš„ Git æ“作 -password_username_disabled=您ä¸è¢«å…è®¸æ›´æ”¹ä½ çš„ç”¨æˆ·å。更多详情请è”系您的系统管ç†å‘˜ã€‚ -password_full_name_disabled=您ä¸è¢«å…è®¸æ›´æ”¹ä½ çš„å…¨å。请è”系您的站点管ç†å‘˜äº†è§£æ›´å¤šè¯¦æƒ…。 +biography_placeholder=å‘Šè¯‰æˆ‘ä»¬ä¸€ç‚¹æ‚¨è‡ªå·±ï¼ (您å¯ä»¥ä½¿ç”¨ Markdown) +location_placeholder=与他人分享您的大概ä½ç½® +profile_desc=控制您的个人资料对其他用户的显示方å¼ã€‚您的主邮箱地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤å’ŒåŸºäºŽç½‘页的 Git æ“作。 +password_username_disabled=您ä¸è¢«å…许更改您的用户å。更多详情请è”系您的系统管ç†å‘˜ã€‚ +password_full_name_disabled=您ä¸è¢«å…许更改您的全å。请è”系您的站点管ç†å‘˜äº†è§£æ›´å¤šè¯¦æƒ…。 full_name=自定义åç§° website=个人网站 location=所在地区 update_theme=更新主题 update_profile=æ›´æ–°ä¿¡æ¯ update_language=æ›´æ–°è¯è¨€ -update_language_not_found=è¯è¨€ %s ä¸å¯ç”¨ã€‚ +update_language_not_found=è¯è¨€ã€Œ%sã€ä¸å¯ç”¨ã€‚ update_language_success=è¯è¨€å·²æ›´æ–°ã€‚ update_profile_success=您的资料信æ¯å·²ç»æ›´æ–° change_username=您的用户å已更改。 @@ -752,7 +752,7 @@ cancel=å–æ¶ˆæ“作 language=界é¢è¯è¨€ ui=主题 hidden_comment_types=éšè—的评论类型 -hidden_comment_types_description=æ¤å¤„选ä¸çš„æ³¨é‡Šç±»åž‹ä¸ä¼šæ˜¾ç¤ºåœ¨é—®é¢˜é¡µé¢ä¸ã€‚æ¯”å¦‚ï¼Œå‹¾é€‰â€æ ‡ç¾â€œåˆ 除所有 "<user> æ·»åŠ /åˆ é™¤çš„ <label>" 注释。 +hidden_comment_types_description=æ¤å¤„选ä¸çš„æ³¨é‡Šç±»åž‹ä¸ä¼šæ˜¾ç¤ºåœ¨å·¥å•页é¢ä¸ã€‚æ¯”å¦‚ï¼Œå‹¾é€‰ã€Œæ ‡ç¾ã€åˆ 除所有「{user} æ·»åŠ /åˆ é™¤çš„ {label}ã€æ³¨é‡Šã€‚ hidden_comment_types.ref_tooltip=注释æ¤é—®é¢˜åœ¨ä½•处被æåŠè¿‡ï¼Œå¦‚å¦ä¸€ä¸ªé—®é¢˜ã€ä»£ç æäº¤ç‰ hidden_comment_types.issue_ref_tooltip=注释用户在何处更改了与æ¤é—®é¢˜ç›¸å…³è”的分支/æ ‡ç¾ comment_type_group_reference=引用 @@ -774,7 +774,7 @@ privacy=éšç§è®¾ç½® keep_activity_private=éšè—个人资料页é¢ä¸çš„æ´»åЍ keep_activity_private_popup=使活动仅对您和管ç†å‘˜å¯è§ -lookup_avatar_by_mail=从电åé‚®ç®±åœ°å€æŸ¥æ‰¾å¤´åƒ +lookup_avatar_by_mail=ä»Žé‚®ç®±åœ°å€æŸ¥æ‰¾å¤´åƒ federated_avatar_lookup=Federated Avatar 查找 enable_custom_avatar=å¯åŠ¨è‡ªå®šä¹‰å¤´åƒ choose_new_avatar=é€‰æ‹©æ–°çš„å¤´åƒ @@ -798,61 +798,61 @@ emails=é‚®ç®±åœ°å€ manage_emails=管ç†é‚®ç®±åœ°å€ manage_themes=选择默认主题 manage_openid=ç®¡ç† OpenID åœ°å€ -email_desc=您的主è¦ç”µå邮件地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤ï¼ŒåŸºäºŽç½‘页界é¢çš„Gitæ“作(åªè¦å®ƒä¸æ˜¯è®¾ç½®ä¸ºéšè—çš„)。 +email_desc=您的主邮箱地å€å°†ç”¨äºŽé€šçŸ¥ã€å¯†ç æ¢å¤ä»¥åŠåŸºäºŽç½‘页的 Git æ“作(如果它未设为éšè—)。 theme_desc=这将是您在整个网站上的默认主题。 theme_colorblindness_help=颜色障ç¢ä¸»é¢˜æ”¯æŒ -theme_colorblindness_prompt=Gitea åªèƒ½èŽ·å¾—ä¸€äº›åŸºæœ¬çš„é¢œè‰²éšœç¢æ”¯æŒï¼Œè¿™äº›ä¸»é¢˜åªå®šä¹‰äº†å°‘数颜色。 这项工作ä»åœ¨è¿›è¡Œä¸ï¼Œå¯ä»¥é€šè¿‡åœ¨ä¸»é¢˜çš„ CSS 文件ä¸å®šä¹‰æ›´å¤šé¢œè‰²æ¥åšæ›´å¤šçš„æ”¹è¿›ã€‚ +theme_colorblindness_prompt=Gitea åªèƒ½èŽ·å¾—ä¸€äº›åŸºæœ¬çš„é¢œè‰²éšœç¢æ”¯æŒï¼Œè¿™äº›ä¸»é¢˜åªå®šä¹‰äº†å°‘数颜色。这项工作ä»åœ¨è¿›è¡Œä¸ï¼Œå¯ä»¥é€šè¿‡åœ¨ä¸»é¢˜çš„ CSS 文件ä¸å®šä¹‰æ›´å¤šé¢œè‰²æ¥åšæ›´å¤šçš„æ”¹è¿›ã€‚ primary=ä¸»è¦ activated=已激活 requires_activation=éœ€è¦æ¿€æ´» -primary_email=设为主è¦é‚®ä»¶åœ°å€ +primary_email=设为主邮箱 activate_email=å‘逿¿€æ´»é‚®ä»¶ activations_pending=ç‰å¾…激活 -can_not_add_email_activations_pending=有一个待处ç†çš„æ¿€æ´»è¯·æ±‚,请ç¨ç‰å‡ 分钟åŽå†å°è¯•æ·»åŠ æ–°çš„ç”µå邮件地å€ã€‚ +can_not_add_email_activations_pending=有一个待处ç†çš„æ¿€æ´»è¯·æ±‚,请ç¨ç‰å‡ 分钟åŽå†å°è¯•æ·»åŠ æ–°çš„é‚®ç®±åœ°å€ã€‚ delete_email=移除 -email_deletion=移除电åé‚®ä»¶åœ°å€ -email_deletion_desc=电å邮箱地å€å’Œç›¸å…³ä¿¡æ¯å°†ä¼šè¢«åˆ 除。使用æ¤ç”µå邮箱地å€å‘é€çš„Gitæäº¤å°†ä¼šä¿ç•™ï¼Œç»§ç»ï¼Ÿ -email_deletion_success=您的电å邮箱地å€å·²è¢«ç§»é™¤ã€‚ +email_deletion=ç§»é™¤é‚®ç®±åœ°å€ +email_deletion_desc=邮箱地å€å’Œç›¸å…³ä¿¡æ¯å°†ä¼šè¢«åˆ 除。使用æ¤é‚®ç®±åœ°å€å‘é€çš„Gitæäº¤å°†ä¼šä¿ç•™ï¼Œç»§ç»ï¼Ÿ +email_deletion_success=您的邮箱地å€å·²ç§»é™¤ã€‚ theme_update_success=您的主题已更新。 theme_update_error=所选主题ä¸å˜åœ¨ã€‚ openid_deletion=移除 OpenID åœ°å€ -openid_deletion_desc=åˆ é™¤æ¤ OpenID 地å€å°†ä¼šé˜»æ¢ä½ ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•ã€‚ä½ ç¡®å®šè¦ç»§ç»å—? -openid_deletion_success=OpenID地å€å·²è¢«ç§»é™¤ã€‚ +openid_deletion_desc=åˆ é™¤æ¤ OpenID 地å€å°†ä¼šé˜»æ¢æ‚¨ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•。您确定è¦ç»§ç»å—? +openid_deletion_success=OpenID 地å€å·²ç§»é™¤ã€‚ add_new_email=æ·»åŠ æ–°çš„é‚®ç®±åœ°å€ add_new_openid=æ·»åŠ æ–°çš„ OpenID URI -add_email=å¢žåŠ ç”µåé‚®ä»¶åœ°å€ +add_email=æ–°å¢žé‚®ç®±åœ°å€ add_openid=æ·»åŠ OpenID URI -add_email_confirmation_sent=一å°ç¡®è®¤é‚®ä»¶å·²ç»è¢«å‘é€è‡³ %s,请检查您的收件箱并在 %s 内完æˆç¡®è®¤æ³¨å†Œæ“作。 -add_email_success=新的电å邮件地å€å·²æ·»åŠ ã€‚ -email_preference_set_success=电å邮件首选项已æˆåŠŸè®¾ç½®ã€‚ +add_email_confirmation_sent=一å°ç¡®è®¤é‚®ä»¶å·²ç»å‘é€è‡³ã€Œ%sã€ï¼Œè¯·æ£€æŸ¥æ‚¨çš„æ”¶ä»¶ç®±å¹¶åœ¨ %s 内完æˆç¡®è®¤æ³¨å†Œæ“作。 +add_email_success=新邮箱地å€å·²æ·»åŠ ã€‚ +email_preference_set_success=邮件首选项已æˆåŠŸè®¾ç½®ã€‚ add_openid_success=æ–°çš„ OpenID 地å€å·²æ·»åŠ ã€‚ -keep_email_private=éšè—电åé‚®ä»¶åœ°å€ -keep_email_private_popup=这将会éšè—您的电å邮件地å€ï¼Œä¸ä»…在您的个人资料ä¸ï¼Œè¿˜åœ¨æ‚¨ä½¿ç”¨Web界é¢åˆ›å»ºåˆå¹¶è¯·æ±‚或编辑文件时。已推é€çš„æäº¤å°†ä¸ä¼šè¢«ä¿®æ”¹ã€‚在æäº¤ä¸ä½¿ç”¨ %s 以和您的账å·å…³è”。 -openid_desc=OpenID è®©ä½ å¯ä»¥å°†è®¤è¯è½¬å‘到外部æœåŠ¡ã€‚ +keep_email_private=éšè—é‚®ç®±åœ°å€ +keep_email_private_popup=这将会éšè—您的邮箱地å€ï¼Œä¸ä»…在您的个人资料ä¸ï¼Œè¿˜åœ¨æ‚¨ä½¿ç”¨ Web 界é¢åˆ›å»ºåˆå¹¶è¯·æ±‚或编辑文件时。已推é€çš„æäº¤å°†ä¸ä¼šè¢«ä¿®æ”¹ã€‚在æäº¤ä¸ä½¿ç”¨ %s 以和您的账å·å…³è”。 +openid_desc=OpenID 让您å¯ä»¥å°†è®¤è¯è½¬å‘到外部æœåŠ¡ã€‚ manage_ssh_keys=ç®¡ç† SSH 密钥 -manage_ssh_principals=管ç†SSHè¯ä¹¦è§„则 +manage_ssh_principals=ç®¡ç† SSH è¯ä¹¦è§„则 manage_gpg_keys=ç®¡ç† GPG 密钥 add_key=å¢žåŠ å¯†é’¥ -ssh_desc=这些 SSH 公钥已ç»å…³è”åˆ°ä½ çš„è´¦å·ã€‚相应的ç§é’¥æ‹¥æœ‰å®Œå…¨æ“ä½œä½ çš„ä»“åº“çš„æƒé™ã€‚ -principal_desc=这些SSHè¯ä¹¦è§„则已关è”åˆ°ä½ çš„è´¦å·å°†å…è®¸å®Œå…¨è®¿é—®ä½ çš„æ‰€æœ‰ä»“åº“ã€‚ -gpg_desc=这些 GPG 公钥已ç»å…³è”åˆ°ä½ çš„è´¦å·ã€‚请妥善ä¿ç®¡ä½ çš„ç§é’¥å› ä¸ºä»–ä»¬å°†è¢«ç”¨äºŽè®¤è¯æäº¤ã€‚ +ssh_desc=这些 SSH 公钥已ç»å…³è”到您的账å·ã€‚相应的ç§é’¥æ‹¥æœ‰å®Œå…¨æ“作您仓库的æƒé™ã€‚ +principal_desc=这些 SSH è¯ä¹¦è§„则已关è”到您的账å·å°†å…许完全访问您所有仓库。 +gpg_desc=这些 GPG 公钥已ç»å…³è”到您的账å·ã€‚请妥善ä¿ç®¡æ‚¨çš„ç§é’¥å› ä¸ºä»–ä»¬å°†è¢«ç”¨äºŽè®¤è¯æäº¤ã€‚ ssh_helper=<strong>需è¦å¸®åŠ©ï¼Ÿ</strong> 请查看有关 <a href="%s">å¦‚ä½•ç”Ÿæˆ SSH 密钥</a> 或 <a href="%s">å¸¸è§ SSH 问题</a> å¯»æ‰¾ç”æ¡ˆã€‚ -gpg_helper=<strong>需è¦å¸®åŠ©å—?</strong>看一看 GitHub <a href="%s">关于GPG</a> 的指导。 +gpg_helper=<strong>需è¦å¸®åŠ©å—?</strong>看一看 GitHub <a href="%s">关于 GPG</a> 的指导。 add_new_key=å¢žåŠ SSH 密钥 add_new_gpg_key=æ·»åŠ çš„ GPG 密钥 key_content_ssh_placeholder=以 'ssh-ed25519'〠'ssh-rsa'〠'ecdsa-sha2-nistp256'ã€'ecdsa-sha2-nistp384'ã€'ecdsa-sha2-nistp521'〠'sk-ecdsa-sha2-nistp256@openssh.com' 或 'sk-ssh-ed25519@openssh.com' 开头 key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头 add_new_principal=æ·»åŠ è§„åˆ™ ssh_key_been_used=æ¤ SSH å¯†é’¥å·²æ·»åŠ åˆ°æœåŠ¡å™¨ã€‚ -ssh_key_name_used=使用相åŒåç§°çš„SSH公钥已ç»å˜åœ¨ï¼ +ssh_key_name_used=使用相åŒåç§°çš„ SSH 公钥已ç»å˜åœ¨ã€‚ ssh_principal_been_used=æ¤è§„则已ç»åŠ å…¥åˆ°äº†æœåŠ¡å™¨ã€‚ -gpg_key_id_used=使用相åŒåç§°çš„GPG公钥已ç»å˜åœ¨ï¼ -gpg_no_key_email_found=æ¤ GPG å¯†é’¥ä¸Žæ‚¨å¸æˆ·å…³è”的任何已激活电å邮件地å€å‡ä¸åŒ¹é…。如果您在æä¾›çš„令牌上ç¾å,它ä»ç„¶å¯ä»¥è¢«æ·»åŠ ã€‚ +gpg_key_id_used=使用相åŒåç§°çš„ GPG 公钥已ç»å˜åœ¨ã€‚ +gpg_no_key_email_found=æ¤ GPG å¯†é’¥ä¸Žæ‚¨å¸æˆ·å…³è”的任何已激活邮箱地å€å‡ä¸åŒ¹é…。如果您已对æä¾›çš„令牌进行ç¾å,ä»å¯æ·»åŠ è¯¥å¯†é’¥ã€‚ gpg_key_matched_identities=匹é…的身份: -gpg_key_matched_identities_long=æ¤å¯†é’¥ä¸åŒ…å«çš„身份信æ¯ä¸Žä¸‹é¢è¿™ä¸ªè¯¥ç”¨æˆ·å·²æ¿€æ´»ç”µåé‚®ä»¶åœ°å€æ˜¯ç›¸åŒ¹é…çš„ã€‚å› æ¤ï¼Œèƒ½ä¸Žè¿™äº›ç”µå邮件地å€ç›¸åŒ¹é…çš„æäº¤å¯ä»¥é€šè¿‡æ¤å¯†é’¥è¿›è¡ŒéªŒè¯ã€‚ +gpg_key_matched_identities_long=æ¤å¯†é’¥ä¸åŒ…å«çš„身份信æ¯ä¸Žä»¥ä¸‹æ¤ç”¨æˆ·å·²æ¿€æ´»é‚®ç®±åœ°å€åŒ¹é…。与这些邮箱地å€ç›¸åŒ¹é…çš„æäº¤å¯é€šè¿‡æ¤å¯†é’¥è¿›è¡ŒéªŒè¯ã€‚ gpg_key_verified=已验è¯çš„密钥 -gpg_key_verified_long=密钥已ç»ç”¨ä»¤ç‰Œè¿›è¡Œäº†éªŒè¯ï¼Œå¹¶ä¸”å¯ä»¥ç”¨æ¥éªŒè¯åŒ¹é…æ¤ç”¨æˆ·ä»»ä½•已激活电å邮件地å€çš„æäº¤ï¼Œä»¥åŠåŒ¹é…æ¤å¯†é’¥çš„任何身份。 +gpg_key_verified_long=密钥已通过令牌验è¯ï¼Œé™¤ä¸Žæ¤å¯†é’¥åŒ¹é…的任何身份外,还å¯ç”¨äºŽéªŒè¯ä¸Žè¯¥ç”¨æˆ·ä»»ä½•已激活邮箱地å€åŒ¹é…çš„æäº¤ã€‚ gpg_key_verify=éªŒè¯ gpg_invalid_token_signature=æä¾›çš„ GPG 密钥ã€ç¾å和令牌ä¸åŒ¹é…或过期。 gpg_token_required=您必须为下é¢çš„令牌æä¾›ç¾å @@ -860,9 +860,9 @@ gpg_token=令牌 gpg_token_help=您å¯ä»¥ä½¿ç”¨ä»¥ä¸‹æ–¹å¼ç”Ÿæˆç¾å: gpg_token_signature=GPG 增强ç¾å key_signature_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头 -verify_gpg_key_success=GPG 密钥 %s 已被验è¯ã€‚ +verify_gpg_key_success=GPG 密钥「%sã€å·²éªŒè¯ã€‚ ssh_key_verified=已验è¯çš„密钥 -ssh_key_verified_long=密钥已ç»ç”¨ä»¤ç‰Œè¿›è¡Œäº†éªŒè¯ï¼Œå¹¶ä¸”å¯ä»¥ç”¨æ¥éªŒè¯åŒ¹é…æ¤ç”¨æˆ·ä»»ä½•已激活电å邮件地å€çš„æäº¤ã€‚ +ssh_key_verified_long=密钥已通过令牌验è¯ï¼Œå¯ç”¨äºŽéªŒè¯ä¸Žè¯¥ç”¨æˆ·ä»»ä½•已激活邮箱地å€åŒ¹é…çš„æäº¤ã€‚ ssh_key_verify=éªŒè¯ ssh_invalid_token_signature=æä¾›çš„ SSH 密钥ã€ç¾å或令牌ä¸åŒ¹é…或令牌已过期。 ssh_token_required=您必须为下é¢çš„令牌æä¾›ç¾å @@ -870,24 +870,24 @@ ssh_token=令牌 ssh_token_help=您å¯ä»¥ä½¿ç”¨ä»¥ä¸‹æ–¹å¼ç”Ÿæˆç¾å: ssh_token_signature=增强 SSH ç¾å key_signature_ssh_placeholder=以 '-----BEGIN SSH SIGNATURE -----' 开头 -verify_ssh_key_success=SSH 密钥 %s 已被验è¯ã€‚ +verify_ssh_key_success=SSH 密钥「%sã€å·²éªŒè¯ã€‚ subkeys=å项 -key_id=é”®ID +key_id=密钥 ID key_name=密钥åç§° key_content=密钥内容 principal_content=内容 -add_key_success=SSH 密钥 %s æ·»åŠ æˆåŠŸã€‚ -add_gpg_key_success=GPG 密钥 %s æ·»åŠ æˆåŠŸã€‚ -add_principal_success=SSHè¯ä¹¦è§„则 %s æ·»åŠ æˆåŠŸã€‚ +add_key_success=SSH 密钥「%sã€æ·»åŠ æˆåŠŸã€‚ +add_gpg_key_success=GPG 密钥「%sã€æ·»åŠ æˆåŠŸã€‚ +add_principal_success=SSH è¯ä¹¦è§„则「%sã€æ·»åŠ æˆåŠŸã€‚ delete_key=åˆ é™¤ ssh_key_deletion=åˆ é™¤ SSH 密钥 gpg_key_deletion=åˆ é™¤ GPG 密钥 ssh_principal_deletion=åˆ é™¤ SSH è¯ä¹¦è§„则 ssh_key_deletion_desc=åˆ é™¤ SSH 公钥将喿¶ˆå¯¹åº”çš„ç§é’¥å¯¹æ‚¨çš„ Gitea 叿ˆ·çš„访问æƒé™ã€‚ç»§ç»ï¼Ÿ -gpg_key_deletion_desc=åˆ é™¤ GPG å…¬é’¥å°†æ— æ³•è®¤çŸ¥ä½¿ç”¨å¯¹åº”ç§é’¥ç¾åçš„æäº¤ï¼Œç»§ç»ï¼Ÿ +gpg_key_deletion_desc=åˆ é™¤ GPG å…¬é’¥å°†æ— æ³•è®¤è¯å¯¹åº”ç§é’¥ç¾åçš„æäº¤ï¼Œç»§ç»ï¼Ÿ ssh_principal_deletion_desc=åˆ é™¤æ¤ SSH è¯ä¹¦è§„åˆ™å°†å–æ¶ˆå®ƒå¯¹æ‚¨çš„账户的访问æƒé™ã€‚ç»§ç»ï¼Ÿ -ssh_key_deletion_success=GPG å¯†é’¥å·²è¢«åˆ é™¤ã€‚ -gpg_key_deletion_success=GPG å¯†é’¥å·²è¢«åˆ é™¤ã€‚ +ssh_key_deletion_success=SSH å¯†é’¥å·²åˆ é™¤ã€‚ +gpg_key_deletion_success=GPG å¯†é’¥å·²åˆ é™¤ã€‚ ssh_principal_deletion_success=æ¤è§„åˆ™åˆ é™¤æˆåŠŸã€‚ added_on=æ·»åŠ äºŽ %s valid_until_date=有效期至 %s @@ -901,7 +901,7 @@ token_state_desc=7 天内使用过该密钥 principal_state_desc=7 天内使用过该规则 show_openid=在个人信æ¯ä¸Šæ˜¾ç¤º hide_openid=在个人信æ¯ä¸Šéšè— -ssh_disabled=SSH 被ç¦ç”¨ +ssh_disabled=SSH å·²ç¦ç”¨ ssh_signonly=SSH ç›®å‰å·²ç¦ç”¨ï¼Œå› æ¤è¿™äº›å¯†é’¥ä»…用于æäº¤ç¾å验è¯ã€‚ ssh_externally_managed=æ¤ SSH 密钥是由外部管ç†çš„ manage_social=管ç†å…³è”ç¤¾äº¤å¸æˆ· @@ -921,10 +921,10 @@ access_token_deletion=åˆ é™¤ Access Token access_token_deletion_cancel_action=å–æ¶ˆ access_token_deletion_confirm_action=刪除 access_token_deletion_desc=åˆ é™¤ä»¤ç‰Œå°†æ’¤é”€ç¨‹åºå¯¹æ‚¨è´¦æˆ·çš„访问æƒé™ã€‚æ¤æ“ä½œæ— æ³•æ’¤æ¶ˆã€‚æ˜¯å¦ç»§ç»ï¼Ÿ -delete_token_success=令牌已ç»è¢«åˆ 除。使用该令牌的应用将ä¸å†èƒ½å¤Ÿè®¿é—®ä½ 的账å·ã€‚ +delete_token_success=令牌已ç»è¢«åˆ 除。使用该令牌的应用将ä¸å†èƒ½å¤Ÿè®¿é—®æ‚¨çš„è´¦å·ã€‚ repo_and_org_access=仓库和组织访问æƒé™ permissions_public_only=仅公开 -permissions_access_all=全部(公开ã€ç§æœ‰å’Œå—é™) +permissions_access_all=全部(公开ã€ç§æœ‰å’Œå—é™ï¼‰ permission_not_set=未设置 permission_no_access=æ— è®¿é—®æƒé™ permission_read=å¯è¯» @@ -933,7 +933,7 @@ permission_anonymous_read=匿å读 permission_everyone_read=所有人å¯è¯» permission_everyone_write=所有人å¯å†™ access_token_desc=所选令牌æƒé™ä»…é™äºŽå¯¹åº”çš„ <a %s>API</a> 路由的授æƒã€‚阅读 <a %s>文档</a> ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚ -at_least_one_permission=ä½ éœ€è¦é€‰æ‹©è‡³å°‘一个æƒé™æ‰èƒ½åˆ›å»ºä»¤ç‰Œ +at_least_one_permission=您需è¦é€‰æ‹©è‡³å°‘一个æƒé™æ‰èƒ½åˆ›å»ºä»¤ç‰Œ permissions_list=æƒé™ï¼š manage_oauth2_applications=ç®¡ç† OAuth2 åº”ç”¨ç¨‹åº @@ -941,13 +941,13 @@ edit_oauth2_application=编辑 OAuth2 åº”ç”¨ç¨‹åº oauth2_applications_desc=OAuth2 应用å…许第三方应用程åºåœ¨æ¤ Gitea 实例ä¸å®‰å…¨éªŒè¯ç”¨æˆ·ã€‚ remove_oauth2_application=åˆ é™¤ OAuth2 åº”ç”¨ç¨‹åº remove_oauth2_application_desc=åˆ é™¤ OAuth2 应用将撤销所有ç¾å的访问令牌。继ç»å—? -remove_oauth2_application_success=è¯¥åº”ç”¨å·²è¢«åˆ é™¤ã€‚ +remove_oauth2_application_success=è¯¥åº”ç”¨å·²åˆ é™¤ã€‚ create_oauth2_application=创建新的 OAuth2 åº”ç”¨ç¨‹åº create_oauth2_application_button=创建应用 create_oauth2_application_success=您已æˆåŠŸåˆ›å»ºäº†ä¸€ä¸ªæ–°çš„ OAuth2 应用。 update_oauth2_application_success=您已æˆåŠŸæ›´æ–°äº†æ¤ OAuth2 应用。 oauth2_application_name=应用åç§° -oauth2_confidential_client=æœºå¯†å®¢æˆ·ç«¯ã€‚æ˜¯å¦æ˜¯èƒ½å¤Ÿç»´æŒå‡æ®æœºå¯†æ€§çš„应用,比如网页应用程åºã€‚如果是本地应用程åºè¯·ä¸è¦å‹¾é€‰ï¼ŒåŒ…括桌é¢å’Œç§»åŠ¨ç«¯åº”ç”¨ã€‚ +oauth2_confidential_client=机密客户端。对于需è¦ä¿å¯†çš„应用(例如 Web 应用),请选择æ¤é€‰é¡¹ã€‚对于包括桌é¢å’Œç§»åŠ¨åº”ç”¨åœ¨å†…çš„æœ¬æœºåº”ç”¨ï¼Œè¯·å‹¿é€‰æ‹©æ¤é€‰é¡¹ã€‚ oauth2_skip_secondary_authorization=首次授æƒåŽå…è®¸å…¬å…±å®¢æˆ·ç«¯è·³è¿‡æŽˆæƒæ¥éª¤ã€‚ <strong>å¯èƒ½ä¼šå¸¦æ¥å®‰å…¨é£Žé™©ã€‚</strong> oauth2_redirect_uris=é‡å®šå‘ URI。æ¯è¡Œä¸€ä¸ª URI。 save_application=ä¿å˜ @@ -959,7 +959,7 @@ oauth2_client_secret_hint=您离开或刷新æ¤é¡µé¢åŽå°†ä¸ä¼šå†æ˜¾ç¤ºæ¤å¯† oauth2_application_edit=编辑 oauth2_application_create_description=OAuth2 应用å…许您的第三方应用程åºè®¿é—®æ¤å®žä¾‹çš„ç”¨æˆ·å¸æˆ·ã€‚ oauth2_application_remove_description=移除一个OAuth2应用将会阻æ¢å®ƒè®¿é—®æ¤å®žä¾‹ä¸Šçš„已授æƒç”¨æˆ·è´¦æˆ·ã€‚是å¦ç»§ç»ï¼Ÿ -oauth2_application_locked=如果é…ç½®å¯ç”¨ï¼ŒGitea预注册一些OAuth2应用程åºã€‚ ä¸ºäº†é˜²æ¢æ„外的行为, 这些应用既ä¸èƒ½ç¼–辑也ä¸èƒ½åˆ 除。请å‚阅OAuth2æ–‡æ¡£ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚ +oauth2_application_locked=如果é…ç½®å¯ç”¨ï¼ŒGitea 将在å¯åŠ¨æ—¶é¢„æ³¨å†Œä¸€äº› OAuth2 应用程åºã€‚ ä¸ºäº†é˜²æ¢æ„外的行为, 这些应用既ä¸èƒ½ç¼–辑也ä¸èƒ½åˆ 除。请å‚阅 OAuth2 æ–‡æ¡£ä»¥èŽ·å–æ›´å¤šä¿¡æ¯ã€‚ authorized_oauth2_applications=已授æƒçš„ OAuth2 应用 authorized_oauth2_applications_description=您已授予这些第三方应用程åºè®¿é—®æ‚¨çš„个人 Gitea 账户的æƒé™ã€‚请撤销那些您ä¸å†éœ€è¦çš„应用程åºçš„访问æƒé™ã€‚ @@ -968,10 +968,10 @@ revoke_oauth2_grant=撤回æƒé™ revoke_oauth2_grant_description=确定撤销æ¤ä¸‰æ–¹åº”用程åºçš„æŽˆæƒï¼Œå¹¶é˜»æ¢æ¤åº”用程åºè®¿é—®æ‚¨çš„æ•°æ®ï¼Ÿ revoke_oauth2_grant_success=æˆåŠŸæ’¤é”€äº†è®¿é—®æƒé™ã€‚ -twofa_desc=ä¸ºä¿æŠ¤ä½ çš„è´¦å·å¯†ç å®‰å…¨ï¼Œä½ å¯ä»¥ä½¿ç”¨æ™ºèƒ½æ‰‹æœºæˆ–å…¶å®ƒè®¾å¤‡æ¥æŽ¥æ”¶æ—¶é—´å¼ºç›¸å…³çš„ä¸€æ¬¡æ€§å¯†ç (TOTP)。 +twofa_desc=ä¸ºä¿æŠ¤æ‚¨çš„è´¦å·å¯†ç 安全,您å¯ä»¥ä½¿ç”¨æ™ºèƒ½æ‰‹æœºæˆ–å…¶å®ƒè®¾å¤‡æ¥æŽ¥æ”¶æ—¶é—´å¼ºç›¸å…³çš„ä¸€æ¬¡æ€§å¯†ç (TOTP)。 twofa_recovery_tip=如果您丢失了您的设备,您将能够使用一次性æ¢å¤å¯†é’¥æ¥é‡æ–°èŽ·å¾—å¯¹æ‚¨è´¦æˆ·çš„è®¿é—®ã€‚ -twofa_is_enrolled=ä½ çš„è´¦å·<strong>å·²å¯ç”¨</strong>了两æ¥éªŒè¯ã€‚ -twofa_not_enrolled=ä½ çš„è´¦å·æœªå¼€å¯ä¸¤æ¥éªŒè¯ã€‚ +twofa_is_enrolled=您的账å·<strong>å·²å¯ç”¨</strong>了两æ¥éªŒè¯ã€‚ +twofa_not_enrolled=æ‚¨çš„è´¦å·æœªå¼€å¯ä¸¤æ¥éªŒè¯ã€‚ twofa_disable=ç¦ç”¨ä¸¤æ¥è®¤è¯ twofa_scratch_token_regenerate=釿–°ç”Ÿæˆåˆå§‹ä»¤ç‰Œ twofa_scratch_token_regenerated=您的åˆå§‹ä»¤ç‰ŒçŽ°åœ¨æ˜¯ %sã€‚å°†å…¶å˜æ”¾åœ¨å®‰å…¨çš„地方,它将ä¸ä¼šå†æ¬¡æ˜¾ç¤ºã€‚ @@ -979,12 +979,12 @@ twofa_enroll=å¯ç”¨ä¸¤æ¥éªŒè¯ twofa_disable_note=如果需è¦, å¯ä»¥ç¦ç”¨åŒå› ç´ èº«ä»½éªŒè¯ã€‚ twofa_disable_desc=关掉两æ¥éªŒè¯ä¼šä½¿å¾—您的账å·ä¸å®‰å…¨ï¼Œç»§ç»æ‰§è¡Œï¼Ÿ regenerate_scratch_token_desc=如果您丢失了您的æ¢å¤å¯†é’¥æˆ–å·²ç»ä½¿ç”¨å®ƒç™»å½•, 您å¯ä»¥åœ¨è¿™é‡Œé‡ç½®å®ƒã€‚ -twofa_disabled=两æ¥éªŒè¯å·²è¢«ç¦ç”¨ã€‚ +twofa_disabled=两æ¥éªŒè¯å·²ç¦ç”¨ã€‚ scan_this_image=使用您的授æƒåº”用扫æè¿™å¼ 图片: or_enter_secret=或者输入密钥:%s then_enter_passcode=并输入应用程åºä¸æ˜¾ç¤ºçš„密ç : passcode_invalid=密ç 䏿£ç¡®ã€‚å†è¯•一次。 -twofa_enrolled=ä½ çš„è´¦å·å·²ç»å¯ç”¨äº†ä¸¤æ¥éªŒè¯ã€‚请ä¿å˜åˆå§‹ä»¤ç‰Œï¼ˆ%s)到一个安全的地方,æ¤ä»¤ç‰Œä»…显示一次。 +twofa_enrolled=您的账å·å·²ç»å¯ç”¨äº†ä¸¤æ¥éªŒè¯ã€‚请ä¿å˜åˆå§‹ä»¤ç‰Œï¼ˆ%s)到一个安全的地方,æ¤ä»¤ç‰Œä»…显示一次。 twofa_failed_get_secret=èŽ·å– secret 失败。 webauthn_desc=安全密钥是包å«åŠ å¯†å¯†é’¥çš„ç¡¬ä»¶è®¾å¤‡ã€‚å®ƒä»¬å¯ä»¥ç”¨äºŽåŒå› ç´ èº«ä»½éªŒè¯ã€‚å®‰å…¨å¯†é’¥å¿…é¡»æ”¯æŒ <a rel="noreferrer" target="_blank" href="%s">WebAuthn 身份验è¯å™¨</a> æ ‡å‡†ã€‚ @@ -1003,14 +1003,14 @@ remove_account_link=åˆ é™¤å·²ç»‘å®šçš„è´¦å· remove_account_link_desc=åˆ é™¤å·²ç»‘å®šå¸æˆ·å°†åŠé”€å…¶å¯¹æ‚¨çš„ Gitea 叿ˆ·çš„访问æƒé™ã€‚ç»§ç»ï¼Ÿ remove_account_link_success=已喿¶ˆç»‘å®šå¸æˆ·ã€‚ -hooks.desc=æ·»åŠ Webhooks,它们将会在您拥有的<strong>所有仓库</strong>ä¸Šè§¦å‘ +hooks.desc=æ·»åŠ Web é’©å,它们将会在您拥有的<strong>所有仓库</strong>上触å‘。 orgs_none=æ‚¨çŽ°åœ¨è¿˜ä¸æ˜¯ä»»ä½•组织的æˆå‘˜ã€‚ -repos_none=ä½ å¹¶ä¸æ‹¥æœ‰ä»»ä½•仓库。 +repos_none=æ‚¨å¹¶ä¸æ‹¥æœ‰ä»»ä½•仓库。 delete_account=åˆ é™¤å½“å‰å¸æˆ· delete_prompt=æ¤æ“ä½œå°†æ°¸ä¹…åˆ é™¤æ‚¨çš„ç”¨æˆ·å¸æˆ·ã€‚它 <strong>ä¸èƒ½</strong> 被撤消。 -delete_with_all_comments=ä½ çš„å¸æˆ·å¹´é¾„å°äºŽ %s。为了é¿å…å¹½çµè¯„论,所有工å•/åˆå¹¶è¯·æ±‚çš„è¯„è®ºéƒ½å°†ä¸Žå®ƒä¸€èµ·è¢«åˆ é™¤ã€‚ +delete_with_all_comments=æ‚¨çš„å¸æˆ·å¹´é¾„å°äºŽ %s。为了é¿å…å¹½çµè¯„论,所有工å•/åˆå¹¶è¯·æ±‚çš„è¯„è®ºéƒ½å°†ä¸Žå®ƒä¸€èµ·è¢«åˆ é™¤ã€‚ confirm_delete_account=ç¡®è®¤åˆ é™¤å¸æˆ· delete_account_title=åˆ é™¤å½“å‰å¸æˆ· delete_account_desc=ç¡®å®žè¦æ°¸ä¹…åˆ é™¤æ¤ç”¨æˆ·å¸æˆ·å—? @@ -1034,9 +1034,9 @@ new_repo_helper=代ç 仓库包å«äº†æ‰€æœ‰çš„项目文件,包括版本历å²è owner=拥有者 owner_helper=由于最大仓库数é‡é™åˆ¶ï¼Œä¸€äº›ç»„织å¯èƒ½ä¸ä¼šæ˜¾ç¤ºåœ¨ä¸‹æ‹‰åˆ—表ä¸ã€‚ repo_name=仓库åç§° -repo_name_profile_public_hint=.profile 是一个特殊的å˜å‚¨åº“,您å¯ä»¥ä½¿ç”¨å®ƒå°† README.md æ·»åŠ åˆ°æ‚¨çš„å…¬å…±ç»„ç»‡èµ„æ–™ä¸ï¼Œä»»ä½•人都å¯ä»¥çœ‹åˆ°ã€‚请确ä¿å®ƒæ˜¯å…¬å¼€çš„,并使用个人资料目录ä¸çš„ README 对其进行åˆå§‹åŒ–以开始使用。 -repo_name_profile_private_hint=.profile-private 是一个特殊的å˜å‚¨åº“,您å¯ä»¥ä½¿ç”¨å®ƒå‘您的组织æˆå‘˜ä¸ªäººèµ„æ–™æ·»åŠ README.md,仅对组织æˆå‘˜å¯è§ã€‚请确ä¿å®ƒæ˜¯ç§æœ‰çš„,并使用个人资料目录ä¸çš„ README 对其进行åˆå§‹åŒ–以开始使用。 -repo_name_helper=ç†æƒ³çš„仓库å称应由简çŸã€æœ‰æ„义和独特的关键è¯ç»„æˆã€‚".profile" å’Œ ".profile-private" å¯ç”¨äºŽä¸ºç”¨æˆ·/ç»„ç»‡æ·»åŠ README.md。 +repo_name_profile_public_hint=.profile 是一个特殊的仓库,您å¯ä»¥ä½¿ç”¨å®ƒå°† README.md æ·»åŠ åˆ°æ‚¨çš„å…¬å…±ç»„ç»‡èµ„æ–™ä¸ï¼Œä»»ä½•人都å¯ä»¥çœ‹åˆ°ã€‚请确ä¿å®ƒæ˜¯å…¬å¼€çš„,并使用个人资料目录ä¸çš„ README 对其进行åˆå§‹åŒ–以开始使用。 +repo_name_profile_private_hint=.profile-private 是一个特殊的仓库,您å¯ä»¥ä½¿ç”¨å®ƒå‘您的组织æˆå‘˜ä¸ªäººèµ„æ–™æ·»åŠ README.md,仅对组织æˆå‘˜å¯è§ã€‚请确ä¿å®ƒæ˜¯ç§æœ‰çš„,并使用个人资料目录ä¸çš„ README 对其进行åˆå§‹åŒ–以开始使用。 +repo_name_helper=ç†æƒ³çš„仓库å称应由简çŸã€æœ‰æ„义和独特的关键è¯ç»„æˆã€‚「.profileã€å’Œã€Œ.profile-privateã€å¯ç”¨äºŽä¸ºç”¨æˆ·/ç»„ç»‡æ·»åŠ README.md。 repo_size=ä»“åº“å¤§å° template=æ¨¡æ¿ template_select=é€‰æ‹©æ¨¡æ¿ @@ -1050,10 +1050,10 @@ visibility_fork_helper=(修改该值将会影å“到所有派生仓库) clone_helper=ä¸çŸ¥é“如何克隆?查看<a target="_blank" rel="noopener noreferrer" href="%s">帮助</a> 。 fork_repo=派生仓库 fork_from=派生自 -already_forked=ä½ å·²ç»æ´¾ç”Ÿè¿‡ %s +already_forked=æ‚¨å·²ç»æ´¾ç”Ÿè¿‡ %s fork_to_different_account=æ´¾ç”Ÿåˆ°å…¶ä»–è´¦å· fork_visibility_helper=æ— æ³•æ›´æ”¹æ´¾ç”Ÿä»“åº“çš„å¯è§æ€§ã€‚ -fork_branch=è¦å…‹éš†åˆ° Fork 的分支 +fork_branch=è¦å…‹éš†ä¸ºæ´¾ç”Ÿçš„分支 all_branches=所有分支 view_all_branches=查看所有分支 view_all_tags=æŸ¥çœ‹æ‰€æœ‰æ ‡ç¾ @@ -1076,15 +1076,15 @@ issue_labels=工啿 ‡ç¾ issue_labels_helper=é€‰æ‹©ä¸€ä¸ªå·¥å•æ ‡ç¾é›† license=授æƒè®¸å¯ license_helper=选择授æƒè®¸å¯æ–‡ä»¶ã€‚ -license_helper_desc=许å¯è¯è¯´æ˜Žäº†å…¶ä»–人å¯ä»¥å’Œä¸å¯ä»¥ç”¨æ‚¨çš„代ç åšä»€ä¹ˆã€‚ä¸ç¡®å®šå“ªä¸€ä¸ªé€‚åˆä½ çš„é¡¹ç›®ï¼Ÿè§ <a target="_blank" rel="noopener noreferrer" href="%s">选择一个许å¯è¯</a> +license_helper_desc=许å¯è¯è¯´æ˜Žäº†å…¶ä»–人å¯ä»¥å’Œä¸å¯ä»¥ç”¨æ‚¨çš„代ç åšä»€ä¹ˆã€‚ä¸ç¡®å®šå“ªä¸€ä¸ªé€‚åˆæ‚¨çš„é¡¹ç›®ï¼Ÿè§ <a target="_blank" rel="noopener noreferrer" href="%s">选择一个许å¯è¯</a> multiple_licenses=多许å¯è¯ object_format=å¯¹è±¡æ ¼å¼ object_format_helper=ä»“åº“çš„å¯¹è±¡æ ¼å¼ã€‚ä¹‹åŽæ— 法更改。SHA1 是最兼容的。 readme=自述 readme_helper=选择自述文件模æ¿ã€‚ readme_helper_desc=这是您å¯ä»¥ä¸ºæ‚¨çš„项目撰写完整æè¿°çš„地方。 -auto_init=åˆå§‹åŒ–仓库(æ·»åŠ . gitignoreã€è®¸å¯è¯å’Œè‡ªè¿°æ–‡ä»¶) -trust_model_helper=选择ç¾å验è¯çš„“信任模型â€ã€‚å¯èƒ½çš„选项是: +auto_init=åˆå§‹åŒ–ä»“åº“ï¼ˆæ·»åŠ .gitignoreã€è®¸å¯è¯å’Œè‡ªè¿°æ–‡ä»¶ï¼‰ +trust_model_helper=选择ç¾å验è¯çš„信任模型。å¯èƒ½çš„选项是: trust_model_helper_collaborator=å作者:信任å作者的ç¾å trust_model_helper_committer=æäº¤è€…:信任与æäº¤è€…相符的ç¾å trust_model_helper_collaborator_committer=å作者+æäº¤è€…:信任åä½œè€…åŒæ—¶æ˜¯æäº¤è€…çš„ç¾å @@ -1106,13 +1106,13 @@ mirror_address_protocol_invalid=æä¾›çš„URLæ— æ•ˆã€‚åªèƒ½ä½¿ç”¨http(s)://或gi mirror_lfs=大文件å˜å‚¨ (LFS) mirror_lfs_desc=é•œåƒ LFS æ•°æ®ã€‚ mirror_lfs_endpoint=LFS ç½‘å€ -mirror_lfs_endpoint_desc=åŒæ¥å°†å°è¯•ä½¿ç”¨å…‹éš†ç½‘å€æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œä½ 还å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚ +mirror_lfs_endpoint_desc=åŒæ¥å°†å°è¯•ä½¿ç”¨å…‹éš†ç½‘å€æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œæ‚¨è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚ mirror_last_synced=ä¸Šæ¬¡åŒæ¥ mirror_password_placeholder=(未更改) mirror_password_blank_placeholder=(未设置) mirror_password_help=更改用户åä»¥åˆ é™¤å·²å‚¨å˜çš„密ç 。 watchers=关注者 -stargazers=称赞者 +stargazers=已赞者 stars_remove_warning=这将清除æ¤ä»“库的所有点赞数。 forks=派生仓库 stars=点赞数 @@ -1136,11 +1136,11 @@ user_search_tooltip=最多显示30å用户 tree_path_not_found=%[2]s ä¸ä¸å˜åœ¨è·¯å¾„ %[1]s transfer.accept=接å—转移 -transfer.accept_desc=`转移到 "%s"` +transfer.accept_desc=转移到「%s〠transfer.reject=æ‹’ç»è½¬ç§» -transfer.reject_desc=`å–æ¶ˆè½¬ç§»åˆ° "%s"` -transfer.no_permission_to_accept=您没有æƒé™æŽ¥å—æ¤è½¬è®©ã€‚ -transfer.no_permission_to_reject=您没有æƒé™æ‹’ç»æ¤è½¬è®©ã€‚ +transfer.reject_desc=å–æ¶ˆè½¬ç§»åˆ°ã€Œ%s〠+transfer.no_permission_to_accept=您没有æƒé™æŽ¥å—æ¤è½¬ç§»ã€‚ +transfer.no_permission_to_reject=您没有æƒé™æ‹’ç»æ¤è½¬ç§»ã€‚ desc.private=ç§æœ‰åº“ desc.public=公开 @@ -1151,9 +1151,9 @@ desc.archived=å·²å˜æ¡£ desc.sha256=SHA256 template.items=模æ¿é€‰é¡¹ -template.git_content=Gitæ•°æ®(默认分支) +template.git_content=Git æ•°æ®ï¼ˆé»˜è®¤åˆ†æ”¯ï¼‰ template.git_hooks=Git é’©å -template.git_hooks_tooltip=ä½ ç›®å‰æ— æ³•ä¿®æ”¹æˆ–åˆ é™¤è¢«æ·»åŠ è¿‡çš„ Git Hookã€‚ä»…å½“ä½ ä¿¡ä»»æ¨¡æ¿ä»“库时æ‰å¯ä»¥é€‰æ‹©æ¤é¡¹ã€‚ +template.git_hooks_tooltip=æ‚¨ç›®å‰æ— æ³•ä¿®æ”¹æˆ–åˆ é™¤è¢«æ·»åŠ è¿‡çš„ Git é’©å。仅当您信任模æ¿ä»“库时æ‰å¯ä»¥é€‰æ‹©æ¤é¡¹ã€‚ template.webhooks=Web é’©å template.topics=主题 template.avatar=å¤´åƒ @@ -1166,9 +1166,9 @@ archive.title_date=该仓库已于 %s 归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶æˆ–克隆它 archive.issue.nocomment=æ¤ä»“åº“å·²å˜æ¡£ï¼Œæ‚¨ä¸èƒ½åœ¨æ¤å·¥å•æ·»åŠ è¯„è®ºã€‚ archive.pull.nocomment=æ¤ä»“åº“å·²å˜æ¡£ï¼Œæ‚¨ä¸èƒ½åœ¨æ¤åˆå¹¶è¯·æ±‚æ·»åŠ è¯„è®ºã€‚ -form.reach_limit_of_creation_1=ä½ å·²ç»è¾¾åˆ°äº† %d 仓库的上é™ã€‚ -form.reach_limit_of_creation_n=ä½ å·²ç»è¾¾åˆ°äº† %d 个仓库的上é™ã€‚ -form.name_reserved=仓库åç§° %s 是被ä¿ç•™çš„。 +form.reach_limit_of_creation_1=您已ç»è¾¾åˆ°äº† %d 仓库的上é™ã€‚ +form.reach_limit_of_creation_n=您已ç»è¾¾åˆ°äº† %d 个仓库的上é™ã€‚ +form.name_reserved=仓库åç§° %s 是ä¿ç•™çš„。 form.name_pattern_not_allowed=仓库åç§°ä¸ä¸å…许使用 %s æ ¼å¼ã€‚ need_auth=æŽˆæƒ @@ -1177,7 +1177,7 @@ migrate_service=è¿ç§»æœåŠ¡ migrate_options_mirror_helper=è¯¥ä»“åº“å°†æ˜¯ä¸€ä¸ªé•œåƒ migrate_options_lfs=è¿ç§» LFS 文件 migrate_options_lfs_endpoint.label=LFS ç½‘å€ -migrate_options_lfs_endpoint.description=è¿ç§»å°†å°è¯•ä½¿ç”¨ä½ çš„ Git remote æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œä½ 还å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚ +migrate_options_lfs_endpoint.description=è¿ç§»å°†å°è¯•使用您的 Git remote æ¥ <a target="_blank" rel="noopener noreferrer" href="%s">确定 LFS æœåС噍</a>。如果仓库 LFS æ•°æ®å˜å‚¨åœ¨å…¶ä»–ä½ç½®ï¼Œæ‚¨è¿˜å¯ä»¥æŒ‡å®šè‡ªå®šä¹‰ç½‘å€ã€‚ migrate_options_lfs_endpoint.description.local=æ”¯æŒæœ¬åœ°æœåŠ¡å™¨è·¯å¾„ã€‚ migrate_options_lfs_endpoint.placeholder=如果留空,网å€å°†ä»Žå…‹éš† URL ä¸å¾—到 migrate_items=è¿ç§»é¡¹ç›® @@ -1187,7 +1187,7 @@ migrate_items_labels=æ ‡ç¾ migrate_items_issues=å·¥å• migrate_items_pullrequests=åˆå¹¶è¯·æ±‚ migrate_items_merge_requests=åˆå¹¶è¯·æ±‚ -migrate_items_releases=版本å‘布 +migrate_items_releases=å‘布 migrate_repo=è¿ç§»ä»“库 migrate.clone_address=从 URL è¿ç§»/克隆 migrate.clone_address_desc=现有仓库的 HTTP(s) 或 Git "clone" URL @@ -1261,14 +1261,14 @@ branch=分支 tree=ç›®å½•æ ‘ clear_ref=`清除当å‰å¼•用` filter_branch_and_tag=è¿‡æ»¤åˆ†æ”¯æˆ–æ ‡ç¾ -find_tag=查找Gitæ ‡ç¾ +find_tag=查找 Git æ ‡ç¾ branches=分支列表 tags=æ ‡ç¾åˆ—表 issues=å·¥å• pulls=åˆå¹¶è¯·æ±‚ projects=项目 packages=软件包 -actions=Actions +actions=å·¥ä½œæµ labels=æ ‡ç¾ org_labels_desc=ç»„ç»‡çº§åˆ«çš„æ ‡ç¾ï¼Œå¯ä»¥è¢«æœ¬ç»„织下的 <strong>所有仓库</strong> 使用 org_labels_desc_manage=ç®¡ç† @@ -1277,9 +1277,9 @@ milestone=里程碑 milestones=里程碑 commits=æäº¤ commit=æäº¤ -release=版本å‘布 -releases=版本å‘布 -tag=Gitæ ‡ç¾ +release=å‘布 +releases=å‘布 +tag=Git æ ‡ç¾ released_this=å‘布 tagged_this=å·²æ ‡è®° file.title=%s ä½äºŽ %s @@ -1339,9 +1339,9 @@ editor.must_be_on_a_branch=您必须在æŸä¸ªåˆ†æ”¯ä¸Šæ‰èƒ½å¯¹æ¤æ–‡ä»¶è¿›è¡Œä¿ editor.fork_before_edit=您必须在派生这个仓库æ‰èƒ½å¯¹æ¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作 editor.delete_this_file=åˆ é™¤æ–‡ä»¶ editor.must_have_write_access=您必须具有写æƒé™æ‰èƒ½å¯¹æ¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作。 -editor.file_delete_success=文件 %s å·²è¢«åˆ é™¤ã€‚ +editor.file_delete_success=文件「%sã€å·²åˆ 除。 editor.name_your_file=命忖‡ä»¶... -editor.filename_help=通过键入åç§°åŽè·Ÿæ–œçº¿ ("/") æ¥æ·»åŠ ç›®å½•ã€‚é€šè¿‡åœ¨è¾“å…¥æ¡†çš„å¼€å¤´é”®å…¥ "é€€æ ¼" æ¥åˆ 除目录。 +editor.filename_help=通过键入åç§°åŽè·Ÿæ–œçº¿ ("/") æ¥æ·»åŠ ç›®å½•ã€‚é€šè¿‡åœ¨è¾“å…¥æ¡†çš„å¼€å¤´é”®å…¥ã€Œé€€æ ¼ã€æ¥åˆ 除目录。 editor.or=或 editor.cancel_lower=å–æ¶ˆ editor.commit_signed_changes=æäº¤å·²ç¾å的更改 @@ -1352,7 +1352,7 @@ editor.update=æ›´æ–° %s editor.delete=åˆ é™¤ %s editor.patch=åº”ç”¨è¡¥ä¸ editor.patching=打补ä¸ï¼š -editor.fail_to_apply_patch=æ— æ³•åº”ç”¨è¡¥ä¸ %s +editor.fail_to_apply_patch=æ— æ³•åº”ç”¨è¡¥ä¸ã€Œ%s〠editor.new_patch=æ–°è¡¥ä¸ editor.commit_message_desc=æ·»åŠ ä¸€ä¸ªå¯é€‰çš„æ‰©å±•æè¿°... editor.signoff_desc=在æäº¤æ—¥å¿—æ¶ˆæ¯æœ«å°¾æ·»åŠ ç¾ç½²äººä¿¡æ¯ã€‚ @@ -1364,42 +1364,42 @@ editor.new_branch_name=为这次æäº¤çš„æ–°åˆ†æ”¯å‘½å editor.new_branch_name_desc=新的分支åç§°... editor.cancel=å–æ¶ˆ editor.filename_cannot_be_empty=文件åä¸èƒ½ä¸ºç©ºã€‚ -editor.filename_is_invalid=文件å %s æ— æ•ˆ +editor.filename_is_invalid=æ–‡ä»¶åæ— 效:「%sã€ã€‚ editor.commit_email=æäº¤é‚®ç®±åœ°å€ editor.invalid_commit_email=æäº¤çš„é‚®ç®±åœ°å€æ— 效。 -editor.branch_does_not_exist=æ¤ä»“库ä¸ä¸å˜åœ¨å为 %s 的分支。 -editor.branch_already_exists=æ¤ä»“库已å˜åœ¨å为 %s 的分支。 -editor.directory_is_a_file=%s å·²ç»ä½œä¸ºæ–‡ä»¶å在æ¤ä»“库ä¸å˜åœ¨ã€‚ -editor.file_is_a_symlink=`"%s" 是一个符å·é“¾æŽ¥ï¼Œæ— 法在 web 编辑器ä¸ç¼–辑` -editor.filename_is_a_directory=æ¤ä»“库ä¸å·²å˜åœ¨å为“%s†的目录。 -editor.file_editing_no_longer_exists=æ£åœ¨ç¼–辑的文件 %s å·²ä¸å˜åœ¨ã€‚ -editor.file_deleting_no_longer_exists=æ£åœ¨åˆ 除的文件 %s å·²ä¸å˜åœ¨ã€‚ +editor.branch_does_not_exist=æ¤ä»“库ä¸ä¸å˜åœ¨å为「%sã€çš„分支。 +editor.branch_already_exists=æ¤ä»“库已å˜åœ¨å为「%sã€çš„分支。 +editor.directory_is_a_file=目录å「%sã€å·²ä½œä¸ºæ–‡ä»¶å在æ¤ä»“库ä¸å˜åœ¨ã€‚ +editor.file_is_a_symlink=`「%sã€æ˜¯ä¸€ä¸ªç¬¦å·é“¾æŽ¥ï¼Œæ— 法在 Web 编辑器ä¸ç¼–辑` +editor.filename_is_a_directory=文件å「%sã€å·²ä½œä¸ºç›®å½•å在æ¤ä»“库ä¸å˜åœ¨ã€‚ +editor.file_editing_no_longer_exists=æ£åœ¨ç¼–辑的文件「%sã€å·²ä¸å˜åœ¨äºŽæ¤ä»“库。 +editor.file_deleting_no_longer_exists=æ£åœ¨åˆ 除的文件「%sã€å·²ä¸å˜åœ¨äºŽæ¤ä»“库。 editor.file_changed_while_editing=文件内容在您进行编辑时已ç»å‘生å˜åŠ¨ã€‚<a target="_blank" rel="noopener noreferrer" href="%s">å•击æ¤å¤„</a> 查看å˜åŠ¨çš„å…·ä½“å†…å®¹ï¼Œæˆ–è€… <strong>冿¬¡æäº¤</strong> 覆盖已å‘生的å˜åŠ¨ã€‚ -editor.file_already_exists=æ¤ä»“库已ç»å˜åœ¨å为 %s 的文件。 -editor.commit_id_not_matching=æäº¤ID与您开始编辑时的IDä¸åŒ¹é…。请æäº¤åˆ°è¡¥ä¸åˆ†æ”¯ç„¶åŽåˆå¹¶ã€‚ +editor.file_already_exists=æ¤ä»“库已ç»å˜åœ¨å为「%sã€çš„æ–‡ä»¶ã€‚ +editor.commit_id_not_matching=æäº¤ ID 与您开始编辑时的 ID ä¸åŒ¹é…。请æäº¤åˆ°è¡¥ä¸åˆ†æ”¯ç„¶åŽåˆå¹¶ã€‚ editor.push_out_of_date=推é€ä¼¼ä¹Žå·²ç»è¿‡æ—¶ã€‚ editor.commit_empty_file_header=æäº¤ä¸€ä¸ªç©ºæ–‡ä»¶ editor.commit_empty_file_text=æ‚¨è¦æäº¤çš„æ–‡ä»¶æ˜¯ç©ºçš„ï¼Œç»§ç»å—? editor.no_changes_to_show=没有å¯ä»¥æ˜¾ç¤ºçš„å˜æ›´ã€‚ -editor.fail_to_update_file=æ›´æ–°/创建文件 %s 失败。 +editor.fail_to_update_file=æ›´æ–°/创建文件「%sã€å¤±è´¥ã€‚ editor.fail_to_update_file_summary=错误信æ¯ï¼š -editor.push_rejected_no_message=æ¤ä¿®æ”¹è¢«æœåŠ¡å™¨æ‹’ç»å¹¶ä¸”没有å馈消æ¯ã€‚请检查 Git Hook。 -editor.push_rejected=æ¤ä¿®æ”¹è¢«æœåŠ¡å™¨æ‹’ç»ã€‚请检查 Git Hook。 +editor.push_rejected_no_message=æ¤ä¿®æ”¹è¢«æœåŠ¡å™¨æ‹’ç»å¹¶ä¸”没有å馈消æ¯ã€‚请检查 Git é’©å。 +editor.push_rejected=æ¤ä¿®æ”¹è¢«æœåŠ¡å™¨æ‹’ç»ã€‚请检查 Git é’©å。 editor.push_rejected_summary=详细拒ç»ä¿¡æ¯ï¼š editor.add_subdir=æ·»åŠ ç›®å½• -editor.unable_to_upload_files=ä¸Šä¼ æ–‡ä»¶è‡³ %s æ—¶å‘生错误:%v -editor.upload_file_is_locked=文件 %s 被 %s é”定。 -editor.upload_files_to_dir=ä¸Šä¼ æ–‡ä»¶è‡³ %s -editor.cannot_commit_to_protected_branch=ä¸å¯ä»¥æäº¤åˆ°å—ä¿æŠ¤çš„åˆ†æ”¯ %s。 +editor.unable_to_upload_files=ä¸Šä¼ æ–‡ä»¶è‡³ã€Œ%sã€å¤±è´¥ï¼š%v +editor.upload_file_is_locked=文件「%sã€è¢« %s é”定。 +editor.upload_files_to_dir=ä¸Šä¼ æ–‡ä»¶è‡³ã€Œ%s〠+editor.cannot_commit_to_protected_branch=ä¸å¯ä»¥æäº¤åˆ°å—ä¿æŠ¤çš„åˆ†æ”¯ã€Œ%sã€ã€‚ editor.no_commit_to_branch=æ— æ³•ç›´æŽ¥æäº¤åˆ†æ”¯ï¼Œå› 为: editor.user_no_push_to_branch=用户ä¸èƒ½æŽ¨é€åˆ°åˆ†æ”¯ editor.require_signed_commit=分支需è¦ç¾åæäº¤ -editor.cherry_pick=Cherry-pick %s 到: +editor.cherry_pick=拣选æäº¤ %s 到: editor.revert=å°† %s 还原到: commits.desc=æµè§ˆä»£ç ä¿®æ”¹åŽ†å² commits.commits=æ¬¡ä»£ç æäº¤ -commits.no_commits=没有共åŒçš„æäº¤ã€‚%s å’Œ %s 的历å²å®Œå…¨ä¸åŒã€‚ +commits.no_commits=没有共åŒçš„æäº¤ã€‚「%sã€å’Œã€Œ%sã€çš„历å²å®Œå…¨ä¸åŒã€‚ commits.nothing_to_compare=这些分支是相åŒçš„。 commits.search.tooltip=`您å¯ä»¥åœ¨å…³é”®è¯å‰åŠ ä¸Šå‰ç¼€ï¼Œå¦‚"author:", "committer:", "after:", 或"before:", 例如 "retrin author:Alice before:2019-01-13"` commits.search_branch=æ¤åˆ†æ”¯ @@ -1419,11 +1419,11 @@ commits.view_file_diff=查看æäº¤ä¸çš„æ–‡ä»¶æ›´æ”¹ commit.operations=æ“作 commit.revert=还原 -commit.revert-header=还原: %s +commit.revert-header=还原:%s commit.revert-content=选择è¦è¿˜åŽŸçš„åˆ†æ”¯ï¼š -commit.cherry-pick=Cherry-pick -commit.cherry-pick-header=Cherry-pick: %s -commit.cherry-pick-content=选择 cherry-pick çš„ç›®æ ‡åˆ†æ”¯ï¼š +commit.cherry-pick=拣选æäº¤ +commit.cherry-pick-header=拣选æäº¤ï¼š%s +commit.cherry-pick-content=é€‰æ‹©è¦æ‹£é€‰æäº¤çš„ç›®æ ‡åˆ†æ”¯ï¼š commitstatus.error=错误 commitstatus.failure=失败 @@ -1440,14 +1440,14 @@ projects.create=创建项目 projects.title=æ ‡é¢˜ projects.new=创建项目 projects.new_subheader=在一个地方åè°ƒã€è·Ÿè¸ªå’Œæ›´æ–°æ‚¨çš„å·¥ä½œï¼Œè®©é¡¹ç›®ä¿æŒé€æ˜Žå¹¶æŒ‰è®¡åˆ’进行。 -projects.create_success=项目 %s 创建æˆåŠŸã€‚ +projects.create_success=项目「%sã€åˆ›å»ºæˆåŠŸã€‚ projects.deletion=åˆ é™¤é¡¹ç›® projects.deletion_desc=åˆ é™¤é¡¹ç›®ä¼šä»Žæ‰€æœ‰ç›¸å…³çš„å·¥å•ä¸ç§»é™¤å®ƒã€‚是å¦ç»§ç»ï¼Ÿ -projects.deletion_success=è¯¥é¡¹ç›®å·²è¢«åˆ é™¤ã€‚ +projects.deletion_success=è¯¥é¡¹ç›®å·²åˆ é™¤ã€‚ projects.edit=编辑项目 projects.edit_subheader=项目用于组织工å•和跟踪进展情况。 projects.modify=更新项目 -projects.edit_success=项目 %s æ›´æ–°æˆåŠŸã€‚ +projects.edit_success=项目「%sã€æ›´æ–°æˆåŠŸã€‚ projects.type.none=æ— projects.type.basic_kanban=åŸºç¡€çœ‹æ¿ projects.type.bug_triage=Bugåˆ†ç±»çœ‹æ¿ @@ -1461,7 +1461,7 @@ projects.column.new=创建列 projects.column.set_default=设为默认 projects.column.set_default_desc=设置æ¤åˆ—为未分类问题和åˆå¹¶è¯·æ±‚的默认值 projects.column.delete=åˆ é™¤åˆ— -projects.column.deletion_desc=åˆ é™¤é¡¹ç›®åˆ—ä¼šå°†æ‰€æœ‰ç›¸å…³é—®é¢˜ç§»åˆ°â€œæœªåˆ†ç±»â€ã€‚是å¦ç»§ç»ï¼Ÿ +projects.column.deletion_desc=åˆ é™¤é¡¹ç›®åˆ—ä¼šå°†æ‰€æœ‰ç›¸å…³é—®é¢˜ç§»è‡³é»˜è®¤åˆ—ã€‚æ˜¯å¦ç»§ç»ï¼Ÿ projects.column.color=颜色 projects.open=å¼€å¯ projects.close=å…³é— @@ -1513,12 +1513,12 @@ issues.new_label_placeholder=æ ‡ç¾åç§° issues.new_label_desc_placeholder=æè¿° issues.create_label=åˆ›å»ºæ ‡ç¾ issues.label_templates.title=åŠ è½½é¢„å®šä¹‰çš„æ ‡ç¾æ¨¡æ¿ -issues.label_templates.info=è¿˜æ²¡æœ‰ä»»ä½•æ ‡ç¾ã€‚您å¯ä»¥ä½¿ç”¨'åˆ›å»ºæ ‡ç¾'æŒ‰é’®æˆ–è€…åŠ è½½é¢„å®šä¹‰çš„æ ‡ç¾é›†åˆ›å»ºæ ‡ç¾ +issues.label_templates.info=è¿˜æ²¡æœ‰ä»»ä½•æ ‡ç¾ã€‚您å¯ä»¥ä½¿ç”¨ã€Œåˆ›å»ºæ ‡ç¾ã€æŒ‰é’®æˆ–è€…åŠ è½½é¢„å®šä¹‰çš„æ ‡ç¾é›†åˆ›å»ºæ ‡ç¾ï¼š issues.label_templates.helper=é€‰æ‹©æ ‡ç¾æ¨¡æ¿ issues.label_templates.use=ä½¿ç”¨æ ‡ç¾é›† -issues.label_templates.fail_to_load_file=åŠ è½½æ ‡ç¾æ¨¡æ¿æ–‡ä»¶ %s æ—¶å‘生错误:%v +issues.label_templates.fail_to_load_file=åŠ è½½æ ‡ç¾æ¨¡æ¿æ–‡ä»¶ã€Œ%sã€æ—¶å‘生错误:%v issues.add_label=于 %[2]s æ·»åŠ äº†æ ‡ç¾ %[1]s -issues.add_labels=于 %s æ·»åŠ %s æ ‡ç¾ +issues.add_labels=于 %[2]s æ·»åŠ äº†æ ‡ç¾ %[1]s issues.remove_label=于 %[2]s åˆ é™¤äº†æ ‡ç¾ %[1]s issues.remove_labels=于 %[2]s åˆ é™¤äº†æ ‡ç¾ %[1]s issues.add_remove_labels=于 %[3]s æ·»åŠ äº†æ ‡ç¾ %[1]s ï¼Œåˆ é™¤äº†æ ‡ç¾ %[2]s @@ -1532,7 +1532,7 @@ issues.remove_project_at=`于 %[2]s å°†æ¤å·¥å•从项目 <b>%[1]s</b> ä¸åˆ é™ issues.deleted_milestone=ï¼ˆå·²åˆ é™¤ï¼‰ issues.deleted_project=`(å·²åˆ é™¤)` issues.self_assign_at=`于 %s 指派给自己` -issues.add_assignee_at=`于 %[2]s 被 <b>%[1]s</b> 指派` +issues.add_assignee_at=`于 %[2]s ç”± <b>%[1]s</b> 指派` issues.remove_assignee_at=`<b>%s</b> å–æ¶ˆäº†æŒ‡æ´¾åœ¨ %s` issues.remove_self_assignment=`于 %s å–æ¶ˆäº†æŒ‡æ´¾` issues.change_title_at=`于 %[3]s ä¿®æ”¹æ ‡é¢˜ <b><strike>%[1]s</strike></b> 为 <b>%[2]s</b>` @@ -1589,11 +1589,11 @@ issues.action_assignee_no_select=未指派 issues.action_check=选ä¸/å–æ¶ˆé€‰ä¸ issues.action_check_all=选ä¸/å–æ¶ˆé€‰ä¸æ‰€æœ‰é¡¹ç›® issues.opened_by=ç”± <a href="%[2]s">%[3]s</a> 于 %[1]s创建 -pulls.merged_by=ç”± <a href="%[2]s">%[3]s</a> 创建,被åˆå¹¶äºŽ %[1]s -pulls.merged_by_fake=ç”± %[2]s 创建,被åˆå¹¶äºŽ %[1]s -issues.closed_by=ç”± <a href="%[2]s">%[3]s</a> 创建,被关é—于 %[1]s +pulls.merged_by=ç”± <a href="%[2]s">%[3]s</a> 创建,åˆå¹¶äºŽ %[1]s +pulls.merged_by_fake=ç”± %[2]s 创建,åˆå¹¶äºŽ %[1]s +issues.closed_by=ç”± <a href="%[2]s">%[3]s</a> 创建,关é—于 %[1]s issues.opened_by_fake=ç”± %[2]s 于 %[1]s 打开 -issues.closed_by_fake=ç”± %[2]s 创建,被关é—于 %[1]s +issues.closed_by_fake=ç”± %[2]s 创建,关é—于 %[1]s issues.previous=上一页 issues.next=下一页 issues.open_title=å¼€å¯ä¸ @@ -1652,6 +1652,7 @@ issues.save=ä¿å˜ issues.label_title=æ ‡ç¾åç§° issues.label_description=æ ‡ç¾æè¿° issues.label_color=æ ‡ç¾é¢œè‰² +issues.label_color_invalid=æ— æ•ˆçš„é¢œè‰² issues.label_exclusive=äº’æ–¥æ ‡ç¾ issues.label_archive=å½’æ¡£æ ‡ç¾ issues.label_archived_filter=æ˜¾ç¤ºå˜æ¡£æ ‡ç¾ @@ -1667,23 +1668,23 @@ issues.label_delete=åˆ é™¤ issues.label_modify=ç¼–è¾‘æ ‡ç¾ issues.label_deletion=åˆ é™¤æ ‡ç¾ issues.label_deletion_desc=åˆ é™¤æ ‡ç¾ä¼šå°†å…¶ä»Žæ‰€æœ‰é—®é¢˜ä¸åˆ 除。继ç»ï¼Ÿ -issues.label_deletion_success=è¯¥æ ‡ç¾å·²è¢«åˆ 除。 +issues.label_deletion_success=è¯¥æ ‡ç¾å·²åˆ 除。 issues.label.filter_sort.alphabetically=æŒ‰å—æ¯é¡ºåºæŽ’åº issues.label.filter_sort.reverse_alphabetically=æŒ‰å—æ¯é€†åºæŽ’åº issues.label.filter_sort.by_size=最å°å°ºå¯¸ issues.label.filter_sort.reverse_by_size=最大尺寸 issues.num_participants=%d åå‚与者 -issues.attachment.open_tab=`åœ¨æ–°çš„æ ‡ç¾é¡µä¸æŸ¥çœ‹ '%s'` -issues.attachment.download=`点击下载 '%s'` +issues.attachment.open_tab=`åœ¨æ–°çš„æ ‡ç¾é¡µä¸æŸ¥çœ‹ã€Œ%sã€` +issues.attachment.download=`点击下载「%sã€` issues.subscribe=订阅 issues.unsubscribe=å–æ¶ˆè®¢é˜… issues.unpin=å–æ¶ˆç½®é¡¶ issues.max_pinned=您ä¸èƒ½ç½®é¡¶æ›´å¤šå·¥å• -issues.pin_comment=于 %s 被置顶 +issues.pin_comment=于 %s 置顶 issues.unpin_comment=于 %s å–æ¶ˆç½®é¡¶ issues.lock=é”å®šå¯¹è¯ issues.unlock=è§£é”å¯¹è¯ -issues.lock_duplicate=一个工å•ä¸èƒ½è¢«é”定两次。 +issues.lock_duplicate=一个工å•ä¸èƒ½é”定两次。 issues.unlock_error=æ— æ³•è§£é”一个未é”定的工å•。 issues.lock_with_reason=å› ä¸º <strong>%s</strong> 而é”定,并将对è¯é™åˆ¶ä¸ºå作者 %s issues.lock_no_reason=é”定并é™åˆ¶ä»…å作者 %s @@ -1710,13 +1711,13 @@ issues.timetracker_timer_discard=åˆ é™¤è®¡æ—¶å™¨ issues.timetracker_timer_manually_add=æ·»åŠ æ—¶é—´ issues.time_estimate_set=设置预计时间 -issues.time_estimate_display=预计: %s +issues.time_estimate_display=预估:%s issues.change_time_estimate_at=预估时间已修改为 <b>%[1]s</b> %[2]s -issues.remove_time_estimate_at=åˆ é™¤é¢„è®¡æ—¶é—´ %s +issues.remove_time_estimate_at=åˆ é™¤é¢„ä¼°æ—¶é—´ %s issues.time_estimate_invalid=é¢„è®¡æ—¶é—´æ ¼å¼æ— 效 issues.start_tracking_history=`开始工作 %s` issues.tracker_auto_close=当æ¤å·¥å•关闿—¶ï¼Œè‡ªåŠ¨åœæ¢è®¡æ—¶å™¨ -issues.tracking_already_started=`ä½ å·²ç»å¼€å§‹å¯¹ <a href="%s">å¦ä¸€ä¸ªå·¥å•</a> 进行时间跟踪ï¼` +issues.tracking_already_started=`您已ç»å¼€å§‹å¯¹ <a href="%s">å¦ä¸€ä¸ªå·¥å•</a> 进行时间跟踪ï¼` issues.stop_tracking=åœæ¢è®¡æ—¶å™¨ issues.stop_tracking_history=工作 <b>%[1]s</b> 于 %[2]s åœæ¢ issues.cancel_tracking=å–æ¶ˆ @@ -1732,7 +1733,7 @@ issues.time_spent_total=总用时 issues.time_spent_from_all_authors=`总花费时间:%s` issues.due_date=到期时间 -issues.invalid_due_date_format=åˆ°æœŸæ—¶é—´çš„æ ¼å¼å¿…须是 'yyyy-mm-dd' 的形å¼ã€‚ +issues.invalid_due_date_format=åˆ°æœŸæ—¶é—´çš„æ ¼å¼å¿…须是「yyyy-mm-ddã€çš„å½¢å¼ã€‚ issues.error_modifying_due_date=修改到期时间失败。 issues.error_removing_due_date=åˆ é™¤åˆ°æœŸæ—¶é—´å¤±è´¥ã€‚ issues.push_commit_1=于 %[2]s 推é€äº† %[1]d 个æäº¤ @@ -1775,7 +1776,7 @@ issues.dependency.remove_header=åˆ é™¤ä¾èµ–项 issues.dependency.issue_remove_text=æ¤æ“作将从工å•ä¸åˆ 除ä¾èµ–。是å¦è¦ç»§ç»? issues.dependency.pr_remove_text=æ¤æ“作将从åˆå¹¶è¯·æ±‚ä¸åˆ 除ä¾èµ–。是å¦è¦ç»§ç»? issues.dependency.setting=为工å•å’Œåˆå¹¶è¯·æ±‚å¯ç”¨ä¾èµ– -issues.dependency.add_error_same_issue=ä½ ä¸èƒ½è®©ä¸€ä¸ªå·¥å•ä¾èµ–于自己。 +issues.dependency.add_error_same_issue=您ä¸èƒ½è®©ä¸€ä¸ªå·¥å•ä¾èµ–于自身。 issues.dependency.add_error_dep_issue_not_exist=ä¾èµ–项ä¸å˜åœ¨ã€‚ issues.dependency.add_error_dep_not_exist=ä¾èµ–项ä¸å˜åœ¨ã€‚ issues.dependency.add_error_dep_exists=ä¾èµ–项已å˜åœ¨ã€‚ @@ -1817,9 +1818,9 @@ issues.review.stale=批准åŽå·²æ›´æ–° issues.review.unofficial=éžå®˜æ–¹å®¡æ‰¹æ•° issues.assignee.error=å› ä¸ºæœªçŸ¥åŽŸå› ï¼Œå¹¶éžæ‰€æœ‰çš„æŒ‡æ´¾éƒ½æˆåŠŸã€‚ issues.reference_issue.body=内容 -issues.content_history.deleted=åˆ é™¤äºŽ -issues.content_history.edited=最åŽç¼–辑于 -issues.content_history.created=创建于 +issues.content_history.deleted=å·²åˆ é™¤ +issues.content_history.edited=已编辑 +issues.content_history.created=已创建 issues.content_history.delete_from_history=从历å²è®°å½•ä¸åˆ 除 issues.content_history.delete_from_history_confirm=从历å²è®°å½•ä¸åˆ 除å—? issues.content_history.options=选项 @@ -1873,7 +1874,7 @@ pulls.merged=å·²åˆå¹¶ pulls.merged_success=åˆå¹¶è¯·æ±‚å·²æˆåŠŸåˆå¹¶å’Œå…³é— pulls.closed=åˆå¹¶è¯·æ±‚å·²å…³é— pulls.manually_merged=已手动åˆå¹¶ -pulls.merged_info_text=分支 %s 现在å¯ä»¥è¢«åˆ 除了。 +pulls.merged_info_text=分支 %s 现在å¯ä»¥åˆ 除了。 pulls.is_closed=åˆå¹¶è¯·æ±‚å·²ç»å…³é—。 pulls.title_wip_desc=`<a href="#">æ ‡é¢˜ä»¥ <strong>%s</strong> 开头</a>以å…åˆå¹¶è¯·æ±‚æ„外åˆå¹¶ã€‚` pulls.cannot_merge_work_in_progress=æ¤åˆå¹¶è¯·æ±‚è¢«æ ‡è®°ä¸ºæ£åœ¨è¿›è¡Œçš„工作。 @@ -1908,7 +1909,7 @@ pulls.waiting_count_1=%d 个æ£åœ¨ç‰å¾…å®¡æ ¸ pulls.waiting_count_n=%d 个æ£åœ¨ç‰å¾…å®¡æ ¸ pulls.wrong_commit_id=æäº¤ id å¿…é¡»åœ¨ç›®æ ‡åˆ†æ”¯ 上 -pulls.no_merge_desc=由于未å¯ç”¨åˆå¹¶é€‰é¡¹ï¼Œæ¤åˆå¹¶è¯·æ±‚æ— æ³•è¢«åˆå¹¶ã€‚ +pulls.no_merge_desc=由于未å¯ç”¨åˆå¹¶é€‰é¡¹ï¼Œæ¤åˆå¹¶è¯·æ±‚æ— æ³•åˆå¹¶ã€‚ pulls.no_merge_helper=在仓库设置ä¸å¯ç”¨åˆå¹¶é€‰é¡¹æˆ–者手工åˆå¹¶è¯·æ±‚。 pulls.no_merge_wip=这个åˆå¹¶è¯·æ±‚æ— æ³•åˆå¹¶ï¼Œå› ä¸ºè¢«æ ‡è®°ä¸ºå°šæœªå®Œæˆçš„工作。 pulls.no_merge_not_ready=æ¤åˆå¹¶è¯·æ±‚尚未准备好åˆå¹¶ï¼Œè¯·æ£€æŸ¥å®¡æ ¸çжæ€å’ŒçŠ¶æ€æ£€æŸ¥ã€‚ @@ -1922,7 +1923,7 @@ pulls.merge_manually=手动åˆå¹¶ pulls.merge_commit_id=åˆå¹¶æäº¤ ID pulls.require_signed_wont_sign=分支需è¦ç¾åçš„æäº¤ï¼Œä½†è¿™ä¸ªåˆå¹¶å°†ä¸ä¼šè¢«ç¾å -pulls.invalid_merge_option=ä½ å¯ä»¥åœ¨æ¤åˆå¹¶è¯·æ±‚ä¸ä½¿ç”¨åˆå¹¶é€‰é¡¹ã€‚ +pulls.invalid_merge_option=您å¯ä»¥åœ¨æ¤åˆå¹¶è¯·æ±‚ä¸ä½¿ç”¨åˆå¹¶é€‰é¡¹ã€‚ pulls.merge_conflict=åˆå¹¶å¤±è´¥ï¼šåˆå¹¶æ—¶æœ‰å†²çªå‘生。æç¤ºï¼šé‡‡ç”¨å…¶å®ƒåˆå¹¶ç–ç•¥ pulls.merge_conflict_summary=é”™è¯¯ä¿¡æ¯ pulls.rebase_conflict=åˆå¹¶å¤±è´¥ï¼šå˜åŸºæäº¤æœ‰å†²çªï¼š%[1]s。æç¤ºï¼šé‡‡ç”¨å…¶å®ƒåˆå¹¶ç–ç•¥ @@ -1930,7 +1931,7 @@ pulls.rebase_conflict_summary=é”™è¯¯ä¿¡æ¯ pulls.unrelated_histories=åˆå¹¶å¤±è´¥ï¼šä¸¤ä¸ªåˆ†æ”¯æ²¡æœ‰å…±åŒåކå²ã€‚æç¤ºï¼šå°è¯•ä¸åŒçš„ç–ç•¥ pulls.merge_out_of_date=åˆå¹¶å¤±è´¥ï¼šåœ¨ç”Ÿæˆåˆå¹¶æ—¶ï¼Œä¸»åˆ†æ”¯å·²æ›´æ–°ã€‚æç¤ºï¼šå†è¯•一次。 pulls.head_out_of_date=åˆå¹¶å¤±è´¥ï¼šåœ¨ç”Ÿæˆåˆå¹¶æ—¶ï¼Œhead 已更新。æç¤ºï¼šå†è¯•一次。 -pulls.has_merged=失败:åˆå¹¶è¯·æ±‚å·²ç»è¢«åˆå¹¶ï¼Œæ‚¨ä¸èƒ½å†æ¬¡åˆå¹¶æˆ–æ›´æ”¹ç›®æ ‡åˆ†æ”¯ã€‚ +pulls.has_merged=失败:åˆå¹¶è¯·æ±‚å·²ç»åˆå¹¶ï¼Œæ‚¨ä¸èƒ½å†æ¬¡åˆå¹¶æˆ–æ›´æ”¹ç›®æ ‡åˆ†æ”¯ã€‚ pulls.push_rejected=推é€å¤±è´¥ï¼šæŽ¨é€è¢«æ‹’ç»ã€‚审查æ¤ä»“库的 Git é’©å。 pulls.push_rejected_summary=详细拒ç»ä¿¡æ¯ pulls.push_rejected_no_message=推é€å¤±è´¥ï¼šæ¤æŽ¨é€è¢«æ‹’ç»ä½†æœªæä¾›å…¶ä»–ä¿¡æ¯ã€‚请检查æ¤ä»“库的 Git é’©å。 @@ -1954,12 +1955,12 @@ pulls.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闿¤åˆå¹¶è¯·æ±‚ pulls.reopened_at=`釿–°æ‰“å¼€æ¤åˆå¹¶è¯·æ±‚ <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.cmd_instruction_hint=查看命令行æç¤º pulls.cmd_instruction_checkout_title=检出 -pulls.cmd_instruction_checkout_desc=ä»Žä½ çš„ä»“åº“ä¸æ£€å‡ºä¸€ä¸ªæ–°çš„åˆ†æ”¯å¹¶æµ‹è¯•å˜æ›´ã€‚ +pulls.cmd_instruction_checkout_desc=ä»Žæ‚¨çš„ä»“åº“ä¸æ£€å‡ºä¸€ä¸ªæ–°çš„åˆ†æ”¯å¹¶æµ‹è¯•å˜æ›´ã€‚ pulls.cmd_instruction_merge_title=åˆå¹¶ pulls.cmd_instruction_merge_desc=åˆå¹¶å˜æ›´å¹¶æ›´æ–°åˆ° Gitea 上 -pulls.cmd_instruction_merge_warning=è¦å‘Šï¼šæ¤æ“作ä¸èƒ½åˆå¹¶è¯¥åˆå¹¶è¯·æ±‚ï¼Œå› ä¸ºâ€œè‡ªåŠ¨æ£€æµ‹æ‰‹åŠ¨åˆå¹¶â€æœªå¯ç”¨ +pulls.cmd_instruction_merge_warning=è¦å‘Šï¼šæ¤æ“作ä¸èƒ½åˆå¹¶è¯¥åˆå¹¶è¯·æ±‚ï¼Œå› ä¸ºã€Œè‡ªåŠ¨æ£€æµ‹æ‰‹åŠ¨åˆå¹¶ã€æœªå¯ç”¨ pulls.clear_merge_message=清除åˆå¹¶ä¿¡æ¯ -pulls.clear_merge_message_hint=清除åˆå¹¶æ¶ˆæ¯åªä¼šåˆ 除æäº¤æ¶ˆæ¯å†…容,并ä¿ç•™ç”Ÿæˆçš„ git é™„åŠ å†…å®¹ï¼Œå¦‚â€œCo-Authored-By …â€ã€‚ +pulls.clear_merge_message_hint=清除åˆå¹¶æ¶ˆæ¯åªä¼šåˆ 除æäº¤æ¶ˆæ¯å†…容,并ä¿ç•™ç”Ÿæˆçš„ Git é™„åŠ å†…å®¹ï¼Œå¦‚ã€ŒCo-Authored-By…ã€ã€‚ pulls.auto_merge_button_when_succeed=(当检查æˆåŠŸæ—¶) pulls.auto_merge_when_succeed=在所有检查æˆåŠŸåŽè‡ªåЍåˆå¹¶ @@ -1974,7 +1975,7 @@ pulls.auto_merge_newly_scheduled_comment=`已于 %[1]s 设置æ¤åˆå¹¶è¯·æ±‚在æ pulls.auto_merge_canceled_schedule_comment=`已于 %[1]s å–æ¶ˆäº†è‡ªåЍåˆå¹¶è®¾ç½® ` pulls.delete.title=åˆ é™¤æ¤åˆå¹¶è¯·æ±‚? -pulls.delete.text=ä½ çœŸçš„è¦åˆ 除这个åˆå¹¶è¯·æ±‚å—? (è¿™å°†æ°¸ä¹…åˆ é™¤æ‰€æœ‰å†…å®¹ã€‚å¦‚æžœä½ æ‰“ç®—å°†å†…å®¹å˜æ¡£ï¼Œè¯·è€ƒè™‘å…³é—它) +pulls.delete.text=您真的è¦åˆ 除这个åˆå¹¶è¯·æ±‚å—ï¼Ÿï¼ˆè¿™å°†æ°¸ä¹…åˆ é™¤æ‰€æœ‰å†…å®¹ã€‚å¦‚æžœæ‚¨æ‰“ç®—å°†å†…å®¹å˜æ¡£ï¼Œè¯·è€ƒè™‘å…³é—它) pulls.recently_pushed_new_branches=您已ç»äºŽ%[2]s推é€äº†åˆ†æ”¯ <strong>%[1]s</strong> pulls.upstream_diverging_prompt_behind_1=该分支è½åŽäºŽ %[2]s %[1]d 个æäº¤ @@ -1983,7 +1984,7 @@ pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改 pulls.upstream_diverging_merge=åŒæ¥æ´¾ç”Ÿ pulls.upstream_diverging_merge_confirm=è¦å°† %[1]s åˆå¹¶åˆ° %[2]s å—? -pull.deleted_branch=(å·²åˆ é™¤): %s +pull.deleted_branch=(å·²åˆ é™¤):%s pull.agit_documentation=查看有关 AGit 的文档 comments.edit.already_changed=æ— æ³•ä¿å˜å¯¹è¯„论的更改。其内容似乎已被其他用户更改。 请刷新页é¢å¹¶é‡æ–°ç¼–辑以é¿å…覆盖他们的更改 @@ -2001,16 +2002,16 @@ milestones.title=æ ‡é¢˜ milestones.desc=æè¿° milestones.due_date=æˆªæ¢æ—¥æœŸï¼ˆå¯é€‰ï¼‰ milestones.clear=清除 -milestones.invalid_due_date_format=åˆ°æœŸæ—¶é—´çš„æ ¼å¼å¿…须是 'yyyy-mm-dd' 的形å¼ã€‚ -milestones.create_success=里程碑 %s 创建æˆåŠŸã€‚ +milestones.invalid_due_date_format=åˆ°æœŸæ—¶é—´çš„æ ¼å¼å¿…须是「yyyy-mm-ddã€çš„å½¢å¼ã€‚ +milestones.create_success=里程碑「%sã€åˆ›å»ºæˆåŠŸã€‚ milestones.edit=编辑里程碑 milestones.edit_subheader=里程碑组织工å•,åˆå¹¶è¯·æ±‚和跟踪进度。 milestones.cancel=å–æ¶ˆ milestones.modify=更新里程碑 -milestones.edit_success=里程碑 %s å·²ç»æ›´æ–°ã€‚ +milestones.edit_success=里程碑「%sã€æ›´æ–°æˆåŠŸã€‚ milestones.deletion=åˆ é™¤é‡Œç¨‹ç¢‘ milestones.deletion_desc=åˆ é™¤è¯¥é‡Œç¨‹ç¢‘å°†ä¼šç§»é™¤æ‰€æœ‰å·¥å•ä¸ç›¸å…³çš„ä¿¡æ¯ã€‚是å¦ç»§ç»ï¼Ÿ -milestones.deletion_success=é‡Œç¨‹ç¢‘å·²è¢«åˆ é™¤ã€‚ +milestones.deletion_success=é‡Œç¨‹ç¢‘å·²åˆ é™¤ã€‚ milestones.filter_sort.name=åç§° milestones.filter_sort.earliest_due_data=到期日从远到近 milestones.filter_sort.latest_due_date=到期日从近到远 @@ -2019,7 +2020,7 @@ milestones.filter_sort.most_complete=完æˆåº¦ä»Žé«˜åˆ°ä½Ž milestones.filter_sort.most_issues=å·¥å•从多到少 milestones.filter_sort.least_issues=å·¥å•从少到多 -signing.will_sign=这个æäº¤å°†ç”¨å¯†é’¥ "%s" ç¾å。 +signing.will_sign=这个æäº¤å°†ç”¨å¯†é’¥ã€Œ%sã€ç¾å。 signing.wont_sign.error=在检查æäº¤æ˜¯å¦å¯ä»¥è¢«ç¾å时出错。 signing.wont_sign.nokey=没有å¯ç”¨çš„密钥æ¥ç¾ç½²è¿™ä¸ªæäº¤ã€‚ signing.wont_sign.never=æäº¤ä»Žæœªç¾å。 @@ -2034,11 +2035,11 @@ signing.wont_sign.approved=åˆå¹¶å°†ä¸ä¼šè¢«ç¾åï¼Œå› ä¸ºåˆå¹¶è¯·æ±‚æœªè¢«æ‰ signing.wont_sign.not_signed_in=您还没有登录。 ext_wiki=访问外部百科 -ext_wiki.desc=链接到外部 wiki。 +ext_wiki.desc=链接到外部百科。 wiki=百科 -wiki.welcome=欢迎æ¥åˆ°ç™¾ç§‘! -wiki.welcome_desc=百科å…è®¸ä½ æ’°å†™å’Œä¸Žå作者分享文档 +wiki.welcome=欢迎æ¥åˆ°ç™¾ç§‘。 +wiki.welcome_desc=百科å…许您撰写和与å作者分享文档。 wiki.desc=撰写和与å作者分享文档 wiki.create_first_page=åˆ›å»ºç¬¬ä¸€ä¸ªé¡µé¢ wiki.page=é¡µé¢ @@ -2055,15 +2056,15 @@ wiki.file_revision=页é¢åŽ†å² wiki.wiki_page_revisions=页é¢åŽ†å² wiki.back_to_wiki=返回百科 wiki.delete_page_button=åˆ é™¤é¡µé¢ -wiki.delete_page_notice_1=ç™¾ç§‘é¡µé¢ %s åˆ é™¤åŽæ— 法æ¢å¤ï¼Œæ˜¯å¦ç»§ç»ï¼Ÿ +wiki.delete_page_notice_1=百科页é¢ã€Œ%sã€åˆ é™¤åŽæ— 法æ¢å¤ï¼Œæ˜¯å¦ç»§ç»ï¼Ÿ wiki.page_already_exists=相åŒåç§°çš„ Wiki 页é¢å·²ç»å˜åœ¨ã€‚ -wiki.reserved_page=百科页é¢åç§° %s 是被ä¿ç•™çš„。 +wiki.reserved_page=百科页é¢å称「%sã€æ˜¯ä¿ç•™çš„。 wiki.pages=æ‰€æœ‰é¡µé¢ wiki.last_updated=æœ€åŽæ›´æ–°äºŽ %s -wiki.page_name_desc=è¾“å…¥æ¤ Wiki 页é¢çš„å称。特殊å称有:'Home', '_Sidebar' å’Œ '_Footer'。 +wiki.page_name_desc=输入æ¤ç™¾ç§‘页é¢çš„å称。特殊å称有:「Homeã€ï¼Œã€Œ_Sidebarã€å’Œã€Œ_Footerã€ã€‚ wiki.original_git_entry_tooltip=查看原始的 Git æ–‡ä»¶è€Œä¸æ˜¯ä½¿ç”¨å‹å¥½é“¾æŽ¥ã€‚ -activity=åŠ¨æ€ +activity=活动 activity.navbar.pulse=活动 activity.navbar.code_frequency=代ç 频率 activity.navbar.contributors=贡献者 @@ -2077,27 +2078,27 @@ activity.period.quarterly=3个月 activity.period.semiyearly=6 个月 activity.period.yearly=1å¹´ activity.overview=概览 -activity.active_prs_count_1=<strong>%d</strong> åˆå¹¶è¯·æ±‚ -activity.active_prs_count_n=<strong>%d</strong> åˆå¹¶è¯·æ±‚ +activity.active_prs_count_1=<strong>%d</strong> 个åˆå¹¶è¯·æ±‚ +activity.active_prs_count_n=<strong>%d</strong> 个åˆå¹¶è¯·æ±‚ activity.merged_prs_count_1=åˆå¹¶è¯·æ±‚ activity.merged_prs_count_n=åˆå¹¶è¯·æ±‚ activity.opened_prs_count_1=æ–°åˆå¹¶è¯·æ±‚ activity.opened_prs_count_n=æ–°åˆå¹¶è¯·æ±‚ -activity.title.user_1=%d 用户 -activity.title.user_n=%d 用户 -activity.title.prs_1=%d åˆå¹¶è¯·æ±‚ -activity.title.prs_n=%d åˆå¹¶è¯·æ±‚ +activity.title.user_1=%d ä½ç”¨æˆ· +activity.title.user_n=%d ä½ç”¨æˆ· +activity.title.prs_1=%d 个åˆå¹¶è¯·æ±‚ +activity.title.prs_n=%d 个åˆå¹¶è¯·æ±‚ activity.title.prs_merged_by=%[2]s ç”± %[1]s åˆå¹¶ activity.title.prs_opened_by=%[2]s 创建了 %[1]s activity.merged_prs_label=å·²åˆå¹¶ activity.opened_prs_label=已创建 -activity.active_issues_count_1=<strong>%d</strong> å·¥å• -activity.active_issues_count_n=<strong>%d</strong> å·¥å• +activity.active_issues_count_1=<strong>%d</strong> å¼ å·¥å• +activity.active_issues_count_n=<strong>%d</strong> å¼ å·¥å• activity.closed_issues_count_1=已关é—çš„å·¥å• activity.closed_issues_count_n=已关é—çš„å·¥å• -activity.title.issues_1=%d å·¥å• -activity.title.issues_n=%d å·¥å• -activity.title.issues_closed_from=%s 从 %s è¢«å…³é— +activity.title.issues_1=%d å¼ å·¥å• +activity.title.issues_n=%d å¼ å·¥å• +activity.title.issues_closed_from=%s 从 %s å…³é— activity.title.issues_created_by=%[2]s 创建了 %[1]s activity.closed_issue_label=å·²å…³é— activity.new_issues_count_1=åˆ›å»ºå·¥å• @@ -2107,23 +2108,23 @@ activity.title.unresolved_conv_1=%d æœªè§£å†³çš„ä¼šè¯ activity.title.unresolved_conv_n=%d æœªè§£å†³çš„ä¼šè¯ activity.unresolved_conv_desc=这些最近更新的工å•å’Œåˆå¹¶è¯·æ±‚还没有解决。 activity.unresolved_conv_label=打开 -activity.title.releases_1=%d 版本å‘布 -activity.title.releases_n=%d 版本å‘布 +activity.title.releases_1=%d 个å‘布 +activity.title.releases_n=%d 个å‘布 activity.title.releases_published_by=%[2]s å‘布了 %[1]s activity.published_release_label=å·²å‘布 activity.no_git_activity=åœ¨æ¤æœŸé—´æ²¡æœ‰ä»»ä½•æäº¤æ´»åŠ¨ã€‚ activity.git_stats_exclude_merges=排除åˆå¹¶ï¼Œ -activity.git_stats_author_1=%d 作者 -activity.git_stats_author_n=%d 作者 +activity.git_stats_author_1=%d ä½ä½œè€… +activity.git_stats_author_n=%d ä½ä½œè€… activity.git_stats_pushed_1=å·²ç»æŽ¨é€ activity.git_stats_pushed_n=å·²ç»æŽ¨é€ -activity.git_stats_commit_1=%d æäº¤ -activity.git_stats_commit_n=%d æäº¤ +activity.git_stats_commit_1=%d 次æäº¤ +activity.git_stats_commit_n=%d 次æäº¤ activity.git_stats_push_to_branch=到 %s å’Œ activity.git_stats_push_to_all_branches=到所有分支。 activity.git_stats_on_default_branch=在 %s 上, -activity.git_stats_file_1=%d 文件 -activity.git_stats_file_n=%d 文件 +activity.git_stats_file_1=%d 个文件 +activity.git_stats_file_n=%d 个文件 activity.git_stats_files_changed_1=å·²ç»æ”¹å˜ activity.git_stats_files_changed_n=å·²ç»æ”¹å˜ activity.git_stats_additions=而且 @@ -2135,15 +2136,15 @@ activity.git_stats_deletion_n=åˆ é™¤ %d 行 contributors.contribution_type.filter_label=贡献类型: contributors.contribution_type.commits=æäº¤ -contributors.contribution_type.additions=更多 +contributors.contribution_type.additions=新增 contributors.contribution_type.deletions=åˆ é™¤ settings=设置 -settings.desc=è®¾ç½®æ˜¯ä½ å¯ä»¥ç®¡ç†ä»“库设置的地方 +settings.desc=设置是您å¯ä»¥ç®¡ç†ä»“库设置的地方 settings.options=仓库 settings.public_access=公开访问 -settings.public_access_desc=é…置公共访客访问æƒé™ä»¥è¦†ç›–æ¤å˜å‚¨åº“的默认值。 -settings.public_access.docs.not_set=未设置:没有é¢å¤–的公共访问æƒé™ã€‚访客æƒé™éµå¾ªå˜å‚¨åº“çš„å¯è§æ€§å’Œæˆå‘˜æƒé™ã€‚ +settings.public_access_desc=é…置公共访客访问æƒé™ä»¥è¦†ç›–æ¤ä»“库的默认值。 +settings.public_access.docs.not_set=未设置:没有é¢å¤–的公共访问æƒé™ã€‚访客æƒé™éµå¾ªä»“库的å¯è§æ€§å’Œæˆå‘˜æƒé™ã€‚ settings.public_access.docs.anonymous_read=匿åå¯è¯»ï¼šæœªç™»å½•的用户å¯ä»¥é€šè¿‡è¯»å–æƒé™è®¿é—®å•元。 settings.public_access.docs.everyone_read=所有人å¯è¯»ï¼šæ‰€æœ‰ç™»å½•用户都å¯ä»¥é€šè¿‡è¯»å–æƒé™è®¿é—®å•元。读å–问题/拉å–请求å•元的æƒé™ä¹Ÿæ„味ç€ç”¨æˆ·å¯ä»¥åˆ›å»ºæ–°çš„问题/拉å–请求。 settings.public_access.docs.everyone_write=所有人å¯å†™ï¼šæ‰€æœ‰ç™»å½•用户都有写入æƒé™ã€‚åªæœ‰ç™¾ç§‘æ”¯æŒæ¤æƒé™ã€‚ @@ -2160,14 +2161,14 @@ settings.mirror_settings=镜åƒè®¾ç½® settings.mirror_settings.docs=è®¾ç½®æ‚¨çš„ä»“åº“ä»¥è‡ªåŠ¨åŒæ¥å¦ä¸€ä¸ªä»“库的æäº¤ã€æ ‡ç¾å’Œåˆ†æ”¯ã€‚ settings.mirror_settings.docs.disabled_pull_mirror.instructions=设置您的项目以自动将æäº¤ã€æ ‡ç¾å’Œåˆ†æ”¯æŽ¨é€åˆ°å¦ä¸€ä¸ªä»“库。您的站点管ç†å‘˜å·²ç¦ç”¨äº†æ‹‰å–镜åƒã€‚ settings.mirror_settings.docs.disabled_push_mirror.instructions=å°†æ‚¨çš„é¡¹ç›®è®¾ç½®ä¸ºè‡ªåŠ¨ä»Žä¸€ä¸ªä»“åº“æ‹‰å–æäº¤ã€æ ‡ç¾å’Œåˆ†æ”¯ã€‚ -settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=现在,这åªèƒ½åœ¨â€œè¿ç§»å¤–部仓库â€èœå•ä¸å®Œæˆã€‚欲了解更多信æ¯ï¼Œè¯·å’¨è¯¢ï¼š +settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=现在,这åªèƒ½åœ¨ã€Œè¿ç§»å¤–部仓库ã€èœå•ä¸å®Œæˆã€‚欲了解更多信æ¯ï¼Œè¯·å‚考: settings.mirror_settings.docs.disabled_push_mirror.info=您的站点管ç†å‘˜å·²ç¦ç”¨æŽ¨é€é•œåƒã€‚ settings.mirror_settings.docs.no_new_mirrors=您的仓库将镜åƒåŒæ¥å¦ä¸€ä¸ªä»“库的更改。请注æ„,您现在ä¸èƒ½åˆ›å»ºä»»ä½•新的镜åƒã€‚ settings.mirror_settings.docs.can_still_use=虽然您ä¸èƒ½ä¿®æ”¹çŽ°æœ‰é•œåƒæˆ–创建新镜åƒï¼Œä½†æ‚¨ä»ç„¶å¯ä»¥ä½¿ç”¨å·²å˜åœ¨çš„镜åƒã€‚ settings.mirror_settings.docs.pull_mirror_instructions=è¦åˆ›å»ºä¸€ä¸ªæ‹‰å–镜åƒï¼Œè¯·å‚阅: settings.mirror_settings.docs.more_information_if_disabled=您å¯ä»¥åœ¨è¿™é‡Œæ‰¾åˆ°æ›´å¤šå…³äºŽæŽ¨é€å’Œæ‹‰å–镜åƒçš„ä¿¡æ¯ï¼š settings.mirror_settings.docs.doc_link_title=如何镜åƒä»“库? -settings.mirror_settings.docs.doc_link_pull_section=文档ä¸çš„ “从远程仓库拉å–†部分。 +settings.mirror_settings.docs.doc_link_pull_section=文档ä¸çš„「从远程仓库拉å–ã€éƒ¨åˆ†ã€‚ settings.mirror_settings.docs.pulling_remote_title=从远程仓库拉å–代ç settings.mirror_settings.mirrored_repository=镜åƒåº“ settings.mirror_settings.pushed_repository=推é€ä»“库 @@ -2195,15 +2196,15 @@ settings.use_internal_wiki=使用内置百科 settings.default_wiki_branch_name=默认百科分支åç§° settings.failed_to_change_default_wiki_branch=更改百科默认分支失败。 settings.use_external_wiki=使用外部百科 -settings.external_wiki_url=外部 Wiki 链接 +settings.external_wiki_url=外部百科链接 settings.external_wiki_url_error=å¤–éƒ¨ç™¾ç§‘é“¾æŽ¥æ— æ•ˆ -settings.external_wiki_url_desc=å½“ç‚¹å‡»ç™¾ç§‘æ ‡ç¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部百科系统的URL。 +settings.external_wiki_url_desc=å½“ç‚¹å‡»ç™¾ç§‘æ ‡ç¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部百科系统的 URL。 settings.issues_desc=å¯ç”¨å·¥å•系统 -settings.use_internal_issue_tracker=使用内置的轻é‡çº§å·¥å•管ç†ç³»ç»Ÿ -settings.use_external_issue_tracker=使用外部的工å•管ç†ç³»ç»Ÿ +settings.use_internal_issue_tracker=使用内置工å•系统 +settings.use_external_issue_tracker=使用外部工å•系统 settings.external_tracker_url=外部工å•系统 URL settings.external_tracker_url_error=å¤–éƒ¨ç™¾ç§‘é“¾æŽ¥æ— æ•ˆ -settings.external_tracker_url_desc=å½“ç‚¹å‡»å·¥å•æ ‡ç¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部工å•系统的URL。 +settings.external_tracker_url_desc=å½“ç‚¹å‡»å·¥å•æ ‡ç¾æ—¶ï¼Œè®¿é—®è€…将被é‡å®šå‘到外部工å•系统的 URL。 settings.tracker_url_format=外部工å•管ç†ç³»ç»Ÿçš„ URL æ ¼å¼ settings.tracker_url_format_error=外部工å•é“¾æŽ¥æ— æ•ˆ settings.tracker_issue_style=外部工å•管ç†ç³»ç»Ÿçš„ç¼–å·æ ¼å¼ @@ -2221,14 +2222,14 @@ settings.pulls.enable_autodetect_manual_merge=å¯ç”¨è‡ªåŠ¨æ£€æµ‹æ‰‹åŠ¨åˆå¹¶ (æ³ settings.pulls.allow_rebase_update=å…许通过å˜åŸºæ›´æ–°åˆå¹¶è¯·æ±‚分支 settings.pulls.default_delete_branch_after_merge=默认åˆå¹¶åŽåˆ 除åˆå¹¶è¯·æ±‚分支 settings.pulls.default_allow_edits_from_maintainers=默认开å¯å…许维护者编辑 -settings.releases_desc=å¯ç”¨å‘布 +settings.releases_desc=å¯ç”¨ä»“库å‘布 settings.packages_desc=å¯ç”¨ä»“库软件包注册ä¸å¿ƒ settings.projects_desc=å¯ç”¨é¡¹ç›® settings.projects_mode_desc=é¡¹ç›®æ¨¡å¼ (è¦æ˜¾ç¤ºçš„项目类型) settings.projects_mode_repo=仅仓库项目 settings.projects_mode_owner=ä»…é™ç”¨æˆ·æˆ–组织项目 settings.projects_mode_all=所有项目 -settings.actions_desc=å¯ç”¨ Actions +settings.actions_desc=å¯ç”¨ä»“åº“å·¥ä½œæµ settings.admin_settings=管ç†å‘˜è®¾ç½® settings.admin_enable_health_check=å¯ç”¨ä»“库å¥åº·æ£€æŸ¥ (git fsck) settings.admin_code_indexer=代ç 索引器 @@ -2251,23 +2252,23 @@ settings.convert_fork_notices_1=该æ“作会将派生仓库转æ¢ä¸ºæ™®é€šä»“库 settings.convert_fork_confirm=转æ¢ä»“库 settings.convert_fork_succeed=æ¤æ´¾ç”Ÿä»“库已ç»è½¬æ¢ä¸ºæ™®é€šä»“库。 settings.transfer=è½¬ç§»ä»“åº“æ‰€æœ‰æƒ -settings.transfer.rejected=代ç 库转移被拒ç»ã€‚ -settings.transfer.success=代ç 库转移æˆåŠŸã€‚ -settings.transfer.blocked_user=æ— æ³•ä¼ è¾“ä»“åº“ï¼Œå› ä¸ºæ‚¨è¢«æ–°çš„æ‰€æœ‰è€…å±è”½ã€‚ +settings.transfer.rejected=仓库转移被拒ç»ã€‚ +settings.transfer.success=仓库转移æˆåŠŸã€‚ +settings.transfer.blocked_user=æ— æ³•è½¬ç§»ä»“åº“ï¼Œå› ä¸ºæ‚¨å·²è¢«æ–°æ‰€æœ‰è€…å±è”½ã€‚ settings.transfer_abort=å–æ¶ˆè½¬ç§» -settings.transfer_abort_invalid=ä½ ä¸èƒ½å–消ä¸å˜åœ¨çš„代ç 库转移。 -settings.transfer_abort_success=æˆåŠŸå–æ¶ˆäº†å°†ä»£ç 库转让给 %s。 +settings.transfer_abort_invalid=您ä¸èƒ½å–消ä¸å˜åœ¨çš„仓库转移。 +settings.transfer_abort_success=æˆåŠŸå–æ¶ˆå°†ä»“库转移给 %s。 settings.transfer_desc=您å¯ä»¥å°†ä»“库转移至您拥有管ç†å‘˜æƒé™çš„叿ˆ·æˆ–组织。 settings.transfer_form_title=输入仓库å称以åšç¡®è®¤: -settings.transfer_in_progress=当剿£åœ¨è¿›è¡Œè½¬è®©ã€‚ å¦‚æžœä½ æƒ³å°†æ¤ä»£ç 库转让给å¦ä¸€ä¸ªç”¨æˆ·ï¼Œè¯·å–消它。 -settings.transfer_notices_1=- 如果将æ¤ä»“库转移给其他用户, 您将失去对æ¤ä»“库的访问æƒé™ã€‚ -settings.transfer_notices_2=-如果将其转移到您 (å…±åŒ) 拥有的组织,您å¯ä»¥ç»§ç»è®¿é—®è¯¥ä»“库。 -settings.transfer_notices_3=- å¦‚æžœä»“åº“æ˜¯ç§æœ‰çš„并且被转移给æŸä¸ªç”¨æˆ·ï¼Œé‚£ä¹ˆæ¤æ“作å¯ä»¥ç¡®ä¿è¯¥ç”¨æˆ·è‡³å°‘具有读æƒé™(以åŠå¿…è¦æ—¶çš„æ›´æ”¹æƒé™)。 -settings.transfer_notices_4=- 如果å˜å‚¨åº“属于æŸä¸ªç»„织,而您将其转移给å¦ä¸€ä¸ªç»„织或个人,那么您将失去å˜å‚¨åº“å·¥å•与其组织项目系统之间的链接。 +settings.transfer_in_progress=当剿£åœ¨è¿›è¡Œè½¬ç§»ã€‚ 如果您想将æ¤ä»“库转移给å¦ä¸€ä¸ªç”¨æˆ·ï¼Œè¯·å–消它。 +settings.transfer_notices_1=- 如果将æ¤ä»“库转移给其他用户,您将失去对æ¤ä»“库的访问æƒé™ã€‚ +settings.transfer_notices_2=- 如果将其转移到您(共åŒï¼‰æ‹¥æœ‰çš„组织,您å¯ä»¥ç»§ç»è®¿é—®è¯¥ä»“库。 +settings.transfer_notices_3=- å¦‚æžœä»“åº“æ˜¯ç§æœ‰çš„并且被转移给æŸä¸ªç”¨æˆ·ï¼Œé‚£ä¹ˆæ¤æ“作å¯ä»¥ç¡®ä¿è¯¥ç”¨æˆ·è‡³å°‘具有读æƒé™ï¼ˆä»¥åŠå¿…è¦æ—¶çš„æ›´æ”¹æƒé™ï¼‰ã€‚ +settings.transfer_notices_4=- 如果仓库属于æŸä¸ªç»„织,而您将其转移给å¦ä¸€ä¸ªç»„织或个人,那么您将失去仓库工å•与其组织项目系统之间的链接。 settings.transfer_owner=新拥有者 -settings.transfer_perform=执行转让 -settings.transfer_started=该代ç åº“å·²è¢«æ ‡è®°ä¸ºè½¬è®©å¹¶ç‰å¾…æ¥è‡ª %s 的确认 -settings.transfer_succeed=仓库已被转移。 +settings.transfer_perform=执行转移 +settings.transfer_started=è¯¥ä»“åº“å·²æ ‡è®°ä¸ºè½¬ç§»å¹¶ç‰å¾…æ¥è‡ª %s 的确认 +settings.transfer_succeed=仓库已转移。 settings.signing_settings=ç¾å验è¯è®¾ç½® settings.trust_model=ç¾å信任模型 settings.trust_model.default=默认信任模型 @@ -2277,7 +2278,7 @@ settings.trust_model.collaborator.long=å作者:信任å作者的ç¾å settings.trust_model.collaborator.desc=æ¤ä»“库ä¸å作者的有效ç¾åå°†è¢«æ ‡è®°ä¸ºã€Œå¯ä¿¡ã€(æ— è®ºå®ƒä»¬æ˜¯å¦æ˜¯æäº¤è€…),ç¾ååªç¬¦åˆæäº¤è€…æ—¶å°†æ ‡è®°ä¸ºã€Œä¸å¯ä¿¡ã€ï¼Œéƒ½ä¸åŒ¹é…æ—¶æ ‡è®°ä¸ºã€Œä¸åŒ¹é…ã€ã€‚ settings.trust_model.committer=æäº¤è€… settings.trust_model.committer.long=æäº¤è€…: 信任与æäº¤è€…相符的ç¾å (æ¤ç‰¹æ€§ç±»ä¼¼ GitHub,这会强制采用 Gitea 作为æäº¤è€…å’Œç¾å者) -settings.trust_model.committer.desc=有效ç¾ååªæœ‰å’Œæäº¤è€…ç›¸åŒ¹é…æ‰ä¼šè¢«æ ‡è®°ä¸ºâ€œå—ä¿¡ä»»â€ï¼Œå¦åˆ™å®ƒä»¬å°†è¢«æ ‡è®°ä¸ºâ€œä¸åŒ¹é…â€ã€‚这强制 Gitea æˆä¸ºç¾åæäº¤çš„æäº¤è€…,而实际æäº¤è€…è¢«åŠ ä¸Š Co-authored-by: å’Œ Co-committed-by: çš„æ ‡è®°ã€‚ 默认的 Gitea å¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“ä¸çš„一å用户。 +settings.trust_model.committer.desc=有效ç¾ååªæœ‰å’Œæäº¤è€…ç›¸åŒ¹é…æ‰ä¼šè¢«æ ‡è®°ä¸ºã€Œå—ä¿¡ä»»ã€ï¼Œå¦åˆ™å®ƒä»¬å°†è¢«æ ‡è®°ä¸ºã€Œä¸åŒ¹é…ã€ã€‚这强制 Gitea æˆä¸ºç¾åæäº¤çš„æäº¤è€…,而实际æäº¤è€…è¢«åŠ ä¸Š Co-authored-by: å’Œ Co-committed-by: çš„æ ‡è®°ã€‚ 默认的 Gitea å¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“ä¸çš„一å用户。 settings.trust_model.collaboratorcommitter=å作者+æäº¤è€… settings.trust_model.collaboratorcommitter.long=å作者+æäº¤è€…:信任åä½œè€…åŒæ—¶æ˜¯æäº¤è€…çš„ç¾å settings.trust_model.collaboratorcommitter.desc=æ¤ä»“库ä¸å作者的有效ç¾ååœ¨ä»–åŒæ—¶æ˜¯æäº¤è€…æ—¶å°†è¢«æ ‡è®°ä¸ºã€Œå¯ä¿¡ã€ï¼Œç¾ååªåŒ¹é…了æäº¤è€…æ—¶å°†æ ‡è®°ä¸ºã€Œä¸å¯ä¿¡ã€ï¼Œéƒ½ä¸åŒ¹é…æ—¶æ ‡è®°ä¸ºã€Œä¸åŒ¹é…ã€ã€‚这会强制 Gitea æˆä¸ºç¾å者和æäº¤è€…,实际的æäº¤è€…å°†è¢«æ ‡è®°äºŽæäº¤æ¶ˆæ¯ç»“尾处的「Co-Authored-By:ã€å’Œã€ŒCo-Committed-By:ã€ã€‚默认的 Gitea ç¾åå¯†é’¥å¿…é¡»åŒ¹é…æ•°æ®åº“ä¸çš„一个用户密钥。 @@ -2291,16 +2292,16 @@ settings.delete_desc=åˆ é™¤ä»“åº“æ˜¯æ°¸ä¹…æ€§çš„, æ— æ³•æ’¤æ¶ˆã€‚ settings.delete_notices_1=- æ¤æ“作 <strong>ä¸å¯ä»¥</strong> 被回滚。 settings.delete_notices_2=- æ¤æ“ä½œå°†æ°¸ä¹…åˆ é™¤ä»“åº“ <strong>%s</strong>,包括 Git æ•°æ®ã€ å·¥å•ã€è¯„论ã€ç™¾ç§‘å’Œå作者的æ“作æƒé™ã€‚ settings.delete_notices_fork_1=- 在æ¤ä»“åº“åˆ é™¤åŽï¼Œå®ƒçš„æ´¾ç”Ÿä»“åº“å°†å˜æˆç‹¬ç«‹ä»“库。 -settings.deletion_success=ä»“åº“å·²è¢«åˆ é™¤ã€‚ +settings.deletion_success=ä»“åº“å·²åˆ é™¤ã€‚ settings.update_settings_success=仓库设置已更新。 -settings.update_settings_no_unit=该代ç 库应该至少å…许æŸç§å½¢å¼çš„交互。 +settings.update_settings_no_unit=该仓库应至少å…许æŸç§å½¢å¼çš„交互。 settings.confirm_delete=åˆ é™¤æœ¬ä»“åº“ settings.add_collaborator=å¢žåŠ å作者 settings.add_collaborator_success=åä½œè€…æ·»åŠ æˆåŠŸï¼ settings.add_collaborator_inactive_user=æ— æ³•æ·»åŠ æœªæ¿€æ´»çš„ç”¨æˆ·ä½œä¸ºåˆä½œè€…。 settings.add_collaborator_owner=ä¸èƒ½å°†æ‰€æœ‰è€…æ·»åŠ ä¸ºå作者。 -settings.add_collaborator_duplicate=åˆä½œè€…å·²ç»è¢«æ·»åŠ åˆ°æœ¬ä»“åº“ã€‚ -settings.add_collaborator.blocked_user=æ¤å†™ä½œè€…被仓库所有者å±è”½ï¼Œå之亦然。 +settings.add_collaborator_duplicate=åä½œè€…å·²ç»æ·»åŠ åˆ°æœ¬ä»“åº“ã€‚ +settings.add_collaborator.blocked_user=æ¤å作者被仓库所有者å±è”½ï¼Œå之亦然。 settings.delete_collaborator=åˆ é™¤ settings.collaborator_deletion=åˆ é™¤å作者 settings.collaborator_deletion_desc=åˆ é™¤å作者åŽä»–å°†æ— æ³•å†å¯¹æ¤ä»“库的访问。继ç»ï¼Ÿ @@ -2314,25 +2315,25 @@ settings.add_team_duplicate=å›¢é˜Ÿå·²ç»æ‹¥æœ‰ä»“库 settings.add_team_success=团队现在å¯ä»¥è®¿é—®ä»“库。 settings.change_team_permission_tip=团队æƒé™è®¾ç½®äºŽå›¢é˜Ÿè®¾ç½®é¡µé¢ï¼Œä¸èƒ½æ ¹æ®ä»“库更改 settings.delete_team_tip=è¯¥å›¢é˜Ÿä»æœ‰ä»“库, æ— æ³•åˆ é™¤ -settings.remove_team_success=团队访问仓库的æƒé™å·²è¢«åˆ 除。 +settings.remove_team_success=团队访问仓库的æƒé™å·²åˆ 除。 settings.add_webhook=æ·»åŠ Web é’©å -settings.add_webhook.invalid_channel_name=Webhook 通é“åç§°ä¸èƒ½ä¸ºç©ºä¸”ä¸èƒ½ä»…包å«ä¸€ä¸ª # å—符。 -settings.hooks_desc=当Gitea事件å‘生时,Webé’©å自动å‘出HTTP POST请求。在 <a target="_blank" rel="noopener noreferrer" href="%s"> 指å—</a> ä¸é˜…读更多内容。 +settings.add_webhook.invalid_channel_name=Web é’©å通é“åç§°ä¸èƒ½ä¸ºç©ºä¸”ä¸èƒ½ä»…包å«ä¸€ä¸ª # å—符。 +settings.hooks_desc=当 Gitea 事件å‘生时,Web é’©å自动å‘出 HTTP POST 请求。在 <a target="_blank" rel="noopener noreferrer" href="%s"> 指å—</a> ä¸é˜…读更多内容。 settings.webhook_deletion=åˆ é™¤ Web é’©å -settings.webhook_deletion_desc=åˆ é™¤ webé’©å å°†åˆ é™¤å…¶è®¾ç½®å’ŒåŽ†å²è®°å½•。继ç»ï¼Ÿ +settings.webhook_deletion_desc=åˆ é™¤ Web é’©åå°†åˆ é™¤å…¶è®¾ç½®å’ŒåŽ†å²è®°å½•。继ç»ï¼Ÿ settings.webhook_deletion_success=Web é’©ååˆ é™¤æˆåŠŸï¼ settings.webhook.test_delivery=æµ‹è¯•æŽ¨é€ -settings.webhook.test_delivery_desc=用å‡äº‹ä»¶æµ‹è¯•这个 webé’©å。 -settings.webhook.test_delivery_desc_disabled=è¦ç”¨ 虚å‡äº‹ä»¶ 测试这个Webhook,请激活它。 +settings.webhook.test_delivery_desc=用å‡äº‹ä»¶æµ‹è¯•这个 Web é’©å。 +settings.webhook.test_delivery_desc_disabled=è¦ç”¨å‡äº‹ä»¶æµ‹è¯•这个 Webé’©å,请激活它。 settings.webhook.request=请求内容 settings.webhook.response=å“应内容 settings.webhook.headers=å¤´ä¿¡æ¯ settings.webhook.payload=内容 settings.webhook.body=å“应体 -settings.webhook.replay.description=釿”¾æ¤ webhook。 -settings.webhook.replay.description_disabled=è‹¥è¦é‡æ’æ¤ WebHook,请激活它。 -settings.webhook.delivery.success=ä¸€ä¸ªäº‹ä»¶å·²è¢«æ·»åŠ åˆ°æŽ¨é€é˜Ÿåˆ—。å¯èƒ½éœ€è¦è¿‡å‡ ç§’é’Ÿæ‰ä¼šæ˜¾ç¤ºåœ¨æŽ¨é€è®°å½•ä¸ã€‚ -settings.githooks_desc=Git Hook 是 Git 本身æä¾›çš„功能。您å¯ä»¥åœ¨ä¸‹æ–¹ç¼–辑 hook 文件以设置自定义æ“作。 +settings.webhook.replay.description=釿”¾æ¤ Web é’©å。 +settings.webhook.replay.description_disabled=è‹¥è¦é‡æ–°è¿è¡Œæ¤ Web é’©å,请激活它。 +settings.webhook.delivery.success=ä¸€ä¸ªäº‹ä»¶å·²æ·»åŠ åˆ°æŽ¨é€é˜Ÿåˆ—。å¯èƒ½éœ€è¦è¿‡å‡ ç§’é’Ÿæ‰ä¼šæ˜¾ç¤ºåœ¨æŽ¨é€è®°å½•ä¸ã€‚ +settings.githooks_desc=Git é’©åæ˜¯ Git 本身æä¾›çš„功能。您å¯ä»¥åœ¨ä¸‹æ–¹ç¼–辑 hook 文件以设置自定义æ“作。 settings.githook_edit_desc=å¦‚æžœé’©åæœªå¯åŠ¨ï¼Œåˆ™ä¼šæ˜¾ç¤ºæ ·ä¾‹æ–‡ä»¶ä¸çš„内容。如果想è¦åˆ 除æŸä¸ªé’©å,则æäº¤ç©ºç™½æ–‡æœ¬å³å¯ã€‚ settings.githook_name=é’©ååç§° settings.githook_content=é’©åæ–‡æœ¬ @@ -2340,8 +2341,8 @@ settings.update_githook=æ›´æ–°é’©å设置 settings.add_webhook_desc=Gitea å°†å‘ç›®æ ‡ URL å‘é€å…·æœ‰æŒ‡å®šå†…容类型的 <code>POST</code> 请求。在 <a target="_blank" rel="noopener noreferrer" href="%s">webhooks 指å—</a> ä¸é˜…读更多内容。 settings.payload_url=ç›®æ ‡ URL settings.http_method=HTTP 方法 -settings.content_type=POST Content Type -settings.secret=密钥文本 +settings.content_type=POST 内容类型 +settings.secret=密钥 settings.slack_username=æœåŠ¡åç§° settings.slack_icon_url=å›¾æ ‡ URL settings.slack_color=颜色 @@ -2357,12 +2358,12 @@ settings.event_create_desc=åˆ›å»ºåˆ†æ”¯æˆ–æ ‡ç¾ settings.event_delete=刪除 settings.event_delete_desc=åˆ†æ”¯æˆ–æ ‡ç¾å·²åˆ 除。 settings.event_fork=派生 -settings.event_fork_desc=仓库被派生。 +settings.event_fork_desc=仓库已派生。 settings.event_wiki=百科 settings.event_wiki_desc=创建ã€é‡å‘½åã€ç¼–è¾‘æˆ–åˆ é™¤äº†ç™¾ç§‘é¡µé¢ã€‚ settings.event_statuses=çŠ¶æ€ settings.event_statuses_desc=已从 API æ›´æ–°æäº¤çжæ€ã€‚ -settings.event_release=版本å‘布 +settings.event_release=å‘布 settings.event_release_desc=å‘å¸ƒã€æ›´æ–°æˆ–åˆ é™¤ç‰ˆæœ¬æ—¶ã€‚ settings.event_push=æŽ¨é€ settings.event_force_push=å¼ºåˆ¶æŽ¨é€ @@ -2373,43 +2374,43 @@ settings.event_header_issue=å·¥å•事件 settings.event_issues=å·¥å• settings.event_issues_desc=å·¥å•已打开ã€å·²å…³é—ã€å·²é‡æ–°æ‰“开或已编辑。 settings.event_issue_assign=å·¥å•已指派 -settings.event_issue_assign_desc=å·¥å•å·²è¢«æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚ +settings.event_issue_assign_desc=å·¥å•å·²æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚ settings.event_issue_label=å·²æ ‡è®°å·¥å• -settings.event_issue_label_desc=工啿 ‡ç¾è¢«æ›´æ–°æˆ–清除。 -settings.event_issue_milestone=å·¥å•è¢«æ”¶å…¥é‡Œç¨‹ç¢‘ä¸ -settings.event_issue_milestone_desc=å·¥å•è¢«æ”¶å…¥æˆ–å–æ¶ˆæ”¶å…¥é‡Œç¨‹ç¢‘ä¸ã€‚ +settings.event_issue_label_desc=工啿 ‡ç¾å·²æ›´æ–°æˆ–清除。 +settings.event_issue_milestone=å·¥å•å·²æ”¶å…¥é‡Œç¨‹ç¢‘ä¸ +settings.event_issue_milestone_desc=å·¥å•å·²æ”¶å…¥æˆ–å–æ¶ˆæ”¶å…¥é‡Œç¨‹ç¢‘ä¸ã€‚ settings.event_issue_comment=å·¥å•评论 -settings.event_issue_comment_desc=å·¥å•评论被创建ã€ç¼–è¾‘æˆ–åˆ é™¤ +settings.event_issue_comment_desc=å·¥å•评论已创建ã€ç¼–è¾‘æˆ–åˆ é™¤ã€‚ settings.event_header_pull_request=åˆå¹¶è¯·æ±‚事件 settings.event_pull_request=åˆå¹¶è¯·æ±‚ -settings.event_pull_request_desc=åˆå¹¶è¯·æ±‚被打开ã€è¢«å…³é—ã€è¢«é‡æ–°æ‰“开或被编辑。 -settings.event_pull_request_assign=åˆå¹¶è¯·æ±‚被指派 -settings.event_pull_request_assign_desc=åˆå¹¶è¯·æ±‚è¢«æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚ -settings.event_pull_request_label=åˆå¹¶è¯·æ±‚è¢«è´´ä¸Šæ ‡ç¾ -settings.event_pull_request_label_desc=åˆå¹¶è¯·æ±‚çš„æ ‡ç¾è¢«æ›´æ–°æˆ–清除。 -settings.event_pull_request_milestone=åˆå¹¶è¯·æ±‚è¢«è®°å½•äºŽé‡Œç¨‹ç¢‘ä¸ -settings.event_pull_request_milestone_desc=åˆå¹¶è¯·æ±‚è¢«è®°å½•æˆ–å–æ¶ˆè®°å½•于里程碑ä¸ã€‚ -settings.event_pull_request_comment=åˆå¹¶è¯·æ±‚被评论 -settings.event_pull_request_comment_desc=åˆå¹¶è¯·æ±‚评论被创建ã€ç¼–è¾‘æˆ–åˆ é™¤ã€‚ +settings.event_pull_request_desc=åˆå¹¶è¯·æ±‚已打开ã€å…³é—ã€é‡æ–°æ‰“开或编辑。 +settings.event_pull_request_assign=åˆå¹¶è¯·æ±‚已指派 +settings.event_pull_request_assign_desc=åˆå¹¶è¯·æ±‚å·²æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚ +settings.event_pull_request_label=åˆå¹¶è¯·æ±‚å·²è´´ä¸Šæ ‡ç¾ +settings.event_pull_request_label_desc=åˆå¹¶è¯·æ±‚çš„æ ‡ç¾å·²æ›´æ–°æˆ–清除。 +settings.event_pull_request_milestone=åˆå¹¶è¯·æ±‚å·²è®°å½•äºŽé‡Œç¨‹ç¢‘ä¸ +settings.event_pull_request_milestone_desc=åˆå¹¶è¯·æ±‚å·²è®°å½•æˆ–å–æ¶ˆè®°å½•于里程碑ä¸ã€‚ +settings.event_pull_request_comment=åˆå¹¶è¯·æ±‚已评论 +settings.event_pull_request_comment_desc=åˆå¹¶è¯·æ±‚评论已创建ã€ç¼–è¾‘æˆ–åˆ é™¤ã€‚ settings.event_pull_request_review=å·²å®¡æ ¸çš„åˆå¹¶è¯·æ±‚ -settings.event_pull_request_review_desc=åˆå¹¶è¯·æ±‚è¢«æ‰¹å‡†ã€æ‹’ç»æˆ–æå‡ºå®¡æŸ¥æ„è§ -settings.event_pull_request_sync=åˆå¹¶è¯·æ±‚è¢«åŒæ¥ -settings.event_pull_request_sync_desc=åˆå¹¶è¯·æ±‚è¢«åŒæ¥ã€‚ +settings.event_pull_request_review_desc=åˆå¹¶è¯·æ±‚å·²æ‰¹å‡†ã€æ‹’ç»æˆ–æå‡ºå®¡æŸ¥æ„è§ã€‚ +settings.event_pull_request_sync=åˆå¹¶è¯·æ±‚å·²åŒæ¥ +settings.event_pull_request_sync_desc=åˆå¹¶è¯·æ±‚å·²åŒæ¥ã€‚ settings.event_pull_request_review_request=å‘èµ·åˆå¹¶è¯·æ±‚评审 settings.event_pull_request_review_request_desc=åˆå¹¶è¯·æ±‚è¯„å®¡å·²è¯·æ±‚æˆ–å·²å–æ¶ˆ settings.event_pull_request_approvals=åˆå¹¶è¯·æ±‚批准 settings.event_pull_request_merge=åˆå¹¶è¯·æ±‚åˆå¹¶ settings.event_header_workflow=工作æµç¨‹äº‹ä»¶ settings.event_workflow_job=工作æµä»»åŠ¡ -settings.event_workflow_job_desc=Gitea Actions 工作æµé˜Ÿåˆ—ä¸ã€ç‰å¾…ä¸ã€æ£åœ¨è¿›è¡Œæˆ–已完æˆä»»åŠ¡ã€‚ +settings.event_workflow_job_desc=Gitea 工作æµé˜Ÿåˆ—ä¸ã€ç‰å¾…ä¸ã€æ£åœ¨è¿›è¡Œæˆ–已完æˆçš„任务。 settings.event_package=软件包 -settings.event_package_desc=软件包已在仓库ä¸è¢«åˆ›å»ºæˆ–åˆ é™¤ã€‚ +settings.event_package_desc=软件包在仓库ä¸å·²åˆ›å»ºæˆ–åˆ é™¤ã€‚ settings.branch_filter=分支过滤 settings.branch_filter_desc=推é€ã€åˆ›å»ºï¼Œåˆ 除分支事件的分支白åå•,使用 glob 模å¼åŒ¹é…指定。若为空或 <code>*</code>ï¼Œåˆ™å°†æŠ¥å‘Šæ‰€æœ‰åˆ†æ”¯çš„äº‹ä»¶ã€‚è¯æ³•æ–‡æ¡£è§ <a href="%[1]s">%[2]s</a>。示例:<code>master</code>,<code>{master,release*}</code>。 settings.authorization_header=æŽˆæƒæ ‡å¤´ settings.authorization_header_desc=当å˜åœ¨æ—¶å°†è¢«ä½œä¸ºæŽˆæƒæ ‡å¤´åŒ…å«åœ¨å†…。例如: %s。 settings.active=激活 -settings.active_helper=触å‘事件的信æ¯å°†å‘é€åˆ°æ¤ webhook 网å€ã€‚ +settings.active_helper=触å‘事件的信æ¯å°†å‘é€åˆ°æ¤ Web é’©å URL。 settings.add_hook_success=Web é’©åæ·»åŠ æˆåŠŸï¼ settings.update_webhook=æ›´æ–° Web é’©å settings.update_hook_success=Web é’©åæ›´æ–°æˆåŠŸï¼ @@ -2419,7 +2420,7 @@ settings.hook_type=é’©å类型 settings.slack_token=令牌 settings.slack_domain=域å settings.slack_channel=é¢‘é“ -settings.add_web_hook_desc=å°† <a target="_blank" rel="noreferrer" href="%s">%s</a>集æˆåˆ°æ‚¨çš„代ç 库。 +settings.add_web_hook_desc=å°† <a target="_blank" rel="noreferrer" href="%s">%s</a> 集æˆåˆ°æ‚¨çš„仓库。 settings.web_hook_name_gitea=Gitea settings.web_hook_name_gogs=Gogs settings.web_hook_name_slack=Slack @@ -2446,7 +2447,7 @@ settings.title=æ ‡é¢˜ settings.deploy_key_content=密钥文本 settings.key_been_used=具有相åŒå†…容的部署密钥已在使用ä¸ã€‚ settings.key_name_used=使用相åŒå称的部署密钥已ç»å˜åœ¨ï¼ -settings.add_key_success=部署密钥 %s æ·»åŠ æˆåŠŸã€‚ +settings.add_key_success=部署密钥「%sã€æ·»åŠ æˆåŠŸã€‚ settings.deploy_key_deletion=åˆ é™¤éƒ¨ç½²å¯†é’¥ settings.deploy_key_deletion_desc=åˆ é™¤éƒ¨ç½²å¯†é’¥å°†å–æ¶ˆæ¤å¯†é’¥å¯¹æ¤ä»“库的访问æƒé™ã€‚ç»§ç»ï¼Ÿ settings.deploy_key_deletion_success=éƒ¨ç½²å¯†é’¥å·²åˆ é™¤ã€‚ @@ -2455,11 +2456,11 @@ settings.protected_branch=åˆ†æ”¯ä¿æŠ¤ settings.protected_branch.save_rule=ä¿å˜è§„则 settings.protected_branch.delete_rule=åˆ é™¤è§„åˆ™ settings.protected_branch_can_push=是å¦å…许推é€ï¼Ÿ -settings.protected_branch_can_push_yes=ä½ å¯ä»¥æŽ¨ -settings.protected_branch_can_push_no=ä½ ä¸èƒ½æŽ¨é€ -settings.branch_protection=分支 '<b>%s</b>' çš„ä¿æŠ¤è§„åˆ™ +settings.protected_branch_can_push_yes=您å¯ä»¥æŽ¨é€ +settings.protected_branch_can_push_no=您ä¸èƒ½æŽ¨é€ +settings.branch_protection=分支「<b>%s</b>ã€çš„ä¿æŠ¤è§„åˆ™ settings.protect_this_branch=å¯ç”¨åˆ†æ”¯ä¿æŠ¤ -settings.protect_this_branch_desc=阻æ¢åˆ 除并é™åˆ¶Git推é€å’Œåˆå¹¶åˆ°åˆ†æ”¯ã€‚ +settings.protect_this_branch_desc=阻æ¢åˆ 除并é™åˆ¶ Git 推é€å’Œåˆå¹¶åˆ°åˆ†æ”¯ã€‚ settings.protect_disable_push=ç¦ç”¨æŽ¨é€ settings.protect_disable_push_desc=æ¤åˆ†æ”¯ä¸å…许推é€ã€‚ settings.protect_disable_force_push=ç¦ç”¨å¼ºåˆ¶æŽ¨é€ @@ -2490,7 +2491,7 @@ settings.protect_status_check_patterns_desc=输入模å¼ï¼ŒæŒ‡å®šå“ªäº›çŠ¶æ€æ£€ settings.protect_check_status_contexts_desc=è¦æ±‚çŠ¶æ€æ£€æŸ¥é€šè¿‡æ‰èƒ½åˆå¹¶ã€‚如果å¯ç”¨ï¼Œæäº¤å¿…须先推é€åˆ°å¦ä¸€ä¸ªåˆ†æ”¯ï¼Œç„¶åŽå†åˆå¹¶æˆ–推é€åˆ°åŒ¹é…è¿™äº›ä¿æŠ¤è§„åˆ™çš„åˆ†æ”¯ã€‚å¦‚æžœæ²¡æœ‰é€‰æ‹©å…·ä½“çš„çŠ¶æ€æ£€æŸ¥ä¸Šä¸‹æ–‡ï¼Œåˆ™æ‰€æœ‰çš„çŠ¶æ€æ£€æŸ¥éƒ½é€šè¿‡æ‰èƒ½åˆå¹¶ã€‚ settings.protect_check_status_contexts_list=æ¤ä»“åº“ä¸Šå‘¨è¿›è¡Œè¿‡çš„çŠ¶æ€æ£€æŸ¥ settings.protect_status_check_matched=åŒ¹é… -settings.protect_invalid_status_check_pattern=æ— æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则:“%sâ€ã€‚ +settings.protect_invalid_status_check_pattern=æ— æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则:「%sã€ã€‚ settings.protect_no_valid_status_check_patterns=æ²¡æœ‰æœ‰æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则。 settings.protect_required_approvals=所需的批准: settings.protect_required_approvals_desc=åªå…许åˆå¹¶æœ‰è¶³å¤Ÿå®¡æ ¸çš„åˆå¹¶è¯·æ±‚ã€‚è¦æ±‚çš„å®¡æ ¸å¿…é¡»æ¥è‡ªç™½å啿ˆ–者有æƒé™çš„用户或团队。 @@ -2513,9 +2514,9 @@ settings.protect_unprotected_file_patterns=ä¸å—ä¿æŠ¤çš„æ–‡ä»¶æ¨¡å¼(使用分 settings.protect_unprotected_file_patterns_desc=如果用户有写æƒé™ï¼Œåˆ™å…许直接更改的ä¸å—ä¿æŠ¤çš„æ–‡ä»¶ï¼Œä»¥ç»•è¿‡æŽ¨é€é™åˆ¶ã€‚å¯ä»¥ä½¿ç”¨åˆ†å·åˆ†éš”å¤šä¸ªæ¨¡å¼ (';')。 è§ <a href='%[1]s'>%[2]s</a> 文档了解模å¼è¯æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code> settings.add_protected_branch=å¯ç”¨ä¿æŠ¤ settings.delete_protected_branch=ç¦ç”¨ä¿æŠ¤ -settings.update_protect_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ %s æ›´æ–°æˆåŠŸã€‚ -settings.remove_protected_branch_success=ç§»é™¤åˆ†æ”¯ä¿æŠ¤è§„åˆ™"%s"æˆåŠŸã€‚ -settings.remove_protected_branch_failed=ç§»é™¤åˆ†æ”¯ä¿æŠ¤è§„åˆ™"%s"失败。 +settings.update_protect_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€æ›´æ–°æˆåŠŸã€‚ +settings.remove_protected_branch_success=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€ç§»é™¤æˆåŠŸã€‚ +settings.remove_protected_branch_failed=åˆ†æ”¯ä¿æŠ¤è§„åˆ™ã€Œ%sã€ç§»é™¤å¤±è´¥ã€‚ settings.protected_branch_deletion=åˆ é™¤åˆ†æ”¯ä¿æŠ¤ settings.protected_branch_deletion_desc=ç¦ç”¨åˆ†æ”¯ä¿æŠ¤å…许具有写入æƒé™çš„ç”¨æˆ·æŽ¨é€æäº¤åˆ°æ¤åˆ†æ”¯ã€‚ç»§ç»ï¼Ÿ settings.block_rejected_reviews=æ‹’ç»å®¡æ ¸é˜»æ¢äº†æ¤åˆå¹¶ @@ -2536,15 +2537,15 @@ settings.protected_branch_required_rule_name=必须填写规则åç§° settings.protected_branch_duplicate_rule_name=规则åç§°å·²å˜åœ¨ settings.protected_branch_required_approvals_min=所需的审批数ä¸èƒ½ä¸ºè´Ÿæ•°ã€‚ settings.tags=æ ‡ç¾ -settings.tags.protection=Gitæ ‡ç¾ä¿æŠ¤ -settings.tags.protection.pattern=Gitæ ‡ç¾æ¨¡å¼ +settings.tags.protection=Git æ ‡ç¾ä¿æŠ¤ +settings.tags.protection.pattern=Git æ ‡ç¾è¡¨è¾¾å¼ settings.tags.protection.allowed=å…许列表 settings.tags.protection.allowed.users=å…è®¸çš„è´¦å· settings.tags.protection.allowed.teams=å…许的团队 settings.tags.protection.allowed.noone=æ— -settings.tags.protection.create=ä¿æŠ¤Gitæ ‡ç¾ -settings.tags.protection.none=没有å—ä¿æŠ¤çš„Gitæ ‡ç¾ -settings.tags.protection.pattern.description=ä½ å¯ä»¥ä½¿ç”¨å•个å称或 glob 模å¼åŒ¹é…或æ£åˆ™è¡¨è¾¾å¼æ¥åŒ¹é…å¤šä¸ªæ ‡ç¾ã€‚了解详情请访问 <a target="_blank" rel="noopener" href="%s">ä¿æŠ¤Gitæ ‡ç¾æŒ‡å—</a>。 +settings.tags.protection.create=ä¿æŠ¤ Git æ ‡ç¾ +settings.tags.protection.none=没有å—ä¿æŠ¤çš„ Git æ ‡ç¾ã€‚ +settings.tags.protection.pattern.description=您å¯ä»¥ä½¿ç”¨å•个å称或 glob 模å¼åŒ¹é…或æ£åˆ™è¡¨è¾¾å¼æ¥åŒ¹é…å¤šä¸ªæ ‡ç¾ã€‚了解详情请访问 <a target="_blank" rel="noopener" href="%s">ä¿æŠ¤ Git æ ‡ç¾æŒ‡å—</a>。 settings.bot_token=Bot 令牌 settings.chat_id=èŠå¤© ID settings.thread_id=线程 ID @@ -2565,13 +2566,13 @@ settings.visibility.error=试图更改仓库å¯è§æ€§æ—¶å‡ºé”™ã€‚ settings.visibility.fork_error=æ— æ³•æ›´æ”¹æ´¾ç”Ÿä»“åº“çš„å¯è§æ€§ã€‚ settings.archive.button=归档仓库 settings.archive.header=å½’æ¡£æ¤ä»“库 -settings.archive.text=归档仓库将使其完全åªè¯»ã€‚它将在首页éšè—ã€‚æ²¡æœ‰äººï¼ˆç”šè‡³ä½ ï¼ï¼‰èƒ½å¤Ÿè¿›è¡Œæ–°çš„æäº¤ï¼Œæˆ–打开工å•åŠåˆå¹¶è¯·æ±‚。 +settings.archive.text=归档仓库将使其完全åªè¯»ã€‚它将在首页éšè—。没有人(包括您)能够进行新的æäº¤ï¼Œæˆ–打开工å•åŠåˆå¹¶è¯·æ±‚。 settings.archive.success=仓库已æˆåŠŸå½’æ¡£ã€‚ settings.archive.error=仓库在归档时出现异常。请通过日志获å–详细信æ¯ã€‚ settings.archive.error_ismirror=请ä¸è¦å¯¹é•œåƒä»“åº“å½’æ¡£ï¼Œè°¢è°¢ï¼ settings.archive.branchsettings_unavailable=å·²å½’æ¡£ä»“åº“æ— æ³•è¿›è¡Œåˆ†æ”¯è®¾ç½®ã€‚ -settings.archive.tagsettings_unavailable=已归档仓库的Gitæ ‡ç¾è®¾ç½®ä¸å¯ç”¨ã€‚ -settings.archive.mirrors_unavailable=如果仓库已被归档,镜åƒå°†ä¸å¯ç”¨ã€‚ +settings.archive.tagsettings_unavailable=已归档仓库的 Git æ ‡ç¾è®¾ç½®ä¸å¯ç”¨ã€‚ +settings.archive.mirrors_unavailable=如果仓库已归档,镜åƒå°†ä¸å¯ç”¨ã€‚ settings.unarchive.button=撤销仓库归档 settings.unarchive.header=撤销æ¤ä»“库归档 settings.unarchive.text=撤销归档将æ¢å¤ä»“库接收æäº¤ã€æŽ¨é€ï¼Œä»¥åŠæ–°å·¥å•å’Œåˆå¹¶è¯·æ±‚的能力。 @@ -2625,7 +2626,7 @@ diff.whitespace_show_everything=显示所有更改 diff.whitespace_ignore_all_whitespace=æ¯”è¾ƒè¡Œæ—¶å¿½ç•¥ç©ºç™½ç¬¦å· diff.whitespace_ignore_amount_changes=å¿½ç•¥ç©ºç™½ç¬¦å·æ•°é‡çš„å˜åŒ– diff.whitespace_ignore_at_eol=忽略行末空白符å·çš„æ›´æ”¹ -diff.stats_desc=共有 <strong> %d 个文件被更改</strong>,包括 <strong>%d 次æ’å…¥</strong> å’Œ <strong>%d æ¬¡åˆ é™¤</strong> +diff.stats_desc=å…±<strong>修改 %d 个文件</strong>ï¼ŒåŒ…å« <strong>%d 行新增</strong>å’Œ <strong>%d è¡Œåˆ é™¤</strong> diff.stats_desc_file=å˜æ›´ %d 行:新增 %d è¡Œï¼Œåˆ é™¤ %d 行 diff.bin=二进制 diff.bin_not_shown=二进制文件未显示。 @@ -2669,22 +2670,22 @@ diff.submodule_deleted=忍¡å— %[1]s 已从 %[2]s ä¸åˆ 除 diff.submodule_updated=忍¡å— %[1]s 已更新:%[2]s releases.desc=跟踪项目版本和下载。 -release.releases=版本å‘布 +release.releases=å‘布 release.detail=å‘布详情 -release.tags=Gitæ ‡ç¾ +release.tags=Git æ ‡ç¾ release.new_release=å‘布新版 release.draft=è‰ç¨¿ -release.prerelease=预å‘行 +release.prerelease=预å‘布 release.stable=稳定 -release.latest=最新版本 +release.latest=最新 release.compare=比较 release.edit=编辑 release.ahead.commits=<strong>%d</strong> 次æäº¤ -release.ahead.target=在æ¤ç‰ˆæœ¬å‘布åŽè¢«åŠ å…¥åˆ° %s +release.ahead.target=在æ¤ç‰ˆæœ¬å‘布åŽå·²åŠ å…¥åˆ° %s tag.ahead.target=è‡ªæ¤æ ‡ç¾åˆ° %s release.source_code=æºä»£ç -release.new_subheader=版本å‘布组织项目的版本。 -release.edit_subheader=版本å‘布组织项目的版本。 +release.new_subheader=å‘布组织项目的版本。 +release.edit_subheader=å‘布组织项目的版本。 release.tag_name=æ ‡ç¾åç§° release.target=ç›®æ ‡åˆ†æ”¯ release.tag_helper=选择一个å˜åœ¨çš„æ ‡ç¾æˆ–è€…åˆ›å»ºæ–°æ ‡ç¾ã€‚ @@ -2693,74 +2694,74 @@ release.tag_helper_existing=çŽ°æœ‰æ ‡ç¾ã€‚ release.title=å‘å¸ƒæ ‡é¢˜ release.title_empty=æ ‡é¢˜ä¸èƒ½ä¸ºç©ºã€‚ release.message=æè¿°è¿™ä¸ªç‰ˆæœ¬ -release.prerelease_desc=æ ‡è®°ä¸ºé¢„å‘行 +release.prerelease_desc=æ ‡è®°ä¸ºé¢„å‘布 release.prerelease_helper=æ ‡è®°æ¤ç‰ˆæœ¬ä¸é€‚åˆç”Ÿäº§ä½¿ç”¨ã€‚ release.cancel=å–æ¶ˆ release.publish=å‘布版本 release.save_draft=ä¿å˜è‰ç¨¿ release.edit_release=ä¿å˜æ¤æ¬¡å‘布 release.delete_release=åˆ é™¤å‘布 -release.delete_tag=åˆ é™¤ Gitæ ‡ç¾ +release.delete_tag=åˆ é™¤ Git æ ‡ç¾ release.deletion=åˆ é™¤å‘布 release.deletion_desc=åˆ é™¤ç‰ˆæœ¬å‘布åªä¼šä»Ž Gitea ä¸ç§»é™¤ã€‚è¿™ä¸ä¼šå½±å“ Git çš„æ ‡ç¾ä»¥åŠæ‚¨ä»“库的内容和历å²ã€‚是å¦ç»§ç»ï¼Ÿ -release.deletion_success=Releaseå·²è¢«åˆ é™¤ã€‚ -release.deletion_tag_desc=将从仓库ä¸åˆ é™¤æ¤ Gitæ ‡ç¾ã€‚仓库内容和历å²è®°å½•ä¿æŒä¸å˜ã€‚ç»§ç»å—? -release.deletion_tag_success=该 Gitæ ‡ç¾ å·²ç»è¢«åˆ 除 -release.tag_name_already_exist=ä½¿ç”¨æ¤æ ‡ç¾åç§°çš„å‘布版本已ç»å˜åœ¨ã€‚ +release.deletion_success=该å‘å¸ƒå·²åˆ é™¤ã€‚ +release.deletion_tag_desc=将从仓库ä¸åˆ é™¤æ¤ Git æ ‡ç¾ã€‚仓库内容和历å²è®°å½•ä¿æŒä¸å˜ã€‚ç»§ç»å—? +release.deletion_tag_success=该 Git æ ‡ç¾å·²åˆ 除。 +release.tag_name_already_exist=ä½¿ç”¨æ¤æ ‡ç¾åç§°çš„å‘布已ç»å˜åœ¨ã€‚ release.tag_name_invalid=æ ‡ç¾åç§°æ— æ•ˆã€‚ -release.tag_name_protected=Gitæ ‡ç¾åç§°å·²å—ä¿æŠ¤ã€‚ -release.tag_already_exist=æ¤ Gitæ ‡ç¾ åç§°å·²å˜åœ¨ +release.tag_name_protected=Git æ ‡ç¾åç§°å·²å—ä¿æŠ¤ã€‚ +release.tag_already_exist=æ¤ Git æ ‡ç¾åç§°å·²å˜åœ¨ã€‚ release.downloads=下载附件 release.download_count=下载:%s release.add_tag_msg=使用å‘å¸ƒçš„æ ‡é¢˜å’Œå†…å®¹ä½œä¸ºæ ‡ç¾æ¶ˆæ¯ã€‚ release.add_tag=ä»…åˆ›å»ºæ ‡ç¾ -release.releases_for=%s 的版本å‘布 +release.releases_for=%s çš„å‘布 release.tags_for=%s çš„æ ‡ç¾ branch.name=分支åç§° -branch.already_exists=å为 %s 的分支已å˜åœ¨ã€‚ +branch.already_exists=å为「%sã€çš„分支已å˜åœ¨ã€‚ branch.delete_head=刪除 -branch.delete=åˆ é™¤åˆ†æ”¯ %s +branch.delete=åˆ é™¤åˆ†æ”¯ã€Œ%s〠branch.delete_html=åˆ é™¤åˆ†æ”¯ branch.delete_desc=åˆ é™¤åˆ†æ”¯æ˜¯æ°¸ä¹…çš„ã€‚è™½ç„¶å·²åˆ é™¤çš„åˆ†æ”¯åœ¨å®žé™…è¢«åˆ é™¤å‰æœ‰å¯èƒ½ä¼šçŸæ—¶é—´å˜åœ¨ï¼Œä½†è¿™åœ¨å¤§å¤šæ•°æƒ…å†µä¸‹æ— æ³•æ’¤é”€ã€‚æ˜¯å¦ç»§ç»ï¼Ÿ -branch.deletion_success=分支 %s å·²è¢«åˆ é™¤ã€‚ -branch.deletion_failed=åˆ é™¤åˆ†æ”¯ %s 失败。 -branch.delete_branch_has_new_commits=å› ä¸ºåˆå¹¶ä¹‹åŽæœ‰æ–°çš„æäº¤ï¼Œåˆ†æ”¯ %s æ— æ³•è¢«åˆ é™¤ã€‚ +branch.deletion_success=分支「%sã€åˆ 除æˆåŠŸã€‚ +branch.deletion_failed=分支「%sã€åˆ 除失败。 +branch.delete_branch_has_new_commits=å› ä¸ºåˆå¹¶ä¹‹åŽæœ‰æ–°çš„æäº¤ï¼Œåˆ†æ”¯ã€Œ%sã€æ— æ³•åˆ é™¤ã€‚ branch.create_branch=创建分支 %s -branch.create_from=从 %s -branch.create_success=分支 '%s' 已创建。 -branch.branch_already_exists=æ¤ä»“库已å˜åœ¨å为 %s 的分支。 -branch.branch_name_conflict=分支åç§°"%s"与已å˜åœ¨çš„分支"%s"冲çªã€‚ -branch.tag_collision=分支 %s ä¸èƒ½è¢«åˆ›å»ºå› 为åŒåçš„æ ‡ç¾å·²ç»å˜åœ¨ã€‚ +branch.create_from=从「%s〠+branch.create_success=分支「%sã€å·²åˆ›å»ºã€‚ +branch.branch_already_exists=æ¤ä»“库已å˜åœ¨å为「%sã€çš„分支。 +branch.branch_name_conflict=分支å称「%sã€ä¸Žå·²å˜åœ¨çš„分支「%sã€å†²çªã€‚ +branch.tag_collision=分支「%sã€æ— æ³•åˆ›å»ºï¼Œå› ä¸ºåŒåçš„æ ‡ç¾å·²ç»å˜åœ¨ã€‚ branch.deleted_by=åˆ é™¤äººï¼š%s -branch.restore_success=分支 "%s"已还原。 -branch.restore_failed=还原分支 "%s"失败。 -branch.protected_deletion_failed=ä¸èƒ½åˆ 除å—ä¿æŠ¤çš„åˆ†æ”¯ "%s"。 -branch.default_deletion_failed=ä¸èƒ½åˆ 除默认分支"%s"。 -branch.default_branch_not_exist=默认分支 %s ä¸å˜åœ¨ã€‚ -branch.restore=`还原分支 "%s"` -branch.download=`下载分支 "%s"` -branch.rename=`é‡å‘½å分支 "%s"` +branch.restore_success=分支「%sã€å·²è¿˜åŽŸã€‚ +branch.restore_failed=分支「%sã€è¿˜åŽŸå¤±è´¥ã€‚ +branch.protected_deletion_failed=ä¸èƒ½åˆ 除å—ä¿æŠ¤çš„åˆ†æ”¯ã€Œ%sã€ã€‚ +branch.default_deletion_failed=ä¸èƒ½åˆ 除默认分支「%sã€ã€‚ +branch.default_branch_not_exist=默认分支「%sã€ä¸å˜åœ¨ã€‚ +branch.restore=还原分支「%s〠+branch.download=下载分支「%s〠+branch.rename=é‡å‘½å分支「%s〠branch.included_desc=æ¤åˆ†æ”¯æ˜¯é»˜è®¤åˆ†æ”¯çš„一部分 branch.included=å·²åŒ…å« branch.create_new_branch=从下列分支创建分支: branch.confirm_create_branch=创建分支 branch.warning_rename_default_branch=您æ£åœ¨é‡å‘½å默认分支。 -branch.rename_branch_to=é‡å‘½å %s 为: +branch.rename_branch_to=é‡å‘½å「%sã€ä¸ºï¼š branch.confirm_rename_branch=é‡å‘½å分支 branch.create_branch_operation=创建分支 branch.new_branch=创建新分支 -branch.new_branch_from=基于"%s"创建新分支 -branch.renamed=分支 %s 被é‡å‘½å为 %s。 +branch.new_branch_from=基于「%sã€åˆ›å»ºæ–°åˆ†æ”¯ +branch.renamed=分支 %s å·²é‡å‘½å为 %s。 branch.rename_default_or_protected_branch_error=åªæœ‰ç®¡ç†å‘˜èƒ½é‡å‘½å默认分支和å—ä¿æŠ¤çš„åˆ†æ”¯ã€‚ branch.rename_protected_branch_failed=æ¤åˆ†æ”¯å—到 glob è¯æ³•è§„åˆ™çš„ä¿æŠ¤ã€‚ tag.create_tag=åˆ›å»ºæ ‡ç¾ %s tag.create_tag_operation=åˆ›å»ºæ ‡ç¾ tag.confirm_create_tag=åˆ›å»ºæ ‡ç¾ -tag.create_tag_from=基于"%s"åˆ›å»ºæ–°æ ‡ç¾ +tag.create_tag_from=基于「%sã€åˆ›å»ºæ–°æ ‡ç¾ -tag.create_success=æ ‡ç¾"%s"å·²å˜åœ¨ +tag.create_success=æ ‡ç¾ã€Œ%sã€å·²å˜åœ¨ã€‚ topic.manage_topics=管ç†ä¸»é¢˜ topic.done=ä¿å˜ @@ -2807,14 +2808,14 @@ team_permission_desc=æƒé™ team_unit_desc=å…许访问仓库å•å…ƒ team_unit_disabled=(å·²ç¦ç”¨) -form.name_reserved=组织åç§° '%s' 是被ä¿ç•™çš„。 -form.name_pattern_not_allowed=仓库åç§°ä¸ä¸å…许使用 "%s"。 +form.name_reserved=组织å称「%sã€æ˜¯ä¿ç•™çš„。 +form.name_pattern_not_allowed=仓库åç§°ä¸ä¸å…许使用「%sã€ã€‚ form.create_org_not_allowed=æ¤è´¦å·ç¦æ¢åˆ›å»ºç»„织 settings=组织设置 settings.options=组织 settings.full_name=组织全å -settings.email=è”系电å邮件 +settings.email=è”系邮箱 settings.website=网站 settings.location=所在地区 settings.permission=æƒé™ @@ -2836,7 +2837,7 @@ settings.delete_account=åˆ é™¤å½“å‰ç»„织 settings.delete_prompt=åˆ é™¤æ“作会永久清除该组织的信æ¯ï¼Œå¹¶ä¸” <strong>ä¸å¯æ¢å¤</strong>ï¼ settings.confirm_delete_account=ç¡®è®¤åˆ é™¤ç»„ç»‡ settings.delete_org_title=åˆ é™¤ç»„ç»‡ -settings.delete_org_desc=æ¤ç»„ç»‡å°†ä¼šè¢«æ°¸ä¹…åˆ é™¤ï¼Œç¡®è®¤ç»§ç»å—? +settings.delete_org_desc=æ¤ç»„ç»‡å°†ä¼šæ°¸ä¹…åˆ é™¤ï¼Œç¡®è®¤ç»§ç»å—? settings.hooks_desc=在æ¤å¤„æ·»åŠ çš„ Web é’©å将会应用到该组织下的 <strong>所有仓库</strong>。 settings.labels_desc=æ·»åŠ èƒ½å¤Ÿè¢«è¯¥ç»„ç»‡ä¸‹çš„ <strong>所有仓库</strong> 的工å•ä½¿ç”¨çš„æ ‡ç¾ã€‚ @@ -2882,7 +2883,7 @@ teams.invite_team_member=é‚€è¯·åŠ å…¥ %s teams.invite_team_member.list=待处ç†çš„邀请 teams.delete_team_title=åˆ é™¤å›¢é˜Ÿ teams.delete_team_desc=åˆ é™¤ä¸€ä¸ªå›¢é˜Ÿå°†åˆ é™¤å›¢é˜Ÿæˆå‘˜çš„访问æƒé™ï¼Œç»§ç»ï¼Ÿ -teams.delete_team_success=è¯¥å›¢é˜Ÿå·²è¢«åˆ é™¤ã€‚ +teams.delete_team_success=è¯¥å›¢é˜Ÿå·²åˆ é™¤ã€‚ teams.read_permission_desc=该团队拥有对所属仓库的 <strong>读å–</strong> æƒé™ï¼Œå›¢é˜Ÿæˆå‘˜å¯ä»¥è¿›è¡ŒæŸ¥çœ‹å’Œå…‹éš†ç‰åªè¯»æ“作。 teams.write_permission_desc=该团队拥有对所属仓库的 <strong>读å–</strong> å’Œ <strong>写入</strong> çš„æƒé™ã€‚ teams.admin_permission_desc=该团队拥有一定的 <strong>管ç†</strong> æƒé™ï¼Œå›¢é˜Ÿæˆå‘˜å¯ä»¥è¯»å–ã€å…‹éš†ã€æŽ¨é€ä»¥åŠæ·»åŠ å…¶å®ƒä»“åº“å作者。 @@ -2933,7 +2934,7 @@ repositories=ä»“åº“ç®¡ç† hooks=Web é’©å integrations=é›†æˆ authentication=è®¤è¯æº -emails=用户邮件 +emails=用户邮箱 config=应用é…ç½® config_summary=æ‘˜è¦ config_settings=设置 @@ -2952,27 +2953,27 @@ dashboard.operation_name=æ“作åç§° dashboard.operation_switch=开关 dashboard.operation_run=执行 dashboard.clean_unbind_oauth=æ¸…ç†æœªç»‘定的 OAuth 连接 -dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth è¿žæŽ¥å·²è¢«åˆ é™¤ã€‚ +dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth è¿žæŽ¥å·²åˆ é™¤ã€‚ dashboard.task.started=已开始任务:%[1]s dashboard.task.process=任务: %[1]s dashboard.task.cancelled=任务: %[1]s 已喿¶ˆ: %[3]s dashboard.task.error=任务ä¸çš„错误: %[1]s: %[3]s dashboard.task.finished=任务: %[2]s å¯åŠ¨çš„ %[1]s å·²å®Œæˆ dashboard.task.unknown=未知任务: %[1]s -dashboard.cron.started=已开始计划任务:%[1]s +dashboard.cron.started=计划任务:%[1]s å·²å¯åЍ dashboard.cron.process=计划任务:%[1]s -dashboard.cron.cancelled=定时任务: %[1]s 已喿¶ˆ: %[3]s -dashboard.cron.error=任务ä¸çš„错误: %s: %[3]s -dashboard.cron.finished=任务:%[1]s å·²ç»å®Œæˆ +dashboard.cron.cancelled=计划任务:%[1]s 已喿¶ˆï¼š%[3]s +dashboard.cron.error=计划任务错误: %s: %[3]s +dashboard.cron.finished=计划任务:%[1]s å·²å®Œæˆ dashboard.delete_inactive_accounts=åˆ é™¤æ‰€æœ‰æœªæ¿€æ´»çš„å¸æˆ· dashboard.delete_inactive_accounts.started=åˆ é™¤æ‰€æœ‰æœªæ¿€æ´»çš„è´¦æˆ·ä»»åŠ¡å·²å¯åŠ¨ã€‚ -dashboard.delete_repo_archives=åˆ é™¤æ‰€æœ‰ä»£ç åº“çš„å˜æ¡£ (ZIP〠TARã€GZ, ç‰...) +dashboard.delete_repo_archives=åˆ é™¤æ‰€æœ‰ä»“åº“çš„å˜æ¡£ï¼ˆZIPã€TARã€GZç‰ï¼‰ dashboard.delete_repo_archives.started=åˆ é™¤æ‰€æœ‰ä»“åº“å˜æ¡£ä»»åС已å¯åŠ¨ã€‚ dashboard.delete_missing_repos=åˆ é™¤æ‰€æœ‰ä¸¢å¤± Git 文件的仓库 dashboard.delete_missing_repos.started=åˆ é™¤æ‰€æœ‰ä¸¢å¤± Git 文件的仓库任务已å¯åŠ¨ã€‚ dashboard.delete_generated_repository_avatars=åˆ é™¤ç”Ÿæˆçš„ä»“åº“å¤´åƒ -dashboard.sync_repo_branches=将缺少的分支从 git æ•°æ®åŒæ¥åˆ°æ•°æ®åº“ -dashboard.sync_repo_tags=从 git æ•°æ®åŒæ¥æ ‡ç¾åˆ°æ•°æ®åº“ +dashboard.sync_repo_branches=将缺少的分支从 Git æ•°æ®åŒæ¥åˆ°æ•°æ®åº“ +dashboard.sync_repo_tags=从 Git æ•°æ®åŒæ¥æ ‡ç¾åˆ°æ•°æ®åº“ dashboard.update_mirrors=更新镜åƒä»“库 dashboard.repo_health_check=å¥åº·æ£€æŸ¥æ‰€æœ‰ä»“库 dashboard.check_repo_stats=检查所有仓库统计 @@ -2987,11 +2988,11 @@ dashboard.reinit_missing_repos=釿–°åˆå§‹åŒ–所有丢失的 Git 仓库å˜åœ¨çš dashboard.sync_external_users=åŒæ¥å¤–éƒ¨ç”¨æˆ·æ•°æ® dashboard.cleanup_hook_task_table=æ¸…ç† hook_task 表 dashboard.cleanup_packages=清ç†è¿‡æœŸçš„软件包 -dashboard.cleanup_actions=清ç†è¿‡æœŸçš„ Actions èµ„æº +dashboard.cleanup_actions=清ç†è¿‡æœŸçš„工作æµèµ„æº dashboard.server_uptime=æœåŠ¡è¿è¡Œæ—¶é—´ dashboard.current_goroutine=å½“å‰ Goroutines æ•°é‡ dashboard.current_memory_usage=当å‰å†…å˜ä½¿ç”¨é‡ -dashboard.total_memory_allocated=所有被分é…çš„å†…å˜ +dashboard.total_memory_allocated=所有已分é…çš„å†…å˜ dashboard.memory_obtained=内å˜å ç”¨é‡ dashboard.pointer_lookup_times=指针查找次数 dashboard.memory_allocate_times=内å˜åˆ†é…次数 @@ -3000,36 +3001,36 @@ dashboard.current_heap_usage=å½“å‰ Heap 内å˜ä½¿ç”¨é‡ dashboard.heap_memory_obtained=Heap 内å˜å ç”¨é‡ dashboard.heap_memory_idle=Heap 内å˜ç©ºé—²é‡ dashboard.heap_memory_in_use=æ£åœ¨ä½¿ç”¨çš„ Heap å†…å˜ -dashboard.heap_memory_released=被释放的 Heap å†…å˜ +dashboard.heap_memory_released=已释放的 Heap å†…å˜ dashboard.heap_objects=Heap å¯¹è±¡æ•°é‡ dashboard.bootstrap_stack_usage=å¯åЍ Stack ä½¿ç”¨é‡ -dashboard.stack_memory_obtained=被分é…çš„ Stack å†…å˜ +dashboard.stack_memory_obtained=已分é…çš„ Stack å†…å˜ dashboard.mspan_structures_usage=MSpan 结构内å˜ä½¿ç”¨é‡ -dashboard.mspan_structures_obtained=被分é…çš„ MSpan ç»“æž„å†…å˜ +dashboard.mspan_structures_obtained=已分é…çš„ MSpan ç»“æž„å†…å˜ dashboard.mcache_structures_usage=MCache 结构内å˜ä½¿ç”¨é‡ -dashboard.mcache_structures_obtained=被分é…çš„ MCache ç»“æž„å†…å˜ -dashboard.profiling_bucket_hash_table_obtained=被分é…的剖æžå“ˆå¸Œè¡¨å†…å˜ -dashboard.gc_metadata_obtained=被分é…çš„ GC 元数æ®å†…å˜ -dashboard.other_system_allocation_obtained=其它被分é…çš„ç³»ç»Ÿå†…å˜ +dashboard.mcache_structures_obtained=已分é…çš„ MCache ç»“æž„å†…å˜ +dashboard.profiling_bucket_hash_table_obtained=已分é…的剖æžå“ˆå¸Œè¡¨å†…å˜ +dashboard.gc_metadata_obtained=已分é…çš„ GC 元数æ®å†…å˜ +dashboard.other_system_allocation_obtained=其它已分é…çš„ç³»ç»Ÿå†…å˜ dashboard.next_gc_recycle=下次 GC 内å˜å›žæ”¶é‡ dashboard.last_gc_time=è·ç¦»ä¸Šæ¬¡ GC æ—¶é—´ dashboard.total_gc_time=GC æš‚åœæ—¶é—´æ€»é‡ dashboard.total_gc_pause=GC æš‚åœæ—¶é—´æ€»é‡ dashboard.last_gc_pause=上次 GC æš‚åœæ—¶é—´ dashboard.gc_times=GC 执行次数 -dashboard.delete_old_actions=从数æ®åº“ä¸åˆ 除所有旧æ“作记录 -dashboard.delete_old_actions.started=已开始从数æ®åº“ä¸åˆ 除所有旧æ“作记录。 +dashboard.delete_old_actions=从数æ®åº“ä¸åˆ 除所有旧工作æµè®°å½• +dashboard.delete_old_actions.started=已开始从数æ®åº“ä¸åˆ 除所有旧工作æµè®°å½•。 dashboard.update_checker=更新检查器 dashboard.delete_old_system_notices=从数æ®åº“ä¸åˆ 除所有旧系统通知 dashboard.gc_lfs=垃圾回收 LFS å…ƒæ•°æ® -dashboard.stop_zombie_tasks=åœæ¢åƒµå°¸ä»»åŠ¡ -dashboard.stop_endless_tasks=åœæ¢æ— æ³•åœæ¢çš„任务 -dashboard.cancel_abandoned_jobs=å–æ¶ˆä¸¢å¼ƒçš„任务 -dashboard.start_schedule_tasks=开始Actions调度任务 +dashboard.stop_zombie_tasks=åœæ¢åƒµå°¸å·¥ä½œæµä»»åŠ¡ +dashboard.stop_endless_tasks=åœæ¢æ— é™å¾ªçŽ¯çš„å·¥ä½œæµä»»åŠ¡ +dashboard.cancel_abandoned_jobs=å–æ¶ˆå·²æ”¾å¼ƒçš„工作æµä»»åŠ¡ +dashboard.start_schedule_tasks=å¯åŠ¨å·¥ä½œæµè®¡åˆ’任务 dashboard.sync_branch.started=åˆ†æ”¯åŒæ¥å·²å¼€å§‹ dashboard.sync_tag.started=æ ‡ç¾åŒæ¥å·²å¼€å§‹ dashboard.rebuild_issue_indexer=é‡å»ºå·¥å•索引 -dashboard.sync_repo_licenses=釿–°ä»“库许å¯è¯æŽ¢æµ‹ +dashboard.sync_repo_licenses=釿–°æŽ¢æµ‹ä»“库许å¯è¯ users.user_manage_panel=ç”¨æˆ·å¸æˆ·ç®¡ç† users.new_account=åˆ›å»ºæ–°å¸æˆ· @@ -3047,33 +3048,33 @@ users.created=创建时间 users.last_login=上次登录 users.never_login=从未登录 users.send_register_notify=å‘逿³¨å†Œé€šçŸ¥ -users.new_success=用户账户 '%s' 已被创建。 +users.new_success=用户账户「%sã€å·²åˆ›å»ºã€‚ users.edit=修改 users.auth_source=è®¤è¯æº users.local=本地 users.auth_login_name=认è¯ç™»å½•åç§° users.password_helper=ä¿æŒå¯†ç ä¸ºç©ºå°†ä¸æ›´æ”¹å¯†ç 。 -users.update_profile_success=è¯¥å¸æˆ·å·²è¢«æ›´æ–°ã€‚ +users.update_profile_success=è¯¥å¸æˆ·å·²æ›´æ–°ã€‚ users.edit_account=编辑å¸å· users.max_repo_creation=最大仓库数 users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值) -users.is_activated=该用户已被激活 +users.is_activated=该用户已激活 users.prohibit_login=ç¦ç”¨ç™»å½• users.is_admin=是管ç†å‘˜ users.is_restricted=å—é™ users.allow_git_hook=å…许创建 Git é’©å -users.allow_git_hook_tooltip=Git é’©å将会被以æ“作系统用户è¿è¡Œï¼Œå°†ä¼šæ‹¥æœ‰åŒæ ·çš„主机访问æƒé™ã€‚å› æ¤ï¼Œæ‹¥æœ‰æ¤ç‰¹æ®Šçš„Git é’©åæƒé™å°†èƒ½å¤Ÿè®¿é—®åˆä¿®æ”¹æ‰€æœ‰çš„ Gitea 仓库或者Gitea的数æ®åº“ã€‚åŒæ—¶ä¹Ÿèƒ½èŽ·å¾—Gitea的管ç†å‘˜æƒé™ã€‚ +users.allow_git_hook_tooltip=Git é’©å将会以æ“作系统用户è¿è¡Œï¼Œæ‹¥æœ‰åŒæ ·çš„主机访问æƒé™ã€‚å› æ¤ï¼Œæ‹¥æœ‰æ¤ç‰¹æ®Šçš„ Git é’©åæƒé™å°†èƒ½å¤Ÿè®¿é—®åˆä¿®æ”¹æ‰€æœ‰çš„ Gitea 仓库或者 Gitea 的数æ®åº“ã€‚åŒæ—¶ä¹Ÿèƒ½èŽ·å¾— Gitea 的管ç†å‘˜æƒé™ã€‚ users.allow_import_local=å…许导入本地仓库 users.allow_create_organization=å…许创建组织 users.update_profile=æ›´æ–°å¸æˆ· users.delete_account=åˆ é™¤å¸æˆ· -users.cannot_delete_self=ä½ ä¸èƒ½åˆ 除自己 -users.still_own_repo=æ¤ç”¨æˆ·ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–å¤šä¸ªä»“åº“ã€‚å¿…é¡»é¦–å…ˆåˆ é™¤æˆ–è½¬è®©è¿™äº›ä»“åº“ã€‚ +users.cannot_delete_self=您ä¸èƒ½åˆ 除自己 +users.still_own_repo=æ¤ç”¨æˆ·ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–å¤šä¸ªä»“åº“ã€‚å¿…é¡»é¦–å…ˆåˆ é™¤æˆ–è½¬ç§»è¿™äº›ä»“åº“ã€‚ users.still_has_org=æ¤ç”¨æˆ·æ˜¯ç»„织的æˆå‘˜ã€‚必须先从组织ä¸åˆ 除用户。 users.purge=清ç†ç”¨æˆ· -users.purge_help=å¼ºåˆ¶åˆ é™¤ç”¨æˆ·å’Œç”¨æˆ·æ‹¥æœ‰çš„ä»»ä½•ä»“åº“ã€ç»„ç»‡å’Œè½¯ä»¶åŒ…ã€‚æ‰€æœ‰è¯„è®ºä¹Ÿå°†è¢«åˆ é™¤ã€‚ +users.purge_help=å¼ºåˆ¶åˆ é™¤ç”¨æˆ·å’Œç”¨æˆ·æ‹¥æœ‰çš„ä»»ä½•ä»“åº“ã€ç»„ç»‡å’Œè½¯ä»¶åŒ…ã€‚æ‰€æœ‰è¯„è®ºä¹Ÿå°†åˆ é™¤ã€‚ users.still_own_packages=æ¤ç”¨æˆ·ä»ç„¶æ‹¥æœ‰ä¸€ä¸ªæˆ–å¤šä¸ªè½¯ä»¶åŒ…ï¼Œè¯·å…ˆåˆ é™¤è¿™äº›è½¯ä»¶åŒ…ã€‚ -users.deletion_success=ç”¨æˆ·å¸æˆ·å·²è¢«åˆ 除。 +users.deletion_success=ç”¨æˆ·å¸æˆ·å·²åˆ 除。 users.reset_2fa=é‡ç½®ä¸¤æ¥éªŒè¯ users.list_status_filter.menu_text=过滤 users.list_status_filter.reset=é‡ç½® @@ -3092,19 +3093,19 @@ users.details=ç”¨æˆ·è¯¦ç»†ä¿¡æ¯ emails.email_manage_panel=é‚®ä»¶ç®¡ç† emails.primary=主è¦çš„ emails.activated=已激活 -emails.filter_sort.email=电å邮件 -emails.filter_sort.email_reverse=电å邮件(逆åº) +emails.filter_sort.email=邮箱 +emails.filter_sort.email_reverse=邮箱(倒åºï¼‰ emails.filter_sort.name=用户å emails.filter_sort.name_reverse=用户å(倒åº) -emails.updated=电å邮件已更新 -emails.not_updated=æ— æ³•æ›´æ–°è¯·æ±‚çš„ç”µå邮件地å€ï¼š %v -emails.duplicate_active=æ¤ç”µå邮件地å€å·²è¢«å¦ä¸€ä¸ªç”¨æˆ·æ¿€æ´»ä½¿ç”¨ã€‚ -emails.change_email_header=更新电å邮件属性 -emails.change_email_text=æ‚¨ç¡®å®šè¦æ›´æ–°è¯¥ç”µå邮件地å€å—? -emails.delete=åˆ é™¤ç”µå邮件 -emails.delete_desc=您确定è¦åˆ 除该电å邮件地å€ï¼Ÿ -emails.deletion_success=电å邮件地å€å·²è¢«åˆ 除。 -emails.delete_primary_email_error=您ä¸èƒ½åˆ 除主电å邮件。 +emails.updated=邮箱已更新 +emails.not_updated=æ— æ³•æ›´æ–°è¯·æ±‚çš„é‚®ç®±åœ°å€ï¼š%v +emails.duplicate_active=æ¤é‚®ç®±åœ°å€å·²è¢«å¦ä¸€ä¸ªç”¨æˆ·æ¿€æ´»ä½¿ç”¨ã€‚ +emails.change_email_header=更新邮箱属性 +emails.change_email_text=æ‚¨ç¡®å®šè¦æ›´æ–°è¯¥é‚®ç®±åœ°å€å—? +emails.delete=åˆ é™¤é‚®ç®± +emails.delete_desc=您确定è¦åˆ 除该邮箱地å€ï¼Ÿ +emails.deletion_success=邮箱地å€å·²åˆ 除。 +emails.delete_primary_email_error=您ä¸èƒ½åˆ 除主邮箱。 orgs.org_manage_panel=ç»„ç»‡ç®¡ç† orgs.name=åç§° @@ -3124,7 +3125,7 @@ repos.lfs_size=LFS å¤§å° packages.package_manage_panel=è½¯ä»¶åŒ…ç®¡ç† packages.total_size=总大å°ï¼š%s -packages.unreferenced_size=未引用大å°ï¼š %s +packages.unreferenced_size=未引用大å°ï¼š%s packages.cleanup=清ç†è¿‡æœŸæ•°æ® packages.cleanup.success=清ç†è¿‡æœŸæ•°æ®æˆåŠŸ packages.owner=所有者 @@ -3136,13 +3137,13 @@ packages.repository=仓库 packages.size=å¤§å° packages.published=å·²å‘布 -defaulthooks=默认Webé’©å +defaulthooks=默认 Web é’©å defaulthooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©åè‡ªåŠ¨å‘æœåС噍å‘出 HTTP POST 请求。这里定义的 Web é’©åæ˜¯é»˜è®¤é…置,将被å¤åˆ¶åˆ°æ‰€æœ‰æ–°çš„仓库ä¸ã€‚详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©åæŒ‡å—</a>。 -defaulthooks.add_webhook=æ·»åŠ é»˜è®¤Web é’©å +defaulthooks.add_webhook=æ·»åŠ é»˜è®¤ Web é’©å defaulthooks.update_webhook=更新默认 Web 钩å systemhooks=系统 Web é’©å -systemhooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©åè‡ªåŠ¨å‘æœåС噍å‘出HTTP POST请求。这里定义的 Web é’©å将作用于系统上的所有仓库,所以请考虑这å¯èƒ½å¸¦æ¥çš„任何性能影å“。了解详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©åæŒ‡å—</a>。 +systemhooks.desc=当æŸäº› Gitea äº‹ä»¶è§¦å‘æ—¶ï¼ŒWeb é’©åè‡ªåŠ¨å‘æœåС噍å‘出 HTTP POST 请求。这里定义的 Web é’©å将作用于系统上的所有仓库,所以请考虑这å¯èƒ½å¸¦æ¥çš„任何性能影å“。了解详情请访问 <a target="_blank" rel="noopener" href="%s">Web é’©åæŒ‡å—</a>。 systemhooks.add_webhook=æ·»åŠ ç³»ç»Ÿ Web é’©å systemhooks.update_webhook=更新系统 Web é’©å @@ -3164,7 +3165,7 @@ auths.bind_password=绑定密ç auths.user_base=用户æœç´¢åŸºå‡† auths.user_dn=用户 DN auths.attribute_username=用户å属性 -auths.attribute_username_placeholder=置空将使用Gitea的用户å。 +auths.attribute_username_placeholder=置空将使用 Gitea 的用户å。 auths.attribute_name=åå—属性 auths.attribute_surname=å§“æ°å±žæ€§ auths.attribute_mail=电å邮箱属性 @@ -3198,7 +3199,7 @@ auths.helo_hostname=HELO 主机å auths.helo_hostname_helper=用 HELO å‘é€çš„主机å。 留空å‘é€å½“å‰ä¸»æœºå。 auths.disable_helo=ç¦ç”¨ HELO auths.pam_service_name=PAM æœåŠ¡åç§° -auths.pam_email_domain=PAM 电å邮件域(å¯é€‰) +auths.pam_email_domain=PAM 邮件域(å¯é€‰ï¼‰ auths.oauth2_provider=OAuth2 æä¾›ç¨‹åº auths.oauth2_icon_url=å›¾æ ‡ URL auths.oauth2_clientID=客户端 ID (键) @@ -3207,8 +3208,8 @@ auths.openIdConnectAutoDiscoveryURL=OpenID 连接自动å‘现 URL auths.oauth2_use_custom_url=使用自定义的 URL è€Œä¸æ˜¯é»˜è®¤çš„ URL auths.oauth2_tokenURL=令牌 URL auths.oauth2_authURL=æŽˆæƒ URL -auths.oauth2_profileURL=Profile URL -auths.oauth2_emailURL=电å邮件 URL +auths.oauth2_profileURL=个人资料 URL +auths.oauth2_emailURL=邮箱 URL auths.skip_local_two_fa=跳过本地两æ¥éªŒè¯ auths.skip_local_two_fa_helper=ä¸è®¾ç½®æ„味ç€è®¾ç½®äº†ä¸¤æ¥éªŒè¯çš„æœ¬åœ°ç”¨æˆ·ä»ç„¶éœ€è¦é€šè¿‡ä¸¤æ¥éªŒè¯æ‰èƒ½ç™»å½• auths.oauth2_tenant=租户 @@ -3228,31 +3229,31 @@ auths.sspi_auto_create_users_helper=å…许 SSPI 认è¯åœ¨ç”¨æˆ·ç¬¬ä¸€æ¬¡ç™»å½•æ— auths.sspi_auto_activate_users=自动激活用户 auths.sspi_auto_activate_users_helper=å…许 SSPI 认è¯è‡ªåŠ¨æ¿€æ´»æ–°ç”¨æˆ· auths.sspi_strip_domain_names=从用户åä¸åˆ 除域å部分 -auths.sspi_strip_domain_names_helper=å¦‚æžœé€‰ä¸æ¤é¡¹ï¼ŒåŸŸå将从登录åä¸åˆ 除(例如,"DOMAIN\user"å’Œ"user@example.org"ï¼Œä¸¤è€…éƒ½å°†å˜æˆåªæ˜¯â€œç”¨æˆ·â€)。 +auths.sspi_strip_domain_names_helper=å¦‚æžœé€‰ä¸æ¤é¡¹ï¼ŒåŸŸå将从登录åä¸åˆ 除(例如,「DOMAIN\userã€å’Œã€Œuser@example.orgã€ï¼Œä¸¤è€…éƒ½å°†å˜æˆåªæ˜¯ã€Œç”¨æˆ·ã€ï¼‰ã€‚ auths.sspi_separator_replacement=è¦ä½¿ç”¨çš„分隔符代替\, / å’Œ @ -auths.sspi_separator_replacement_helper=用于替æ¢ä¸‹çº§ç™»å½•å称分隔符的å—符 (例如 "DOMAIN\user") ä¸çš„ \ 和用户主åå—(如"user@example.org"ä¸çš„ @)。 +auths.sspi_separator_replacement_helper=用于替æ¢ä¸‹çº§ç™»å½•å称分隔符的å—符(例如「DOMAIN\userã€ï¼‰ä¸çš„ \ 和用户主åå—(如「user@example.orgã€ä¸çš„ @)。 auths.sspi_default_language=默认è¯è¨€ auths.sspi_default_language_helper=SSPI è®¤è¯æ–¹æ³•为用户自动创建的默认è¯è¨€ã€‚如果您想è¦è‡ªåŠ¨æ£€æµ‹åˆ°è¯è¨€ï¼Œè¯·ç•™ç©ºã€‚ auths.tips=帮助æç¤º auths.tips.oauth2.general=OAuth2 è®¤è¯ auths.tips.oauth2.general.tip=当注册新的 OAuth2 èº«ä»½éªŒè¯æ—¶ï¼Œå›žè°ƒ/é‡å®šå‘ URL 应该是: auths.tip.oauth2_provider=OAuth2 æä¾›ç¨‹åº -auths.tip.bitbucket=在 %s 注册新的 OAuth ä½¿ç”¨è€…åŒæ—¶æ·»åŠ æƒé™â€œè´¦å·â€-“读å–†-auths.tip.nextcloud=使用下é¢çš„èœå•“设置(Settings) -> 安全(Security) -> OAuth 2.0 clientâ€åœ¨æ‚¨çš„实例上注册一个新的 OAuth 客户端。 +auths.tip.bitbucket=在 %s 注册新的 OAuth ä½¿ç”¨è€…åŒæ—¶æ·»åŠ æƒé™ã€Œè´¦å·ã€-「读å–〠+auths.tip.nextcloud=使用下é¢çš„èœå•「设置 -> 安全 -> OAuth 2.0 客户端ã€åœ¨æ‚¨çš„实例上注册一个新的 OAuth 客户端 auths.tip.dropbox=在 %s ä¸Šåˆ›å»ºä¸€ä¸ªæ–°çš„åº”ç”¨ç¨‹åº -auths.tip.facebook=`在 %s æ³¨å†Œä¸€ä¸ªæ–°çš„åº”ç”¨ï¼Œå¹¶æ·»åŠ äº§å“"Facebook 登录"` +auths.tip.facebook=在 %s æ³¨å†Œä¸€ä¸ªæ–°çš„åº”ç”¨ï¼Œå¹¶æ·»åŠ äº§å“「Facebook 登录〠auths.tip.github=在 %s 注册一个 OAuth åº”ç”¨ç¨‹åº auths.tip.gitlab_new=在 %s 注册一个新的应用 auths.tip.google_plus=ä»Žè°·æŒ API æŽ§åˆ¶å° %s 获得 OAuth2 å®¢æˆ·ç«¯å‡æ® auths.tip.openid_connect=使用 OpenID 连接å‘现 URL ({server}/.well-known/openid-configuration) æ¥æŒ‡å®šç»ˆç‚¹ -auths.tip.twitter=访问 %s,创建应用并确ä¿å¯ç”¨äº†"å…许æ¤åº”用程åºç”¨äºŽç™»å½• Twitter"的选项。 +auths.tip.twitter=访问 %s,创建应用并确ä¿å¯ç”¨äº†ã€Œå…许æ¤åº”用程åºä½¿ç”¨ Twitter 登录ã€çš„选项 auths.tip.discord=在 %s ä¸Šæ³¨å†Œæ–°åº”ç”¨ç¨‹åº auths.tip.gitea=注册一个新的 OAuth2 应用程åºã€‚å¯ä»¥è®¿é—® %s 查看帮助 -auths.tip.yandex=在 %s 上创建一个新的应用程åºã€‚在“ Yandex.Passport APIâ€è¿™éƒ¨åˆ†ä¸é€‰æ‹©ä»¥ä¸‹æƒé™ï¼šâ€œè®¿é—®ç”µå邮件地å€ï¼ˆAccess to email address)â€ï¼Œâ€œè®¿é—®ç”¨æˆ·å¤´åƒï¼ˆAccess to user avatar)â€å’Œâ€œè®¿é—®ç”¨æˆ·å,åå—和姓æ°ï¼Œæ€§åˆ«ï¼ˆAccess to username, first name and surname, genderAccess to username, first name and surname, gender)†+auths.tip.yandex=在 %s 上创建一个新的应用。在「Yandex.Passport APIã€è¿™éƒ¨åˆ†ä¸é€‰æ‹©ä»¥ä¸‹æƒé™ï¼šã€Œè®¿é—®é‚®ç®±åœ°å€ã€ï¼Œã€Œè®¿é—®ç”¨æˆ·å¤´åƒã€å’Œã€Œè®¿é—®ç”¨æˆ·å,åå—和姓æ°ï¼Œæ€§åˆ«ã€ auths.tip.mastodon=输入您想è¦è®¤è¯çš„ mastodon 实例的自定义 URL (或使用默认值) auths.edit=ä¿®æ”¹è®¤è¯æº auths.activated=è¯¥è®¤è¯æºå·²ç»å¯ç”¨ -auths.new_success=å·²æ·»åŠ èº«ä»½éªŒè¯ '%s'。 +auths.new_success=å·²æ·»åŠ èº«ä»½éªŒè¯ã€Œ%sã€ã€‚ auths.update_success=è®¤è¯æºå·²ç»æ›´æ–°ã€‚ auths.update=æ›´æ–°è®¤è¯æº auths.delete=åˆ é™¤è®¤è¯æº @@ -3260,10 +3261,10 @@ auths.delete_auth_title=åˆ é™¤èº«ä»½éªŒè¯æº auths.delete_auth_desc=åˆ é™¤ä¸€ä¸ªè®¤è¯æºå°†é˜»æ¢ä½¿ç”¨å®ƒè¿›è¡Œç™»å½•。确认? auths.still_in_used=è®¤è¯æºä»åœ¨ä½¿ç”¨ã€‚è¯·å…ˆè§£é™¤æˆ–è€…åˆ é™¤ä½¿ç”¨æ¤è®¤è¯æºçš„用户。 auths.deletion_success=è®¤è¯æºå·²ç»æ›´æ–°ã€‚ -auths.login_source_exist=è®¤è¯æº '%s' å·²ç»å˜åœ¨ã€‚ +auths.login_source_exist=è®¤è¯æºã€Œ%sã€å·²ç»å˜åœ¨ã€‚ auths.login_source_of_type_exist=æ¤ç±»åž‹çš„è®¤è¯æºå·²å˜åœ¨ã€‚ -auths.unable_to_initialize_openid=æ— æ³•åˆå§‹åŒ– OpenID Connect æä¾›å•†ï¼š %s -auths.invalid_openIdConnectAutoDiscoveryURL=æ— æ•ˆçš„ Auto Discovery URL (这必须是一个以 http:// 或 https://开头的有效的 URL) +auths.unable_to_initialize_openid=æ— æ³•åˆå§‹åŒ– OpenID Connect æä¾›å•†ï¼š%s +auths.invalid_openIdConnectAutoDiscoveryURL=æ— æ•ˆçš„è‡ªåŠ¨å‘现 URL(这必须是一个以 http:// 或 https:// 开头的有效的 URL) config.server_config=æœåС噍é…ç½® config.app_name=站点åç§° @@ -3309,11 +3310,11 @@ config.db_ssl_mode=SSL config.db_path=æ•°æ®åº“路径 config.service_config=æœåŠ¡é…ç½® -config.register_email_confirm=需è¦ç”µå邮件确认注册 +config.register_email_confirm=需è¦é‚®ä»¶ç¡®è®¤æ³¨å†Œ config.disable_register=ç¦æ¢ç”¨æˆ·æ³¨å†Œ -config.allow_only_internal_registration=åªå…许通过 Gitea 进行注册 +config.allow_only_internal_registration=ä»…å…许通过 Gitea 进行注册 config.allow_only_external_registration=ä»…å…许通过外部æœåŠ¡æ³¨å†Œ -config.enable_openid_signup=å¯ç”¨ OpenID 自注册 +config.enable_openid_signup=å¯ç”¨ OpenID 自助注册 config.enable_openid_signin=å¯ç”¨ OpenID 登录 config.show_registration_button=显示注册按钮 config.require_sign_in_view=å¯ç”¨ç™»å½•访问é™åˆ¶ @@ -3321,12 +3322,12 @@ config.mail_notify=å¯ç”¨é‚®ä»¶é€šçŸ¥ config.enable_captcha=å¯ç”¨ç™»å½•验è¯ç config.active_code_lives=激活用户链接有效期 config.reset_password_code_lives=æ¢å¤è´¦æˆ·éªŒè¯ç 过期时间 -config.default_keep_email_private=默认情况下éšè—电åé‚®ä»¶åœ°å€ +config.default_keep_email_private=默认éšè—é‚®ç®±åœ°å€ config.default_allow_create_organization=默认情况下å…许创建组织 config.enable_timetracking=å¯ç”¨æ—¶é—´è·Ÿè¸ª config.default_enable_timetracking=默认情况下å¯ç”¨æ—¶é—´è·Ÿè¸ª config.default_allow_only_contributors_to_track_time=ä»…å…许æˆå‘˜è·Ÿè¸ªæ—¶é—´ -config.no_reply_address=éšè—电å邮件域 +config.no_reply_address=éšè—邮件域 config.default_visibility_organization=新组织的默认å¯è§æ€§ config.default_enable_dependencies=默认情况下å¯ç”¨å·¥å•ä¾èµ– @@ -3337,7 +3338,7 @@ config.skip_tls_verify=跳过 TLS éªŒè¯ config.mailer_config=Mailer é…ç½® config.mailer_enabled=å¯ç”¨æœåŠ¡ -config.mailer_enable_helo=å¯ç”¨HELO +config.mailer_enable_helo=å¯ç”¨ HELO config.mailer_name=任务åç§° config.mailer_protocol=åè®® config.mailer_smtp_addr=SMTP åœ°å€ @@ -3351,8 +3352,8 @@ config.mailer_use_dummy=Dummy config.test_email_placeholder=电åé‚®å€ (例如,test@example.com) config.send_test_mail=å‘逿µ‹è¯•邮件 config.send_test_mail_submit=å‘é€ -config.test_mail_failed=å‘逿µ‹è¯•邮件至 '%s' 时失败:%v -config.test_mail_sent=测试邮件已ç»å‘é€è‡³ '%s'。 +config.test_mail_failed=å‘逿µ‹è¯•邮件至「%sã€å¤±è´¥ï¼š%v +config.test_mail_sent=测试邮件已ç»å‘é€è‡³ã€Œ%sã€ã€‚ config.oauth_config=OAuth é…ç½® config.oauth_enabled=å¯ç”¨ @@ -3364,7 +3365,7 @@ config.cache_conn=Cache 连接å—符串 config.cache_item_ttl=缓å˜é¡¹ç›® TTL config.cache_test=æµ‹è¯•ç¼“å˜ config.cache_test_failed=ç¼“å˜æµ‹è¯•失败: %v。 -config.cache_test_slow=ç¼“å˜æµ‹è¯•æˆåŠŸï¼Œä½†å“应缓慢: %s。 +config.cache_test_slow=ç¼“å˜æµ‹è¯•æˆåŠŸï¼Œä½†å“应缓慢:%s。 config.cache_test_succeeded=ç¼“å˜æµ‹è¯•æˆåŠŸï¼Œåœ¨ %s 时间内得到å“应。 config.session_config=Session é…ç½® @@ -3405,7 +3406,7 @@ config.set_setting_failed=设置 %s 失败 monitor.stats=统计 -monitor.cron=Cron 任务 +monitor.cron=计划任务 monitor.name=任务åç§° monitor.schedule=任务安排 monitor.next=下次执行时间 @@ -3426,7 +3427,7 @@ monitor.process.cancel_desc=䏿¢ä¸€ä¸ªè¿›ç¨‹å¯èƒ½å¯¼è‡´æ•°æ®ä¸¢å¤± monitor.process.children=å进程 monitor.queues=队列 -monitor.queue=队列: %s +monitor.queue=队列:%s monitor.queue.name=åç§° monitor.queue.type=类型 monitor.queue.exemplar=æ•°æ®ç±»åž‹ @@ -3443,7 +3444,7 @@ monitor.queue.settings.maxnumberworkers.error=æœ€å¤§å·¥ä½œè€…æ•°å¿…é¡»æ˜¯æ•°å— monitor.queue.settings.submit=更新设置 monitor.queue.settings.changed=设置已更新 monitor.queue.settings.remove_all_items=移除全部 -monitor.queue.settings.remove_all_items_done=队列ä¸çš„æ‰€æœ‰é¡¹ç›®å·²è¢«ç§»é™¤ã€‚ +monitor.queue.settings.remove_all_items_done=队列ä¸çš„æ‰€æœ‰é¡¹ç›®å·²ç§»é™¤ã€‚ notices.system_notice_list=系统æç¤ºç®¡ç† notices.view_detail_header=查看æç¤ºè¯¦æƒ… @@ -3458,16 +3459,16 @@ notices.type_1=仓库 notices.type_2=任务 notices.desc=æç¤ºæè¿° notices.op=æ“作 -notices.delete_success=ç³»ç»Ÿé€šçŸ¥å·²è¢«åˆ é™¤ã€‚ +notices.delete_success=ç³»ç»Ÿé€šçŸ¥å·²åˆ é™¤ã€‚ self_check.no_problem_found=尚未å‘现问题。 self_check.startup_warnings=å¯åЍè¦å‘Šï¼š self_check.database_collation_mismatch=期望数æ®åº“ä½¿ç”¨çš„æ ¡éªŒæ–¹å¼ï¼š%s -self_check.database_collation_case_insensitive=æ•°æ®åº“æ£åœ¨ä½¿ç”¨ä¸€ä¸ªæ ¡éªŒ %s, è¿™æ˜¯ä¸€ä¸ªä¸æ•æ„Ÿçš„æ ¡éªŒ. 虽然Giteaå¯ä»¥ä¸Žå®ƒåˆä½œï¼Œä½†å¯èƒ½æœ‰ä¸€äº›ç½•è§çš„æƒ…况ä¸å¦‚é¢„æœŸçš„é‚£æ ·èµ·ä½œç”¨ã€‚ -self_check.database_inconsistent_collation_columns=æ•°æ®åº“æ£åœ¨ä½¿ç”¨%s的排åºè§„则,但是这些列使用了ä¸åŒ¹é…的排åºè§„则。这å¯èƒ½ä¼šé€ æˆä¸€äº›æ„外问题。 -self_check.database_fix_mysql=对于MySQL/MariaDB用户,您å¯ä»¥ä½¿ç”¨â€œgitea doctor convertâ€å‘½ä»¤æ¥è§£å†³æ ¡éªŒé—®é¢˜ã€‚ 或者您也å¯ä»¥é€šè¿‡ "ALTER ... COLLATE ..." è¿™æ ·çš„SQL æ¥æ‰‹åŠ¨è§£å†³è¿™ä¸ªé—®é¢˜ã€‚ -self_check.database_fix_mssql=对于MSSQL用户,您现在åªèƒ½é€šè¿‡"ALTER ... COLLATE ..."SQLs手动解决这个问题。 -self_check.location_origin_mismatch=å½“å‰ URL (%[1]s) 与 Gitea çš„ URL (%[2]s) ä¸åŒ¹é… 。 如果您æ£åœ¨ä½¿ç”¨åå‘代ç†ï¼Œè¯·ç¡®ä¿è®¾ç½®æ£ç¡®çš„“主机â€å’Œâ€œX-转å‘-åŽŸå§‹â€æ ‡é¢˜ã€‚ +self_check.database_collation_case_insensitive=æ•°æ®åº“æ£åœ¨ä½¿ç”¨ä¸€ä¸ªæ ¡éªŒ %sï¼Œè¿™æ˜¯ä¸€ä¸ªä¸æ•æ„Ÿçš„æ ¡éªŒã€‚è™½ç„¶ Gitea å¯ä»¥ä¸Žå®ƒåˆä½œï¼Œä½†å¯èƒ½æœ‰ä¸€äº›ç½•è§çš„æƒ…况ä¸å¦‚é¢„æœŸçš„é‚£æ ·èµ·ä½œç”¨ã€‚ +self_check.database_inconsistent_collation_columns=æ•°æ®åº“æ£åœ¨ä½¿ç”¨ %s 的排åºè§„则,但是这些列使用了ä¸åŒ¹é…的排åºè§„则。这å¯èƒ½ä¼šé€ æˆä¸€äº›æ„外问题。 +self_check.database_fix_mysql=对于 MySQL/MariaDB 用户,您å¯ä»¥ä½¿ç”¨ã€Œgitea doctor convertã€å‘½ä»¤æ¥è§£å†³æ ¡éªŒé—®é¢˜ã€‚ 或者您也å¯ä»¥é€šè¿‡ã€ŒALTER ... COLLATE ...ã€è¿™æ ·çš„ SQL æ¥æ‰‹åŠ¨è§£å†³è¿™ä¸ªé—®é¢˜ã€‚ +self_check.database_fix_mssql=对于 MSSQL 用户,您现在åªèƒ½é€šè¿‡ã€ŒALTER ... COLLATE ...ã€SQL 手动解决这个问题。 +self_check.location_origin_mismatch=å½“å‰ URL(%[1]s)与 Gitea çš„ URL(%[2]s)ä¸åŒ¹é… 。 如果您æ£åœ¨ä½¿ç”¨åå‘代ç†ï¼Œè¯·ç¡®ä¿è®¾ç½®æ£ç¡®çš„「Hostã€å’Œã€ŒX-Forwarded-Protoã€æ ‡å¤´ã€‚ [action] create_repo=创建了仓库 <a href="%s">%s</a> @@ -3546,7 +3547,7 @@ no_subscriptions=æ— è®¢é˜… default_key=使用默认密钥ç¾å error.extract_sign=æ— æ³•æå–ç¾å error.generate_hash=æ— æ³•ç”Ÿæˆæäº¤çš„å“ˆå¸Œ -error.no_committer_account=æ²¡æœ‰å¸æˆ·é“¾æŽ¥åˆ°æäº¤è€…的电å邮件 +error.no_committer_account=æ²¡æœ‰å¸æˆ·é“¾æŽ¥åˆ°æäº¤è€…的邮箱 error.no_gpg_keys_found=找ä¸åˆ°æ¤ç¾å对应的密钥 error.not_signed_commit=未ç¾åçš„æäº¤ error.failed_retrieval_gpg_keys=找ä¸åˆ°ä»»ä½•与该æäº¤è€…è´¦å·ç›¸å…³çš„密钥 @@ -3672,11 +3673,11 @@ rubygems.dependencies.development=å¼€å‘ä¾èµ– rubygems.required.ruby=éœ€è¦ Ruby 版本 rubygems.required.rubygems=éœ€è¦ RubyGem 版本 swift.registry=ä»Žå‘½ä»¤è¡Œè®¾ç½®æ¤æ³¨å†Œä¸å¿ƒï¼š -swift.install=åœ¨ä½ çš„ <code>Package.swift</code>æ–‡ä»¶ä¸æ·»åŠ è¯¥åŒ…ï¼š +swift.install=在您的 <code>Package.swift</code>æ–‡ä»¶ä¸æ·»åŠ è¯¥åŒ…ï¼š swift.install2=å¹¶è¿è¡Œä»¥ä¸‹å‘½ä»¤ï¼š vagrant.install=è‹¥è¦æ·»åŠ ä¸€ä¸ª Vagrant box,请è¿è¡Œä»¥ä¸‹å‘½ä»¤ï¼š settings.link=å°†æ¤è½¯ä»¶åŒ…链接到仓库 -settings.link.description=如果您将一个软件包与一个代ç 库链接起æ¥ï¼Œè½¯ä»¶åŒ…将显示在代ç 库的软件包列表ä¸ã€‚ +settings.link.description=如果您将一个软件包与一个仓库链接起æ¥ï¼Œè½¯ä»¶åŒ…将显示在仓库的软件包列表ä¸ã€‚ settings.link.select=选择仓库 settings.link.button=更新仓库链接 settings.link.success=仓库链接已æˆåŠŸæ›´æ–°ã€‚ @@ -3684,13 +3685,13 @@ settings.link.error=更新仓库链接失败。 settings.delete=åˆ é™¤è½¯ä»¶åŒ… settings.delete.description=åˆ é™¤è½¯ä»¶åŒ…æ˜¯æ°¸ä¹…æ€§çš„ï¼Œæ— æ³•æ’¤æ¶ˆã€‚ settings.delete.notice=您将è¦åˆ 除 %s (%s)ã€‚æ¤æ“作是ä¸å¯é€†çš„,您确定å—? -settings.delete.success=è½¯ä»¶åŒ…å·²è¢«åˆ é™¤ã€‚ +settings.delete.success=è½¯ä»¶åŒ…å·²åˆ é™¤ã€‚ settings.delete.error=åˆ é™¤è½¯ä»¶åŒ…å¤±è´¥ã€‚ owner.settings.cargo.title=Cargo 注册ä¸å¿ƒç´¢å¼• owner.settings.cargo.initialize=åˆå§‹åŒ–索引 -owner.settings.cargo.initialize.description=使用 Cargo 注册ä¸å¿ƒæ—¶éœ€è¦ä¸€ä¸ªç‰¹æ®Šç´¢å¼•çš„ Git 仓库。使用æ¤é€‰é¡¹å°†ï¼ˆé‡æ–°ï¼‰åˆ›å»ºå˜å‚¨åº“并自动é…置它。 -owner.settings.cargo.initialize.error=åˆå§‹åŒ–Cargo索引失败: %v -owner.settings.cargo.initialize.success=Cargoç´¢å¼•å·²ç»æˆåŠŸåˆ›å»ºã€‚ +owner.settings.cargo.initialize.description=使用 Cargo 注册ä¸å¿ƒæ—¶éœ€è¦ä¸€ä¸ªç‰¹æ®Šç´¢å¼•çš„ Git 仓库。使用æ¤é€‰é¡¹å°†ï¼ˆé‡æ–°ï¼‰åˆ›å»ºä»“库并自动é…置它。 +owner.settings.cargo.initialize.error=åˆå§‹åŒ– Cargo 索引失败: %v +owner.settings.cargo.initialize.success=Cargo ç´¢å¼•å·²ç»æˆåŠŸåˆ›å»ºã€‚ owner.settings.cargo.rebuild=é‡å»ºç´¢å¼• owner.settings.cargo.rebuild.description=如果索引与å˜å‚¨çš„ Cargo 包ä¸åŒæ¥ï¼Œé‡å»ºå¯èƒ½ä¼šæœ‰ç”¨ã€‚ owner.settings.cargo.rebuild.error=æ— æ³•é‡å»º Cargo 索引: %v @@ -3721,30 +3722,30 @@ owner.settings.chef.keypair.description=需è¦å¯†é’¥å¯¹æ‰èƒ½å‘ Chef 注册ä¸å [secrets] secrets=密钥 -description=Secrets å°†è¢«ä¼ ç»™ç‰¹å®šçš„ Actions,其它情况将ä¸èƒ½è¯»å– +description=å¯†é’¥å°†è¢«ä¼ ç»™ç‰¹å®šçš„å·¥ä½œæµï¼Œå…¶å®ƒæƒ…å†µæ— æ³•è¯»å–。 none=还没有密钥。 ; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation creation.description=组织æè¿° -creation.name_placeholder=ä¸åŒºåˆ†å¤§å°å†™ï¼Œå—æ¯æ•°å—或下划线ä¸èƒ½ä»¥GITEA_ 或 GITHUB_ 开头。 -creation.value_placeholder=输入任何内容,开头和结尾的空白都会被çœç•¥ -creation.description_placeholder=è¾“å…¥ç®€çŸæè¿°(å¯é€‰)。 +creation.name_placeholder=ä¸åŒºåˆ†å¤§å°å†™ï¼Œä»…é™å—æ¯æ•°å—或下划线且ä¸èƒ½ä»¥ GITEA_ 或 GITHUB_ 开头 +creation.value_placeholder=输入任何内容,开头和结尾的空白将会被忽略。 +creation.description_placeholder=è¾“å…¥ç®€çŸæè¿°ï¼ˆå¯é€‰ï¼‰ã€‚ -save_success=密钥 '%s' ä¿å˜æˆåŠŸã€‚ +save_success=密钥「%sã€ä¿å˜æˆåŠŸã€‚ save_failed=密钥ä¿å˜å¤±è´¥ã€‚ add_secret=æ·»åŠ å¯†é’¥ edit_secret=编辑密钥 deletion=åˆ é™¤å¯†é’¥ -deletion.description=åˆ é™¤å¯†é’¥æ˜¯æ°¸ä¹…æ€§çš„ï¼Œæ— æ³•æ’¤æ¶ˆã€‚ç»§ç»å—? -deletion.success=æ¤Secretå·²è¢«åˆ é™¤ã€‚ +deletion.description=åˆ é™¤å¯†é’¥æ˜¯æ°¸ä¹…æ€§çš„ä¸”æ— æ³•æ’¤æ¶ˆã€‚ç»§ç»ï¼Ÿ +deletion.success=æ¤å¯†é’¥å·²åˆ 除。 deletion.failed=åˆ é™¤å¯†é’¥å¤±è´¥ã€‚ management=å¯†é’¥ç®¡ç† [actions] -actions=Actions +actions=å·¥ä½œæµ -unit.desc=管ç†Actions +unit.desc=管ç†å·¥ä½œæµ status.unknown=未知 status.waiting=ç‰å¾…ä¸ @@ -3755,9 +3756,9 @@ status.cancelled=已喿¶ˆ status.skipped=已忽略 status.blocked=é˜»å¡žä¸ -runners=Runners -runners.runner_manage_panel=Runnersç®¡ç† -runners.new=创建 Runner +runners=è¿è¡Œå™¨ +runners.runner_manage_panel=è¿è¡Œå™¨ç®¡ç† +runners.new=创建新è¿è¡Œå™¨ runners.new_notice=如何å¯åŠ¨ä¸€ä¸ªè¿è¡Œå™¨ runners.status=çŠ¶æ€ runners.id=ID @@ -3766,9 +3767,9 @@ runners.owner_type=类型 runners.description=æè¿° runners.labels=æ ‡ç¾ runners.last_online=上次在线时间 -runners.runner_title=Runner -runners.task_list=最近在æ¤runner上的任务 -runners.task_list.no_tasks=还没有任务。 +runners.runner_title=è¿è¡Œå™¨ +runners.task_list=最近在æ¤è¿è¡Œå™¨ä¸Šçš„任务 +runners.task_list.no_tasks=ç›®å‰è¿˜æ²¡æœ‰ä»»åŠ¡ã€‚ runners.task_list.run=执行 runners.task_list.status=çŠ¶æ€ runners.task_list.repository=仓库 @@ -3777,13 +3778,13 @@ runners.task_list.done_at=完æˆäºŽ runners.edit_runner=编辑è¿è¡Œå™¨ runners.update_runner=更新更改 runners.update_runner_success=è¿è¡Œå™¨æ›´æ–°æˆåŠŸ -runners.update_runner_failed=æ›´æ–°è¿è¡Œå™¨å¤±è´¥ -runners.delete_runner=åˆ é™¤è¿è¡Œå™¨ +runners.update_runner_failed=è¿è¡Œå™¨æ›´æ–°å¤±è´¥ +runners.delete_runner=åˆ é™¤æ¤è¿è¡Œå™¨ runners.delete_runner_success=è¿è¡Œå™¨åˆ 除æˆåŠŸ -runners.delete_runner_failed=åˆ é™¤è¿è¡Œå™¨å¤±è´¥ +runners.delete_runner_failed=è¿è¡Œå™¨åˆ 除失败 runners.delete_runner_header=确认è¦åˆ 除æ¤è¿è¡Œå™¨ runners.delete_runner_notice=如果一个任务æ£åœ¨è¿è¡Œåœ¨æ¤è¿è¡Œå™¨ä¸Šï¼Œå®ƒå°†è¢«ç»ˆæ¢å¹¶æ ‡è®°ä¸ºå¤±è´¥ã€‚它å¯èƒ½ä¼šæ‰“æ–æ£åœ¨æž„建的工作æµã€‚ -runners.none=æ— å¯ç”¨çš„ Runner +runners.none=æ— å¯ç”¨è¿è¡Œå™¨ runners.status.unspecified=未知 runners.status.idle=空闲 runners.status.active=å¯ç”¨ @@ -3797,8 +3798,8 @@ runs.all_workflows=æ‰€æœ‰å·¥ä½œæµ runs.commit=æäº¤ runs.scheduled=已计划的 runs.pushed_by=推é€è€… -runs.invalid_workflow_helper=工作æµé…ç½®æ–‡ä»¶æ— æ•ˆã€‚è¯·æ£€æŸ¥æ‚¨çš„é…置文件: %s -runs.no_matching_online_runner_helper=æ²¡æœ‰åŒ¹é…æ ‡ç¾çš„在线 runner: %s +runs.invalid_workflow_helper=工作æµé…ç½®æ–‡ä»¶æ— æ•ˆã€‚è¯·æ£€æŸ¥æ‚¨çš„é…置文件:%s +runs.no_matching_online_runner_helper=æ²¡æœ‰åŒ¹é… %s æ ‡ç¾çš„在线è¿è¡Œå™¨ runs.no_job_without_needs=工作æµå¿…须包å«è‡³å°‘一个没有ä¾èµ–关系的作业。 runs.no_job=工作æµå¿…须包å«è‡³å°‘一个作业 runs.actor=æ“作者 @@ -3807,27 +3808,27 @@ runs.actors_no_select=所有æ“作者 runs.status_no_select=æ‰€æœ‰çŠ¶æ€ runs.no_results=没有匹é…的结果。 runs.no_workflows=ç›®å‰è¿˜æ²¡æœ‰å·¥ä½œæµã€‚ -runs.no_workflows.quick_start=ä¸çŸ¥é“如何使用 Gitea Actionså—?请查看 <a target="_blank" rel="noopener noreferrer" href="%s">快速å¯åŠ¨æŒ‡å—</a>。 -runs.no_workflows.documentation=关于Gitea Actions的更多信æ¯ï¼Œè¯·å‚阅 <a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。 +runs.no_workflows.quick_start=ä¸çŸ¥é“如何使用 Gitea 工作æµå—?请查看<a target="_blank" rel="noopener noreferrer" href="%s">快速开始指å—</a>。 +runs.no_workflows.documentation=关于 Gitea 工作æµçš„æ›´å¤šä¿¡æ¯ï¼Œè¯·å‚阅<a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。 runs.no_runs=工作æµå°šæœªè¿è¡Œè¿‡ã€‚ -runs.empty_commit_message=(空白的æäº¤æ¶ˆæ¯) -runs.expire_log_message=旧的日志已被清除 +runs.empty_commit_message=(空白的æäº¤æ¶ˆæ¯ï¼‰ +runs.expire_log_message=旧的日志已清除。 runs.delete=åˆ é™¤å·¥ä½œæµè¿è¡Œ runs.delete.description=æ‚¨ç¡®å®šè¦æ°¸ä¹…åˆ é™¤æ¤å·¥ä½œæµè¿è¡Œå—ï¼Ÿæ¤æ“ä½œæ— æ³•æ’¤æ¶ˆã€‚ -runs.not_done=æ¤å·¥ä½œæµè¿è¡Œæœªå®Œæˆã€‚ +runs.not_done=æ¤å·¥ä½œæµè¿è¡Œå°šæœªå®Œæˆã€‚ runs.view_workflow_file=æŸ¥çœ‹å·¥ä½œæµæ–‡ä»¶ workflow.disable=ç¦ç”¨å·¥ä½œæµ -workflow.disable_success=å·¥ä½œæµ '%s' å·²æˆåŠŸç¦ç”¨ã€‚ +workflow.disable_success=工作æµã€Œ%sã€å·²æˆåŠŸç¦ç”¨ã€‚ workflow.enable=å¯ç”¨å·¥ä½œæµ -workflow.enable_success=å·¥ä½œæµ '%s' å·²æˆåŠŸå¯ç”¨ã€‚ +workflow.enable_success=工作æµã€Œ%sã€å·²æˆåŠŸå¯ç”¨ã€‚ workflow.disabled=工作æµå·²ç¦ç”¨ã€‚ workflow.run=è¿è¡Œå·¥ä½œæµ -workflow.not_found=å·¥ä½œæµ %s 未找到。 -workflow.run_success=å·¥ä½œæµ %s å·²æˆåŠŸè¿è¡Œã€‚ +workflow.not_found=工作æµã€Œ%sã€æœªæ‰¾åˆ°ã€‚ +workflow.run_success=工作æµã€Œ%sã€å·²æˆåŠŸè¿è¡Œã€‚ workflow.from_ref=使用工作æµä»Ž -workflow.has_workflow_dispatch=æ¤ Workflow 有一个 Workflow_dispatch 事件触å‘器。 -workflow.has_no_workflow_dispatch=å·¥ä½œæµ %s 没有 workflow_dispatch 事件的触å‘器。 +workflow.has_workflow_dispatch=æ¤å·¥ä½œæµæœ‰ä¸€ä¸ª workflow_dispatch 事件触å‘器。 +workflow.has_no_workflow_dispatch=工作æµã€Œ%sã€æ²¡æœ‰ workflow_dispatch 事件触å‘器。 need_approval_desc=该工作æµç”±æ´¾ç”Ÿä»“库的åˆå¹¶è¯·æ±‚所触å‘ï¼Œéœ€è¦æ‰¹å‡†æ–¹å¯è¿è¡Œã€‚ @@ -3837,15 +3838,15 @@ variables.creation=æ·»åŠ å˜é‡ variables.none=ç›®å‰è¿˜æ²¡æœ‰å˜é‡ã€‚ variables.deletion=åˆ é™¤å˜é‡ variables.deletion.description=åˆ é™¤å˜é‡æ˜¯æ°¸ä¹…æ€§çš„ï¼Œæ— æ³•æ’¤æ¶ˆã€‚ç»§ç»å—? -variables.description=å˜é‡å°†è¢«ä¼ 给特定的 Actions,其它情况将ä¸èƒ½è¯»å– +variables.description=å˜é‡å°†è¢«ä¼ 给特定的工作æµï¼Œå…¶å®ƒæƒ…å†µä¸‹æ— æ³•è¯»å–。 variables.id_not_exist=ID为 %d çš„å˜é‡ä¸å˜åœ¨ã€‚ variables.edit=编辑å˜é‡ -variables.deletion.failed=åˆ é™¤å˜é‡å¤±è´¥ã€‚ -variables.deletion.success=å˜é‡å·²è¢«åˆ 除。 -variables.creation.failed=æ·»åŠ å˜é‡å¤±è´¥ã€‚ -variables.creation.success=å˜é‡ “%sâ€ æ·»åŠ æˆåŠŸã€‚ -variables.update.failed=编辑å˜é‡å¤±è´¥ã€‚ -variables.update.success=该å˜é‡å·²è¢«ç¼–辑。 +variables.deletion.failed=å˜é‡åˆ 除失败。 +variables.deletion.success=å˜é‡å·²åˆ 除。 +variables.creation.failed=å˜é‡æ·»åŠ å¤±è´¥ã€‚ +variables.creation.success=å˜é‡ã€Œ%sã€æ·»åŠ æˆåŠŸã€‚ +variables.update.failed=å˜é‡ç¼–辑失败。 +variables.update.success=å˜é‡å·²ç¼–辑。 logs.always_auto_scroll=总是自动滚动日志 logs.always_expand_running=总是展开è¿è¡Œæ—¥å¿— diff --git a/package-lock.json b/package-lock.json index 780b95d592..8a2f3e0db7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,16 +37,16 @@ "license-checker-webpack-plugin": "0.2.1", "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", - "minimatch": "10.0.1", + "minimatch": "10.0.2", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.1", "perfect-debounce": "1.0.0", - "postcss": "8.5.4", + "postcss": "8.5.5", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.1", + "postcss-nesting": "13.0.2", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.24.0", + "swagger-ui-dist": "5.24.1", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -66,7 +66,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", - "@playwright/test": "1.52.0", + "@playwright/test": "1.53.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin-js": "3.1.0", "@stylistic/stylelint-plugin": "3.1.2", @@ -80,25 +80,25 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.4", - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", "@vitejs/plugin-vue": "5.2.4", - "@vitest/eslint-plugin": "1.2.1", + "@vitest/eslint-plugin": "1.2.2", "eslint": "8.57.0", "eslint-import-resolver-typescript": "4.4.3", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.15.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", - "eslint-plugin-regexp": "2.8.0", + "eslint-plugin-regexp": "2.9.0", "eslint-plugin-sonarjs": "3.0.2", "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.2.0", "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", - "happy-dom": "17.6.3", + "happy-dom": "18.0.1", "markdownlint-cli": "0.45.0", "material-icon-theme": "5.23.0", "nolyfill": "1.0.44", @@ -113,7 +113,7 @@ "type-fest": "4.41.0", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.2.2", + "vitest": "3.2.3", "vue-tsc": "2.2.10" }, "engines": { @@ -1054,10 +1054,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1177,10 +1184,17 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1546,9 +1560,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", - "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", "dev": true, "license": "MIT", "optional": true, @@ -1624,13 +1638,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", - "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0.tgz", + "integrity": "sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.52.0" + "playwright": "1.53.0" }, "bin": { "playwright": "cli.js" @@ -1706,9 +1720,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", - "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", "cpu": [ "arm" ], @@ -1720,9 +1734,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", - "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", "cpu": [ "arm64" ], @@ -1734,9 +1748,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", - "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", "cpu": [ "arm64" ], @@ -1748,9 +1762,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", - "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", "cpu": [ "x64" ], @@ -1762,9 +1776,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", - "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", "cpu": [ "arm64" ], @@ -1776,9 +1790,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", - "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", "cpu": [ "x64" ], @@ -1790,9 +1804,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", - "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", "cpu": [ "arm" ], @@ -1804,9 +1818,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", - "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", "cpu": [ "arm" ], @@ -1818,9 +1832,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", - "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", "cpu": [ "arm64" ], @@ -1832,9 +1846,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", - "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", "cpu": [ "arm64" ], @@ -1846,9 +1860,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", - "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", "cpu": [ "loong64" ], @@ -1860,9 +1874,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", - "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", "cpu": [ "ppc64" ], @@ -1874,9 +1888,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", - "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", "cpu": [ "riscv64" ], @@ -1888,9 +1902,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", - "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", "cpu": [ "riscv64" ], @@ -1902,9 +1916,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", - "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", "cpu": [ "s390x" ], @@ -1916,9 +1930,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", - "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", "cpu": [ "x64" ], @@ -1930,9 +1944,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", - "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", "cpu": [ "x64" ], @@ -1944,9 +1958,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", - "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", "cpu": [ "arm64" ], @@ -1958,9 +1972,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", - "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", "cpu": [ "ia32" ], @@ -1972,9 +1986,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", - "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", "cpu": [ "x64" ], @@ -2221,10 +2235,17 @@ "node": "^12.20 || >=14.13" } }, + "node_modules/@stoplight/spectral-core/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/@stoplight/spectral-core/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3005,12 +3026,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", + "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/normalize-package-data": { @@ -3167,18 +3188,25 @@ "node": ">= 8" } }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", + "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/type-utils": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3192,7 +3220,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3208,16 +3236,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", + "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4" }, "engines": { @@ -3233,14 +3261,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", + "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.34.0", + "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "engines": { @@ -3255,14 +3283,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", + "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3273,9 +3301,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", + "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", "dev": true, "license": "MIT", "engines": { @@ -3290,14 +3318,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", + "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/utils": "8.34.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3314,9 +3342,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", + "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", "dev": true, "license": "MIT", "engines": { @@ -3328,16 +3356,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", + "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.0", + "@typescript-eslint/tsconfig-utils": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3356,6 +3384,23 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3373,16 +3418,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3397,13 +3442,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", + "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3421,10 +3466,38 @@ "dev": true, "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.0.tgz", + "integrity": "sha512-h1T2c2Di49ekF2TE8ZCoJkb+jwETKUIPDJ/nO3tJBKlLFPu+fyd93f0rGP/BvArKx2k2HlRM4kqkNarj3dvZlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.0.tgz", + "integrity": "sha512-sG1NHtgXtX8owEkJ11yn34vt0Xqzi3k9TJ8zppDmyG8GZV4kVWw44FHwKwHeEFl07uKPeC4ZoyuQaGh5ruJYPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.11.tgz", - "integrity": "sha512-i3/wlWjQJXMh1uiGtiv7k1EYvrrS3L1hdwmWJJiz1D8jWy726YFYPIxQWbEIVPVAgrfRR0XNlLrTQwq17cuCGw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.0.tgz", + "integrity": "sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==", "cpu": [ "arm64" ], @@ -3436,9 +3509,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.11.tgz", - "integrity": "sha512-8XXyFvc6w6kmMmi6VYchZhjd5CDcp+Lv6Cn1YmUme0ypsZ/0Kzd+9ESrWtDrWibKPTgSteDTxp75cvBOY64FQQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.0.tgz", + "integrity": "sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==", "cpu": [ "x64" ], @@ -3450,9 +3523,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.11.tgz", - "integrity": "sha512-0qJBYzP8Qk24CZ05RSWDQUjdiQUeIJGfqMMzbtXgCKl/a5xa6thfC0MQkGIr55LCLd6YmMyO640ifYUa53lybQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.0.tgz", + "integrity": "sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==", "cpu": [ "x64" ], @@ -3464,9 +3537,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.11.tgz", - "integrity": "sha512-1sGwpgvx+WZf0GFT6vkkOm6UJ+mlsVnjw+Yv9esK71idWeRAG3bbpkf3AoY8KIqKqmnzJExi0uKxXpakQ5Pcbg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.0.tgz", + "integrity": "sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==", "cpu": [ "arm" ], @@ -3478,9 +3551,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.11.tgz", - "integrity": "sha512-D/1F/2lTe+XAl3ohkYj51NjniVly8sIqkA/n1aOND3ZMO418nl2JNU95iVa1/RtpzaKcWEsNTtHRogykrUflJg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.0.tgz", + "integrity": "sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==", "cpu": [ "arm" ], @@ -3492,9 +3565,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.11.tgz", - "integrity": "sha512-7vFWHLCCNFLEQlmwKQfVy066ohLLArZl+AV/AdmrD1/pD1FlmqM+FKbtnONnIwbHtgetFUCV/SRi1q4D49aTlw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.0.tgz", + "integrity": "sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==", "cpu": [ "arm64" ], @@ -3506,9 +3579,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.11.tgz", - "integrity": "sha512-tYkGIx8hjWPh4zcn17jLEHU8YMmdP2obRTGkdaB3BguGHh31VCS3ywqC4QjTODjmhhNyZYkj/1Dz/+0kKvg9YA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.0.tgz", + "integrity": "sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==", "cpu": [ "arm64" ], @@ -3520,9 +3593,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.11.tgz", - "integrity": "sha512-6F328QIUev29vcZeRX6v6oqKxfUoGwIIAhWGD8wSysnBYFY0nivp25jdWmAb1GildbCCaQvOKEhCok7YfWkj4Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.0.tgz", + "integrity": "sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==", "cpu": [ "ppc64" ], @@ -3534,9 +3607,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.11.tgz", - "integrity": "sha512-NqhWmiGJGdzbZbeucPZIG9Iav4lyYLCarEnxAceguMx9qlpeEF7ENqYKOwB8Zqk7/CeuYMEcLYMaW2li6HyDzQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.0.tgz", + "integrity": "sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==", "cpu": [ "riscv64" ], @@ -3548,9 +3621,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.11.tgz", - "integrity": "sha512-J2RPIFKMdTrLtBdfR1cUMKl8Gcy05nlQ+bEs/6al7EdWLk9cs3tnDREHZ7mV9uGbeghpjo4i8neNZNx3PYUY9w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.0.tgz", + "integrity": "sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==", "cpu": [ "riscv64" ], @@ -3562,9 +3635,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.11.tgz", - "integrity": "sha512-bDpGRerHvvHdhun7MmFUNDpMiYcJSqWckwAVVRTJf8F+RyqYJOp/mx04PDc7DhpNPeWdnTMu91oZRMV+gGaVcQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.0.tgz", + "integrity": "sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==", "cpu": [ "s390x" ], @@ -3576,9 +3649,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.11.tgz", - "integrity": "sha512-G9U7bVmylzRLma3cK39RBm3guoD1HOvY4o0NS4JNm37AD0lS7/xyMt7kn0JejYyc0Im8J+rH69/dXGM9DAJcSQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.0.tgz", + "integrity": "sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==", "cpu": [ "x64" ], @@ -3590,9 +3663,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.11.tgz", - "integrity": "sha512-7qL20SBKomekSunm7M9Fe5L93bFbn+FbHiGJbfTlp0RKhPVoJDP73vOxf1QrmJHyDPECsGWPFnKa/f8fO2FsHw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.0.tgz", + "integrity": "sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==", "cpu": [ "x64" ], @@ -3604,9 +3677,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.11.tgz", - "integrity": "sha512-jisvIva8MidjI+B1lFRZZMfCPaCISePgTyR60wNT1MeQvIh5Ksa0G3gvI+Iqyj3jqYbvOHByenpa5eDGcSdoSg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.0.tgz", + "integrity": "sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==", "cpu": [ "wasm32" ], @@ -3614,16 +3687,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.10" + "@napi-rs/wasm-runtime": "^0.2.11" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.11.tgz", - "integrity": "sha512-G+H5nQZ8sRZ8ebMY6mRGBBvTEzMYEcgVauLsNHpvTUavZoCCRVP1zWkCZgOju2dW3O22+8seTHniTdl1/uLz3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.0.tgz", + "integrity": "sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==", "cpu": [ "arm64" ], @@ -3635,9 +3708,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.11.tgz", - "integrity": "sha512-Hfy46DBfFzyv0wgR0MMOwFFib2W2+Btc8oE5h4XlPhpelnSyA6nFxkVIyTgIXYGTdFaLoZFNn62fmqx3rjEg3A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.0.tgz", + "integrity": "sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==", "cpu": [ "ia32" ], @@ -3649,9 +3722,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.11.tgz", - "integrity": "sha512-7L8NdsQlCJ8T106Gbz/AjzM4QKWVsoQbKpB9bMBGcIZswUuAnJMHpvbqGW3RBqLHCIwX4XZ5fxSBHEFcK2h9wA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.0.tgz", + "integrity": "sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==", "cpu": [ "x64" ], @@ -3677,9 +3750,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.1.tgz", - "integrity": "sha512-JQr1jdVcrsoS7Sdzn83h9sq4DvREf9Q/onTZbJCqTVlv/76qb+TZrLv/9VhjnjSMHweQH5FdpMDeCR6aDe2fgw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.2.tgz", + "integrity": "sha512-R8NwW+VxyKqVGcMfYsUbdThQyMbtNcoeg+jJeTgMHqWdFdcS0nrODAQXhkplvWzgd7jIJ+GQeydGqFLibsxMxg==", "dev": true, "license": "MIT", "dependencies": { @@ -3700,15 +3773,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.2.tgz", - "integrity": "sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz", + "integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.2", - "@vitest/utils": "3.2.2", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -3717,13 +3790,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.2.tgz", - "integrity": "sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz", + "integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.2", + "@vitest/spy": "3.2.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -3771,9 +3844,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.2.tgz", - "integrity": "sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz", + "integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==", "dev": true, "license": "MIT", "dependencies": { @@ -3784,27 +3857,28 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.2.tgz", - "integrity": "sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz", + "integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.2", - "pathe": "^2.0.3" + "@vitest/utils": "3.2.3", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.2.tgz", - "integrity": "sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz", + "integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.2", + "@vitest/pretty-format": "3.2.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -3823,9 +3897,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.2.tgz", - "integrity": "sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz", + "integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==", "dev": true, "license": "MIT", "dependencies": { @@ -3836,13 +3910,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.2.tgz", - "integrity": "sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz", + "integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.2", + "@vitest/pretty-format": "3.2.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -3974,6 +4048,23 @@ } } }, + "node_modules/@vue/language-core/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@vue/language-core/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4256,9 +4347,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4622,10 +4713,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "license": "MIT", + "engines": { + "node": ">= 16" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -4676,12 +4770,15 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.1.tgz", + "integrity": "sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^3.0.0" + }, + "engines": { + "node": ">= 18" } }, "node_modules/braces": { @@ -4799,9 +4896,9 @@ } }, "node_modules/cacheable": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz", - "integrity": "sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.0.tgz", + "integrity": "sha512-SSgQTAnhd7WlJXnGlIi4jJJOiHzgnM5wRMEPaXAU4kECTAMpBoYKoZ9i5zHmclIEZbxcu3j7yY/CF8DTmwIsHg==", "dev": true, "license": "MIT", "dependencies": { @@ -4810,9 +4907,9 @@ } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", - "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.4.tgz", + "integrity": "sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw==", "dev": true, "license": "MIT", "dependencies": { @@ -4838,9 +4935,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", "funding": [ { "type": "opencollective", @@ -5241,13 +5338,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -6282,9 +6379,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -6817,21 +6914,21 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.15.1.tgz", - "integrity": "sha512-JfVpNg1qMkPD66iaSgmMoSYeUCGS8UFSm3GwHV0IbuV3Knar/SyK5qqCct9+AxoMIzaM+KSO7KK5pOeOkC/3GQ==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.15.2.tgz", + "integrity": "sha512-J5gx7sN6DTm0LRT//eP3rVVQ2Yi4hrX0B+DbWxa5er8PZ6JjLo9GUBwogIFvEDdwJaSqZplpQT+haK/cXhb7VQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/types": "^8.34.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", - "eslint-import-context": "^0.1.7", + "eslint-import-context": "^0.1.8", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.2", "stable-hash-x": "^0.1.1", - "unrs-resolver": "^1.7.10" + "unrs-resolver": "^1.9.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6853,10 +6950,17 @@ } } }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -6940,10 +7044,17 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7051,9 +7162,9 @@ } }, "node_modules/eslint-plugin-regexp": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.8.0.tgz", - "integrity": "sha512-xme90IvkMgdyS+NJC21FM0H6ek4urGsdlIFTXpZRqH2BKJKVSd8hRbyrCpbcqfGBi2jth3eQoLiO3RC1gxZHiw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.9.0.tgz", + "integrity": "sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==", "dev": true, "license": "MIT", "dependencies": { @@ -7093,6 +7204,23 @@ "eslint": "^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-sonarjs/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -7260,9 +7388,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7289,10 +7417,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7352,15 +7487,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7811,10 +7946,16 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7967,19 +8108,37 @@ } }, "node_modules/happy-dom": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz", - "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", + "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==", "dev": true, "license": "MIT", "dependencies": { - "webidl-conversions": "^7.0.0", + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" }, "engines": { "node": ">=20.0.0" } }, + "node_modules/happy-dom/node_modules/@types/node": { + "version": "20.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", + "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/happy-dom/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8880,10 +9039,16 @@ "webpack": "^4.4.0 || ^5.4.0" } }, + "node_modules/license-checker-webpack-plugin/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, "node_modules/license-checker-webpack-plugin/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -9991,12 +10156,12 @@ } }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.2.tgz", + "integrity": "sha512-+9TJCIYXgZ2Dm5LxVCFsa8jOm+evMwXHFI0JM1XROmkfkpz8/iLLDh+TwSmyIBrs6C6Xu9294/fq8cBA+P6AqA==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^4.0.1" }, "engines": { "node": "20 || >=22" @@ -10694,13 +10859,13 @@ } }, "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0.tgz", + "integrity": "sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.52.0" + "playwright-core": "1.53.0" }, "bin": { "playwright": "cli.js" @@ -10713,9 +10878,9 @@ } }, "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz", + "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10762,9 +10927,9 @@ } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", + "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", "funding": [ { "type": "opencollective", @@ -10983,9 +11148,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", - "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", "funding": [ { "type": "github", @@ -10998,7 +11163,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, @@ -12215,6 +12380,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -12424,25 +12602,25 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz", - "integrity": "sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz", + "integrity": "sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.9" + "flat-cache": "^6.1.10" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz", - "integrity": "sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.10.tgz", + "integrity": "sha512-B6/v1f0NwjxzmeOhzfXPGWpKBVA207LS7lehaVKQnFrVktcFRfkzjZZ2gwj2i1TkEUMQht7ZMJbABUT5N+V1Nw==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^1.9.0", + "cacheable": "^1.10.0", "flatted": "^3.3.3", - "hookified": "^1.8.2" + "hookified": "^1.9.1" } }, "node_modules/stylelint/node_modules/ignore": { @@ -12565,6 +12743,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -12779,9 +12972,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.24.0.tgz", - "integrity": "sha512-okwN8vf14TOgBTUyGgCXEAoHnrwwp/042dC00B3kPu2OAe9zD75BtSbLlgAK1Y5e3csJhs+AdnIxJYZN9uvptg==", + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.24.1.tgz", + "integrity": "sha512-ITeWc7CCAfK53u8jnV39UNqStQZjSt+bVYtJHsOEL3vVj/WV9/8HmsF8Ej4oD8r+Xk1HpWyeW/t59r1QNeAcUQ==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -12915,9 +13108,9 @@ } }, "node_modules/terser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.41.0.tgz", - "integrity": "sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz", + "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -13046,9 +13239,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -13268,9 +13461,9 @@ "license": "ISC" }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, "node_modules/universalify": { @@ -13284,9 +13477,9 @@ } }, "node_modules/unrs-resolver": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.11.tgz", - "integrity": "sha512-OhuAzBImFPjKNgZ2JwHMfGFUA6NSbRegd1+BPjC1Y0E6X9Y/vJ4zKeGmIMqmlYboj6cMNEwKI+xQisrg4J0HaQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.0.tgz", + "integrity": "sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13297,23 +13490,25 @@ "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.11", - "@unrs/resolver-binding-darwin-x64": "1.7.11", - "@unrs/resolver-binding-freebsd-x64": "1.7.11", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.11", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.11", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.11", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.11", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.11", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-x64-musl": "1.7.11", - "@unrs/resolver-binding-wasm32-wasi": "1.7.11", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.11", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.11", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.11" + "@unrs/resolver-binding-android-arm-eabi": "1.9.0", + "@unrs/resolver-binding-android-arm64": "1.9.0", + "@unrs/resolver-binding-darwin-arm64": "1.9.0", + "@unrs/resolver-binding-darwin-x64": "1.9.0", + "@unrs/resolver-binding-freebsd-x64": "1.9.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-x64-musl": "1.9.0", + "@unrs/resolver-binding-wasm32-wasi": "1.9.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.0" } }, "node_modules/update-browserslist-db": { @@ -13508,9 +13703,9 @@ } }, "node_modules/vite-node": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.2.tgz", - "integrity": "sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz", + "integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13545,9 +13740,9 @@ "license": "MIT" }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -13588,9 +13783,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", - "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", "dev": true, "license": "MIT", "dependencies": { @@ -13604,44 +13799,44 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.42.0", - "@rollup/rollup-android-arm64": "4.42.0", - "@rollup/rollup-darwin-arm64": "4.42.0", - "@rollup/rollup-darwin-x64": "4.42.0", - "@rollup/rollup-freebsd-arm64": "4.42.0", - "@rollup/rollup-freebsd-x64": "4.42.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", - "@rollup/rollup-linux-arm-musleabihf": "4.42.0", - "@rollup/rollup-linux-arm64-gnu": "4.42.0", - "@rollup/rollup-linux-arm64-musl": "4.42.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-musl": "4.42.0", - "@rollup/rollup-linux-s390x-gnu": "4.42.0", - "@rollup/rollup-linux-x64-gnu": "4.42.0", - "@rollup/rollup-linux-x64-musl": "4.42.0", - "@rollup/rollup-win32-arm64-msvc": "4.42.0", - "@rollup/rollup-win32-ia32-msvc": "4.42.0", - "@rollup/rollup-win32-x64-msvc": "4.42.0", + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", "fsevents": "~2.3.2" } }, "node_modules/vitest": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.2.tgz", - "integrity": "sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz", + "integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.2", - "@vitest/mocker": "3.2.2", - "@vitest/pretty-format": "^3.2.2", - "@vitest/runner": "3.2.2", - "@vitest/snapshot": "3.2.2", - "@vitest/spy": "3.2.2", - "@vitest/utils": "3.2.2", + "@vitest/expect": "3.2.3", + "@vitest/mocker": "3.2.3", + "@vitest/pretty-format": "^3.2.3", + "@vitest/runner": "3.2.3", + "@vitest/snapshot": "3.2.3", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -13655,7 +13850,7 @@ "tinypool": "^1.1.0", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.2", + "vite-node": "3.2.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -13671,8 +13866,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.2", - "@vitest/ui": "3.2.2", + "@vitest/browser": "3.2.3", + "@vitest/ui": "3.2.3", "happy-dom": "*", "jsdom": "*" }, @@ -13846,9 +14041,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -13916,14 +14111,10 @@ } }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/webpack": { "version": "5.99.9", @@ -14104,12 +14295,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 0f12b831fa..96ade30f37 100644 --- a/package.json +++ b/package.json @@ -36,16 +36,16 @@ "license-checker-webpack-plugin": "0.2.1", "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", - "minimatch": "10.0.1", + "minimatch": "10.0.2", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.1", "perfect-debounce": "1.0.0", - "postcss": "8.5.4", + "postcss": "8.5.5", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.1", + "postcss-nesting": "13.0.2", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.24.0", + "swagger-ui-dist": "5.24.1", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -65,7 +65,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", - "@playwright/test": "1.52.0", + "@playwright/test": "1.53.0", "@stoplight/spectral-cli": "6.15.0", "@stylistic/eslint-plugin-js": "3.1.0", "@stylistic/stylelint-plugin": "3.1.2", @@ -79,25 +79,25 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.4", - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", "@vitejs/plugin-vue": "5.2.4", - "@vitest/eslint-plugin": "1.2.1", + "@vitest/eslint-plugin": "1.2.2", "eslint": "8.57.0", "eslint-import-resolver-typescript": "4.4.3", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.15.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", - "eslint-plugin-regexp": "2.8.0", + "eslint-plugin-regexp": "2.9.0", "eslint-plugin-sonarjs": "3.0.2", "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.2.0", "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", - "happy-dom": "17.6.3", + "happy-dom": "18.0.1", "markdownlint-cli": "0.45.0", "material-icon-theme": "5.23.0", "nolyfill": "1.0.44", @@ -112,7 +112,7 @@ "type-fest": "4.41.0", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.2.2", + "vitest": "3.2.3", "vue-tsc": "2.2.10" }, "browserslist": [ diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 95512cb9b6..2c138ad61a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -971,7 +971,8 @@ func Routes() *web.Router { // Misc (public accessible) m.Group("", func() { m.Get("/version", misc.Version) - m.Get("/signing-key.gpg", misc.SigningKey) + m.Get("/signing-key.gpg", misc.SigningKeyGPG) + m.Get("/signing-key.pub", misc.SigningKeySSH) m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup) m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw) @@ -1427,7 +1428,8 @@ func Routes() *web.Router { m.Combo("/file-contents", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()). Get(repo.GetFileContentsGet). Post(bind(api.GetFilesOptions{}), repo.GetFileContentsPost) // POST method requires "write" permission, so we also support "GET" method above - m.Get("/signing-key.gpg", misc.SigningKey) + m.Get("/signing-key.gpg", misc.SigningKeyGPG) + m.Get("/signing-key.pub", misc.SigningKeySSH) m.Group("/topics", func() { m.Combo("").Get(repo.ListTopics). Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics) diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go index 667396e39c..db70e04b8f 100644 --- a/routers/api/v1/misc/signing.go +++ b/routers/api/v1/misc/signing.go @@ -4,14 +4,35 @@ package misc import ( - "fmt" - + "code.gitea.io/gitea/modules/git" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" ) -// SigningKey returns the public key of the default signing key if it exists -func SigningKey(ctx *context.APIContext) { +func getSigningKey(ctx *context.APIContext, expectedFormat string) { + // if the handler is in the repo's route group, get the repo's signing key + // otherwise, get the global signing key + path := "" + if ctx.Repo != nil && ctx.Repo.Repository != nil { + path = ctx.Repo.Repository.RepoPath() + } + content, format, err := asymkey_service.PublicSigningKey(ctx, path) + if err != nil { + ctx.APIErrorInternal(err) + return + } + if format == "" { + ctx.APIErrorNotFound("no signing key") + return + } else if format != expectedFormat { + ctx.APIErrorNotFound("signing key format is " + format) + return + } + _, _ = ctx.Write([]byte(content)) +} + +// SigningKeyGPG returns the public key of the default signing key if it exists +func SigningKeyGPG(ctx *context.APIContext) { // swagger:operation GET /signing-key.gpg miscellaneous getSigningKey // --- // summary: Get default signing-key.gpg @@ -44,19 +65,42 @@ func SigningKey(ctx *context.APIContext) { // description: "GPG armored public key" // schema: // type: string + getSigningKey(ctx, git.SigningKeyFormatOpenPGP) +} - path := "" - if ctx.Repo != nil && ctx.Repo.Repository != nil { - path = ctx.Repo.Repository.RepoPath() - } +// SigningKeySSH returns the public key of the default signing key if it exists +func SigningKeySSH(ctx *context.APIContext) { + // swagger:operation GET /signing-key.pub miscellaneous getSigningKeySSH + // --- + // summary: Get default signing-key.pub + // produces: + // - text/plain + // responses: + // "200": + // description: "ssh public key" + // schema: + // type: string - content, err := asymkey_service.PublicSigningKey(ctx, path) - if err != nil { - ctx.APIErrorInternal(err) - return - } - _, err = ctx.Write([]byte(content)) - if err != nil { - ctx.APIErrorInternal(fmt.Errorf("Error writing key content %w", err)) - } + // swagger:operation GET /repos/{owner}/{repo}/signing-key.pub repository repoSigningKeySSH + // --- + // summary: Get signing-key.pub for given repository + // produces: + // - text/plain + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // description: "ssh public key" + // schema: + // type: string + getSigningKey(ctx, git.SigningKeyFormatSSH) } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 5552a8726c..6602685e94 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -62,7 +62,7 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner) signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) - ctx.Data["SigningKeyAvailable"] = len(signing) > 0 + ctx.Data["SigningKeyAvailable"] = signing != nil ctx.Data["SigningSettings"] = setting.Repository.Signing ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled @@ -105,7 +105,7 @@ func SettingsPost(ctx *context.Context) { ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) - ctx.Data["SigningKeyAvailable"] = len(signing) > 0 + ctx.Data["SigningKeyAvailable"] = signing != nil ctx.Data["SigningSettings"] = setting.Repository.Signing ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go index 105782a93a..148f51fd10 100644 --- a/services/asymkey/commit.go +++ b/services/asymkey/commit.go @@ -6,6 +6,7 @@ package asymkey import ( "context" "fmt" + "slices" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -359,24 +360,39 @@ func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si return nil } +func verifySSHCommitVerificationByInstanceKey(c *git.Commit, committerUser, signerUser *user_model.User, committerGitEmail, publicKeyContent string) *asymkey_model.CommitVerification { + fingerprint, err := asymkey_model.CalcFingerprint(publicKeyContent) + if err != nil { + log.Error("Error calculating the fingerprint public key %q, err: %v", publicKeyContent, err) + return nil + } + sshPubKey := &asymkey_model.PublicKey{ + Verified: true, + Content: publicKeyContent, + Fingerprint: fingerprint, + HasUsed: true, + } + return verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, sshPubKey, committerUser, signerUser, committerGitEmail) +} + // ParseCommitWithSSHSignature check if signature is good against keystore. -func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification { +func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committerUser *user_model.User) *asymkey_model.CommitVerification { // Now try to associate the signature with the committer, if present - if committer.ID != 0 { + if committerUser.ID != 0 { keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ - OwnerID: committer.ID, + OwnerID: committerUser.ID, NotKeytype: asymkey_model.KeyTypePrincipal, }) if err != nil { // Skipping failed to get ssh keys of user log.Error("ListPublicKeys: %v", err) return &asymkey_model.CommitVerification{ - CommittingUser: committer, + CommittingUser: committerUser, Verified: false, Reason: "gpg.error.failed_retrieval_gpg_keys", } } - committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses) + committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committerUser.ID, user_model.GetEmailAddresses) if err != nil { log.Error("GetEmailAddresses: %v", err) } @@ -391,7 +407,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer * for _, k := range keys { if k.Verified && activated { - commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) + commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committerUser, committerUser, c.Committer.Email) if commitVerification != nil { return commitVerification } @@ -399,8 +415,45 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer * } } + // Try the pre-set trusted keys (for key-rotation purpose) + // At the moment, we still use the SigningName&SigningEmail for the rotated keys. + // Maybe in the future we can extend the key format to "ssh-xxx .... old-user@example.com" to support different signer emails. + for _, k := range setting.Repository.Signing.TrustedSSHKeys { + signerUser := &user_model.User{ + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } + commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, c.Committer.Email, k) + if commitVerification != nil && commitVerification.Verified { + return commitVerification + } + } + + // Try the configured instance-wide SSH public key + if setting.Repository.Signing.SigningFormat == git.SigningKeyFormatSSH && !slices.Contains([]string{"", "default", "none"}, setting.Repository.Signing.SigningKey) { + gpgSettings := git.GPGSettings{ + Sign: true, + KeyID: setting.Repository.Signing.SigningKey, + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + Format: setting.Repository.Signing.SigningFormat, + } + signerUser := &user_model.User{ + Name: gpgSettings.Name, + Email: gpgSettings.Email, + } + if err := gpgSettings.LoadPublicKeyContent(); err != nil { + log.Error("Error getting instance-wide SSH signing key %q, err: %v", gpgSettings.KeyID, err) + } else { + commitVerification := verifySSHCommitVerificationByInstanceKey(c, committerUser, signerUser, gpgSettings.Email, gpgSettings.PublicKeyContent) + if commitVerification != nil && commitVerification.Verified { + return commitVerification + } + } + } + return &asymkey_model.CommitVerification{ - CommittingUser: committer, + CommittingUser: committerUser, Verified: false, Reason: asymkey_model.NoKeyFound, } diff --git a/services/asymkey/commit_test.go b/services/asymkey/commit_test.go new file mode 100644 index 0000000000..0438209a61 --- /dev/null +++ b/services/asymkey/commit_test.go @@ -0,0 +1,54 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package asymkey + +import ( + "strings" + "testing" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseCommitWithSSHSignature(t *testing.T) { + // Here we only test the TrustedSSHKeys. The complete signing test is in tests/integration/gpg_ssh_git_test.go + t.Run("TrustedSSHKey", func(t *testing.T) { + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")() + defer test.MockVariableValue(&setting.Repository.Signing.TrustedSSHKeys, []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH6Y4idVaW3E+bLw1uqoAfJD7o5Siu+HqS51E9oQLPE9"})() + + commit, err := git.CommitFromReader(nil, git.Sha1ObjectFormat.EmptyObjectID(), strings.NewReader(`tree 9a93ffa76e8b72bdb6431910b3a506fa2b39f42e +author User Two <user2@example.com> 1749230009 +0200 +committer User Two <user2@example.com> 1749230009 +0200 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgfpjiJ1VpbcT5svDW6qgB8kPujl + KK74epLnUT2hAs8T0AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 + AAAAQDX2t2iHuuLxEWHLJetYXKsgayv3c43r0pJNfAzdLN55Q65pC5M7rG6++gT2bxcpOu + Y6EXbpLqia9sunEF3+LQY= + -----END SSH SIGNATURE----- + +Initial commit with signed file +`)) + require.NoError(t, err) + committingUser := &user_model.User{ + ID: 2, + Name: "User Two", + Email: "user2@example.com", + } + ret := ParseCommitWithSSHSignature(t.Context(), commit, committingUser) + require.NotNil(t, ret) + assert.True(t, ret.Verified) + assert.False(t, ret.Warning) + assert.Equal(t, committingUser, ret.CommittingUser) + if assert.NotNil(t, ret.SigningUser) { + assert.Equal(t, "gitea", ret.SigningUser.Name) + assert.Equal(t, "gitea@fake.local", ret.SigningUser.Email) + } + }) +} diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 2216bca54a..f94462ea46 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -6,6 +6,7 @@ package asymkey import ( "context" "fmt" + "os" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -85,9 +86,9 @@ func IsErrWontSign(err error) bool { } // SigningKey returns the KeyID and git Signature for the repo -func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) { +func SigningKey(ctx context.Context, repoPath string) (*git.SigningKey, *git.Signature) { if setting.Repository.Signing.SigningKey == "none" { - return "", nil + return nil, nil } if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { @@ -95,53 +96,77 @@ func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) { value, _, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { - return "", nil + return nil, nil } + format, _, _ := git.NewCommand("config", "--default", git.SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) signingKey, _, _ := git.NewCommand("config", "--get", "user.signingkey").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) signingName, _, _ := git.NewCommand("config", "--get", "user.name").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) signingEmail, _, _ := git.NewCommand("config", "--get", "user.email").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) - return strings.TrimSpace(signingKey), &git.Signature{ - Name: strings.TrimSpace(signingName), - Email: strings.TrimSpace(signingEmail), + + if strings.TrimSpace(signingKey) == "" { + return nil, nil } + + return &git.SigningKey{ + KeyID: strings.TrimSpace(signingKey), + Format: strings.TrimSpace(format), + }, &git.Signature{ + Name: strings.TrimSpace(signingName), + Email: strings.TrimSpace(signingEmail), + } } - return setting.Repository.Signing.SigningKey, &git.Signature{ - Name: setting.Repository.Signing.SigningName, - Email: setting.Repository.Signing.SigningEmail, + if setting.Repository.Signing.SigningKey == "" { + return nil, nil } + + return &git.SigningKey{ + KeyID: setting.Repository.Signing.SigningKey, + Format: setting.Repository.Signing.SigningFormat, + }, &git.Signature{ + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } } // PublicSigningKey gets the public signing key within a provided repository directory -func PublicSigningKey(ctx context.Context, repoPath string) (string, error) { +func PublicSigningKey(ctx context.Context, repoPath string) (content, format string, err error) { signingKey, _ := SigningKey(ctx, repoPath) - if signingKey == "" { - return "", nil + if signingKey == nil { + return "", "", nil + } + if signingKey.Format == git.SigningKeyFormatSSH { + content, err := os.ReadFile(signingKey.KeyID) + if err != nil { + log.Error("Unable to read SSH public key file in %s: %s, %v", repoPath, signingKey, err) + return "", signingKey.Format, err + } + return string(content), signingKey.Format, nil } content, stderr, err := process.GetManager().ExecDir(ctx, -1, repoPath, - "gpg --export -a", "gpg", "--export", "-a", signingKey) + "gpg --export -a", "gpg", "--export", "-a", signingKey.KeyID) if err != nil { log.Error("Unable to get default signing key in %s: %s, %s, %v", repoPath, signingKey, stderr, err) - return "", err + return "", signingKey.Format, err } - return content, nil + return content, signingKey.Format, nil } // SignInitialCommit determines if we should sign the initial commit to this repository -func SignInitialCommit(ctx context.Context, repoPath string, u *user_model.User) (bool, string, *git.Signature, error) { +func SignInitialCommit(ctx context.Context, repoPath string, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) signingKey, sig := SigningKey(ctx, repoPath) - if signingKey == "" { - return false, "", nil, &ErrWontSign{noKey} + if signingKey == nil { + return false, nil, nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", nil, &ErrWontSign{never} + return false, nil, nil, &ErrWontSign{never} case always: break Loop case pubkey: @@ -150,18 +175,18 @@ Loop: IncludeSubKeys: true, }) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if len(keys) == 0 { - return false, "", nil, &ErrWontSign{pubkey} + return false, nil, nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID) if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { - return false, "", nil, err + return false, nil, nil, err } if twofaModel == nil { - return false, "", nil, &ErrWontSign{twofa} + return false, nil, nil, &ErrWontSign{twofa} } } } @@ -169,19 +194,19 @@ Loop: } // SignWikiCommit determines if we should sign the commits to this repository wiki -func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, string, *git.Signature, error) { +func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) { repoWikiPath := repo.WikiPath() rules := signingModeFromStrings(setting.Repository.Signing.Wiki) signingKey, sig := SigningKey(ctx, repoWikiPath) - if signingKey == "" { - return false, "", nil, &ErrWontSign{noKey} + if signingKey == nil { + return false, nil, nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", nil, &ErrWontSign{never} + return false, nil, nil, &ErrWontSign{never} case always: break Loop case pubkey: @@ -190,35 +215,35 @@ Loop: IncludeSubKeys: true, }) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if len(keys) == 0 { - return false, "", nil, &ErrWontSign{pubkey} + return false, nil, nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID) if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { - return false, "", nil, err + return false, nil, nil, err } if twofaModel == nil { - return false, "", nil, &ErrWontSign{twofa} + return false, nil, nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo()) if err != nil { - return false, "", nil, err + return false, nil, nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit("HEAD") if err != nil { - return false, "", nil, err + return false, nil, nil, err } if commit.Signature == nil { - return false, "", nil, &ErrWontSign{parentSigned} + return false, nil, nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{parentSigned} + return false, nil, nil, &ErrWontSign{parentSigned} } } } @@ -226,18 +251,18 @@ Loop: } // SignCRUDAction determines if we should sign a CRUD commit to this repository -func SignCRUDAction(ctx context.Context, repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) { +func SignCRUDAction(ctx context.Context, repoPath string, u *user_model.User, tmpBasePath, parentCommit string) (bool, *git.SigningKey, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) signingKey, sig := SigningKey(ctx, repoPath) - if signingKey == "" { - return false, "", nil, &ErrWontSign{noKey} + if signingKey == nil { + return false, nil, nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", nil, &ErrWontSign{never} + return false, nil, nil, &ErrWontSign{never} case always: break Loop case pubkey: @@ -246,35 +271,35 @@ Loop: IncludeSubKeys: true, }) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if len(keys) == 0 { - return false, "", nil, &ErrWontSign{pubkey} + return false, nil, nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID) if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { - return false, "", nil, err + return false, nil, nil, err } if twofaModel == nil { - return false, "", nil, &ErrWontSign{twofa} + return false, nil, nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := git.OpenRepository(ctx, tmpBasePath) if err != nil { - return false, "", nil, err + return false, nil, nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit(parentCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if commit.Signature == nil { - return false, "", nil, &ErrWontSign{parentSigned} + return false, nil, nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{parentSigned} + return false, nil, nil, &ErrWontSign{parentSigned} } } } @@ -282,16 +307,16 @@ Loop: } // SignMerge determines if we should sign a PR merge commit to the base repository -func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) { +func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, *git.SigningKey, *git.Signature, error) { if err := pr.LoadBaseRepo(ctx); err != nil { log.Error("Unable to get Base Repo for pull request") - return false, "", nil, err + return false, nil, nil, err } repo := pr.BaseRepo signingKey, signer := SigningKey(ctx, repo.RepoPath()) - if signingKey == "" { - return false, "", nil, &ErrWontSign{noKey} + if signingKey == nil { + return false, nil, nil, &ErrWontSign{noKey} } rules := signingModeFromStrings(setting.Repository.Signing.Merges) @@ -302,7 +327,7 @@ Loop: for _, rule := range rules { switch rule { case never: - return false, "", nil, &ErrWontSign{never} + return false, nil, nil, &ErrWontSign{never} case always: break Loop case pubkey: @@ -311,91 +336,91 @@ Loop: IncludeSubKeys: true, }) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if len(keys) == 0 { - return false, "", nil, &ErrWontSign{pubkey} + return false, nil, nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := auth.GetTwoFactorByUID(ctx, u.ID) if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) { - return false, "", nil, err + return false, nil, nil, err } if twofaModel == nil { - return false, "", nil, &ErrWontSign{twofa} + return false, nil, nil, &ErrWontSign{twofa} } case approved: protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch) if err != nil { - return false, "", nil, err + return false, nil, nil, err } if protectedBranch == nil { - return false, "", nil, &ErrWontSign{approved} + return false, nil, nil, &ErrWontSign{approved} } if issues_model.GetGrantedApprovalsCount(ctx, protectedBranch, pr) < 1 { - return false, "", nil, &ErrWontSign{approved} + return false, nil, nil, &ErrWontSign{approved} } case baseSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(ctx, tmpBasePath) if err != nil { - return false, "", nil, err + return false, nil, nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(baseCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{baseSigned} + return false, nil, nil, &ErrWontSign{baseSigned} } case headSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(ctx, tmpBasePath) if err != nil { - return false, "", nil, err + return false, nil, nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{headSigned} + return false, nil, nil, &ErrWontSign{headSigned} } case commitsSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(ctx, tmpBasePath) if err != nil { - return false, "", nil, err + return false, nil, nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{commitsSigned} + return false, nil, nil, &ErrWontSign{commitsSigned} } // need to work out merge-base mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) if err != nil { - return false, "", nil, err + return false, nil, nil, err } for _, commit := range commitList { verification := ParseCommitWithSignature(ctx, commit) if !verification.Verified { - return false, "", nil, &ErrWontSign{commitsSigned} + return false, nil, nil, &ErrWontSign{commitsSigned} } } } diff --git a/services/context/repo.go b/services/context/repo.go index 61841aa90b..dafa398dc8 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -101,7 +101,7 @@ type CanCommitToBranchResults struct { UserCanPush bool RequireSigned bool WillSign bool - SigningKey string + SigningKey *git.SigningKey WontSignReason string } diff --git a/services/convert/pull.go b/services/convert/pull.go index 7798bebb08..8f9679f649 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -419,6 +419,9 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs if baseBranch != nil { apiPullRequest.Base.Sha = baseBranch.CommitID } + if pr.HeadRepoID == pr.BaseRepoID { + apiPullRequest.Head.Repository = apiPullRequest.Base.Repository + } // pull request head branch, both repository and branch could not exist if pr.HeadRepo != nil { @@ -431,22 +434,19 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs if exist { apiPullRequest.Head.Ref = pr.HeadBranch } + if pr.HeadRepoID != pr.BaseRepoID { + p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) + p.AccessMode = perm.AccessModeNone + } + apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) + } } if apiPullRequest.Head.Ref == "" { apiPullRequest.Head.Ref = pr.GetGitRefName() } - if pr.HeadRepoID == pr.BaseRepoID { - apiPullRequest.Head.Repository = apiPullRequest.Base.Repository - } else { - p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) - if err != nil { - log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) - p.AccessMode = perm.AccessModeNone - } - apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) - } - if pr.Flow == issues_model.PullRequestFlowAGit { apiPullRequest.Head.Name = "" } diff --git a/services/convert/pull_test.go b/services/convert/pull_test.go index cd86283c8a..dfbe24d184 100644 --- a/services/convert/pull_test.go +++ b/services/convert/pull_test.go @@ -46,4 +46,11 @@ func TestPullRequest_APIFormat(t *testing.T) { assert.NotNil(t, apiPullRequest) assert.Nil(t, apiPullRequest.Head.Repository) assert.EqualValues(t, -1, apiPullRequest.Head.RepoID) + + apiPullRequests, err := ToAPIPullRequests(git.DefaultContext, pr.BaseRepo, []*issues_model.PullRequest{pr}, nil) + assert.NoError(t, err) + assert.Len(t, apiPullRequests, 1) + assert.NotNil(t, apiPullRequests[0]) + assert.Nil(t, apiPullRequests[0].Head.Repository) + assert.EqualValues(t, -1, apiPullRequests[0].Head.RepoID) } diff --git a/services/pull/merge.go b/services/pull/merge.go index 829d4b7ee1..2a2f47e880 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -432,10 +432,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use func commitAndSignNoAuthor(ctx *mergeContext, message string) error { cmdCommit := git.NewCommand("commit").AddOptionFormat("--message=%s", message) - if ctx.signKeyID == "" { + if ctx.signKey == nil { cmdCommit.AddArguments("--no-gpg-sign") } else { - cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) + if ctx.signKey.Format != "" { + cmdCommit.AddConfig("gpg.format", ctx.signKey.Format) + } + cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 719cc6b965..31a1e13734 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -27,7 +27,7 @@ type mergeContext struct { doer *user_model.User sig *git.Signature committer *git.Signature - signKeyID string // empty for no-sign, non-empty to sign + signKey *git.SigningKey env []string } @@ -99,9 +99,9 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque mergeCtx.committer = mergeCtx.sig // Determine if we should sign - sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch) + sign, key, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch) if sign { - mergeCtx.signKeyID = keyID + mergeCtx.signKey = key if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { mergeCtx.committer = signer } diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 119b28736c..0049c0b117 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -71,10 +71,13 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { cmdCommit := git.NewCommand("commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). AddOptionFormat("--message=%s", message) - if ctx.signKeyID == "" { + if ctx.signKey == nil { cmdCommit.AddArguments("--no-gpg-sign") } else { - cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) + if ctx.signKey.Format != "" { + cmdCommit.AddConfig("gpg.format", ctx.signKey.Format) + } + cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 493ff9998d..1cf30edc7b 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -293,15 +293,18 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit } var sign bool - var keyID string + var key *git.SigningKey var signer *git.Signature if opts.ParentCommitID != "" { - sign, keyID, signer, _ = asymkey_service.SignCRUDAction(ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID) + sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID) } else { - sign, keyID, signer, _ = asymkey_service.SignInitialCommit(ctx, t.repo.RepoPath(), opts.DoerUser) + sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, t.repo.RepoPath(), opts.DoerUser) } if sign { - cmdCommitTree.AddOptionFormat("-S%s", keyID) + if key.Format != "" { + cmdCommitTree.AddConfig("gpg.format", key.Format) + } + cmdCommitTree.AddOptionFormat("-S%s", key.KeyID) if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { // Add trailers diff --git a/services/repository/init.go b/services/repository/init.go index bd777b8a2f..1eeeb4aa4f 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -42,9 +42,12 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi cmd := git.NewCommand("commit", "--message=Initial commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) - sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) + sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) if sign { - cmd.AddOptionFormat("-S%s", keyID) + if key.Format != "" { + cmd.AddConfig("gpg.format", key.Format) + } + cmd.AddOptionFormat("-S%s", key.KeyID) if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { // need to set the committer to the KeyID owner diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 9405f7cfc8..0a955406e2 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -194,7 +194,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer) if sign { - commitTreeOpts.KeyID = signingKey + commitTreeOpts.Key = signingKey if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { committer = signer } @@ -316,7 +316,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer) if sign { - commitTreeOpts.KeyID = signingKey + commitTreeOpts.Key = signingKey if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { committer = signer } diff --git a/templates/repo/actions/workflow_dispatch_inputs.tmpl b/templates/repo/actions/workflow_dispatch_inputs.tmpl index 8b8292af1d..37538a318f 100644 --- a/templates/repo/actions/workflow_dispatch_inputs.tmpl +++ b/templates/repo/actions/workflow_dispatch_inputs.tmpl @@ -33,7 +33,8 @@ </div> {{end}} <div class="ui field"> - <button class="ui tiny primary button" type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button> + {{/* use autofocus here to prevent the "branch selection" dropdown from getting focus, otherwise it will auto popup */}} + <button class="ui tiny primary button" autofocus type="submit">{{ctx.Locale.Tr "actions.workflow.run"}}</button> </div> {{end}} {{range .workflows}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 68b09c484b..cc9abd92bd 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -15164,6 +15164,42 @@ } } }, + "/repos/{owner}/{repo}/signing-key.pub": { + "get": { + "produces": [ + "text/plain" + ], + "tags": [ + "repository" + ], + "summary": "Get signing-key.pub for given repository", + "operationId": "repoSigningKeySSH", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ssh public key", + "schema": { + "type": "string" + } + } + } + } + }, "/repos/{owner}/{repo}/stargazers": { "get": { "produces": [ @@ -16997,6 +17033,26 @@ } } }, + "/signing-key.pub": { + "get": { + "produces": [ + "text/plain" + ], + "tags": [ + "miscellaneous" + ], + "summary": "Get default signing-key.pub", + "operationId": "getSigningKeySSH", + "responses": { + "200": { + "description": "ssh public key", + "schema": { + "type": "string" + } + } + } + } + }, "/teams/{id}": { "get": { "produces": [ diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go index 61f11c58b0..3878302ef0 100644 --- a/tests/integration/cmd_keys_test.go +++ b/tests/integration/cmd_keys_test.go @@ -13,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func Test_CmdKeys(t *testing.T) { @@ -36,18 +36,21 @@ func Test_CmdKeys(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := new(bytes.Buffer) - app := cli.NewApp() - app.Writer = out - app.Commands = []*cli.Command{cmd.CmdKeys} + var stdout, stderr bytes.Buffer + app := &cli.Command{ + Writer: &stdout, + ErrWriter: &stderr, + Commands: []*cli.Command{cmd.CmdKeys}, + } cmd.CmdKeys.HideHelp = true - err := app.Run(append([]string{"prog"}, tt.args...)) + err := app.Run(t.Context(), append([]string{"prog"}, tt.args...)) if tt.wantErr { assert.Error(t, err) + assert.Equal(t, tt.expectedOutput, stderr.String()) } else { assert.NoError(t, err) + assert.Equal(t, tt.expectedOutput, stdout.String()) } - assert.Equal(t, tt.expectedOutput, out.String()) }) } }) diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_ssh_git_test.go index 32de200f63..56f9f87783 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_ssh_git_test.go @@ -4,7 +4,10 @@ package integration import ( + "crypto/ed25519" + "crypto/rand" "encoding/base64" + "encoding/pem" "fmt" "net/url" "os" @@ -23,6 +26,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" ) func TestGPGGit(t *testing.T) { @@ -42,6 +46,37 @@ func TestGPGGit(t *testing.T) { defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})() defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})() + testGitSigning(t) +} + +func TestSSHGit(t *testing.T) { + tmpDir := t.TempDir() // use a temp dir to store the SSH keys + err := os.Chmod(tmpDir, 0o700) + assert.NoError(t, err) + + pub, priv, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err, "ed25519.GenerateKey") + sshPubKey, err := ssh.NewPublicKey(pub) + require.NoError(t, err, "ssh.NewPublicKey") + + err = os.WriteFile(tmpDir+"/id_ed25519.pub", ssh.MarshalAuthorizedKey(sshPubKey), 0o600) + require.NoError(t, err, "os.WriteFile id_ed25519.pub") + block, err := ssh.MarshalPrivateKey(priv, "") + require.NoError(t, err, "ssh.MarshalPrivateKey") + err = os.WriteFile(tmpDir+"/id_ed25519", pem.EncodeToMemory(block), 0o600) + require.NoError(t, err, "os.WriteFile id_ed25519") + + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, tmpDir+"/id_ed25519.pub")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "gitea")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "gitea@fake.local")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningFormat, "ssh")() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"never"})() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"never"})() + + testGitSigning(t) +} + +func testGitSigning(t *testing.T) { username := "user2" user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username}) baseAPITestContext := NewAPITestContext(t, username, "repo1") diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index b983855630..8f92a51749 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -2,7 +2,7 @@ overflow: hidden; font-size: 16px; line-height: 1.5 !important; - overflow-wrap: anywhere; + overflow-wrap: break-word; } .markup > *:first-child { diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index 61b0a1f962..ee371f1b1c 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -67,6 +67,7 @@ .repo-view-content { flex: 1; + min-width: 0; } .language-stats { diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css index fb832bd05a..27f3c2d554 100644 --- a/web_src/css/repo/issue-card.css +++ b/web_src/css/repo/issue-card.css @@ -7,6 +7,7 @@ padding: 8px 10px; border: 1px solid var(--color-secondary); background: var(--color-card); + color: var(--color-text); /* it can't inherit from parent because the card already has its own background */ } .issue-card-icon, diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 6b16ff9efb..6573633227 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -429,7 +429,7 @@ export default defineComponent({ <svg-icon name="octicon-archive" :size="16"/> </div> </a> - <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link" :data-tooltip-content="repo.locale_latest_commit_status_state"> + <a class="tw-flex tw-items-center" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link || null" :data-tooltip-content="repo.locale_latest_commit_status_state"> <!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl --> <svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class="'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> </a> |