diff options
183 files changed, 6306 insertions, 3331 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ab30e1789d..4128f1466f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "features": { // installs nodejs into container "ghcr.io/devcontainers/features/node:1": { - "version": "20" + "version": "lts" }, "ghcr.io/devcontainers/features/git-lfs:1.2.2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {}, diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 64090d6490..f6720bf2f6 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -37,7 +37,7 @@ jobs: python-version: "3.12" - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: pip install poetry @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend @@ -137,7 +137,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend @@ -186,7 +186,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index 87e931117c..cc3fbd9c34 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -25,7 +25,7 @@ jobs: check-latest: true - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend frontend deps-backend diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 2558a16a71..c2cc14f771 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -22,7 +22,7 @@ jobs: check-latest: true - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend deps-backend diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 37b3ff57d2..c9c15c31a0 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -23,7 +23,7 @@ jobs: check-latest: true - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend deps-backend diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 4250623da0..ae717c7cec 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -27,7 +27,7 @@ jobs: check-latest: true - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: package-lock.json - run: make deps-frontend deps-backend 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..8c6001e727 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")) @@ -215,7 +212,7 @@ func runServ(c *cli.Context) error { if git.DefaultFeatures().SupportProcReceive { // for AGit Flow if cmd == "ssh_info" { - fmt.Print(`{"type":"gitea","version":1}`) + fmt.Print(`{"type":"agit","version":1}`) return nil } } 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 a7476ad1be..aa2fcee765 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -186,17 +186,13 @@ RUN_USER = ; git ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;; -;; For the built-in SSH server, choose the ciphers to support for SSH connections, -;; for system SSH this setting has no effect -;SSH_SERVER_CIPHERS = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com -;; -;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, -;; for system SSH this setting has no effect -;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1 -;; -;; For the built-in SSH server, choose the MACs to support for SSH connections, -;; for system SSH this setting has no effect -;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1 +;; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections. +;; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go. +;; Leave them empty to use the Golang crypto's recommended default values. +;; For system SSH (non-builtin SSH server), this setting has no effect. +;SSH_SERVER_CIPHERS = +;SSH_SERVER_KEY_EXCHANGES = +;SSH_SERVER_MACS = ;; ;; For the built-in SSH server, choose the keypair to offer as the host key ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub @@ -1190,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 @@ -1227,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 @@ -118,14 +117,13 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 gitlab.com/gitlab-org/api/client-go v0.127.0 - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.39.0 golang.org/x/image v0.26.0 - golang.org/x/net v0.39.0 + golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.29.0 - golang.org/x/sync v0.13.0 - golang.org/x/sys v0.32.0 - golang.org/x/text v0.24.0 - golang.org/x/tools v0.32.0 + golang.org/x/sync v0.15.0 + golang.org/x/sys v0.33.0 + golang.org/x/text v0.26.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,18 +258,17 @@ 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 go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/mod v0.24.0 // 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= @@ -835,8 +760,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= @@ -850,8 +775,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -869,8 +794,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -886,8 +811,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -920,8 +845,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -933,8 +858,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -946,8 +871,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -960,8 +885,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -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/activities/statistic.go b/models/activities/statistic.go index 983a124550..940651d359 100644 --- a/models/activities/statistic.go +++ b/models/activities/statistic.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" ) // Statistic contains the database statistics @@ -68,7 +69,7 @@ func GetStatistic(ctx context.Context) (stats Statistic) { } stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts) - stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludePrivate: true}) + stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate}) stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{}) stats.Counter.Watch, _ = e.Count(new(repo_model.Watch)) diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go index 46dcf4d894..00d75b8e82 100644 --- a/models/asymkey/ssh_key_parse.go +++ b/models/asymkey/ssh_key_parse.go @@ -208,7 +208,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { // The ssh library can parse the key, so next we find out what key exactly we have. switch pkey.Type() { - case ssh.KeyAlgoDSA: + case ssh.KeyAlgoDSA: //nolint rawPub := struct { Name string P, Q, G, Y *big.Int diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 2e765391b8..f85e1b15e5 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -17,10 +17,10 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/translation" @@ -30,17 +30,17 @@ import ( // CommitStatus holds a single Status of a single Commit type CommitStatus struct { - ID int64 `xorm:"pk autoincr"` - Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` - RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` - Repo *repo_model.Repository `xorm:"-"` - State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"` - SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` - TargetURL string `xorm:"TEXT"` - Description string `xorm:"TEXT"` - ContextHash string `xorm:"VARCHAR(64) index"` - Context string `xorm:"TEXT"` - Creator *user_model.User `xorm:"-"` + ID int64 `xorm:"pk autoincr"` + Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + Repo *repo_model.Repository `xorm:"-"` + State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` + TargetURL string `xorm:"TEXT"` + Description string `xorm:"TEXT"` + ContextHash string `xorm:"VARCHAR(64) index"` + Context string `xorm:"TEXT"` + Creator *user_model.User `xorm:"-"` CreatorID int64 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` @@ -230,28 +230,25 @@ func (status *CommitStatus) HideActionsURL(ctx context.Context) { // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { - // This function is widely used, but it is not quite right. - // Ideally it should return something like "CommitStatusSummary" with properly aggregated state. - // GitHub's behavior: if all statuses are "skipped", GitHub will return "success" as the combined status. - var lastStatus *CommitStatus - state := api.CommitStatusSuccess + if len(statuses) == 0 { + return nil + } + + states := make(commitstatus.CommitStatusStates, 0, len(statuses)) + targetURL := "" for _, status := range statuses { - if state == status.State || status.State.HasHigherPriorityThan(state) { - state = status.State - lastStatus = status + states = append(states, status.State) + if status.TargetURL != "" { + targetURL = status.TargetURL } } - if lastStatus == nil { - if len(statuses) > 0 { - // FIXME: a bad case: Gitea just returns the first commit status, its status is "skipped" in this case. - lastStatus = statuses[0] - } else { - // FIXME: another bad case: if the "statuses" slice is empty, the returned value is an invalid CommitStatus, all its fields are empty. - // Frontend code (tmpl&vue) sometimes depend on the empty fields to skip rendering commit status elements (need to double check in the future) - lastStatus = &CommitStatus{} - } + + return &CommitStatus{ + RepoID: statuses[0].RepoID, + SHA: statuses[0].SHA, + State: states.Combine(), + TargetURL: targetURL, } - return lastStatus } // CommitStatusOptions holds the options for query commit statuses diff --git a/models/git/commit_status_summary.go b/models/git/commit_status_summary.go index 774e49bb98..dd416fa015 100644 --- a/models/git/commit_status_summary.go +++ b/models/git/commit_status_summary.go @@ -7,19 +7,19 @@ import ( "context" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" "xorm.io/builder" ) // CommitStatusSummary holds the latest commit Status of a single Commit type CommitStatusSummary struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"` - SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"` - State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"` - TargetURL string `xorm:"TEXT"` + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"` + State commitstatus.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"` + TargetURL string `xorm:"TEXT"` } func init() { diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index a01d030e71..4c0f5e891b 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -14,9 +14,9 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) @@ -38,23 +38,23 @@ func TestGetCommitStatuses(t *testing.T) { assert.Len(t, statuses, 5) assert.Equal(t, "ci/awesomeness", statuses[0].Context) - assert.Equal(t, structs.CommitStatusPending, statuses[0].State) + assert.Equal(t, commitstatus.CommitStatusPending, statuses[0].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL(db.DefaultContext)) assert.Equal(t, "cov/awesomeness", statuses[1].Context) - assert.Equal(t, structs.CommitStatusWarning, statuses[1].State) + assert.Equal(t, commitstatus.CommitStatusWarning, statuses[1].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL(db.DefaultContext)) assert.Equal(t, "cov/awesomeness", statuses[2].Context) - assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State) + assert.Equal(t, commitstatus.CommitStatusSuccess, statuses[2].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL(db.DefaultContext)) assert.Equal(t, "ci/awesomeness", statuses[3].Context) - assert.Equal(t, structs.CommitStatusFailure, statuses[3].State) + assert.Equal(t, commitstatus.CommitStatusFailure, statuses[3].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[3].APIURL(db.DefaultContext)) assert.Equal(t, "deploy/awesomeness", statuses[4].Context) - assert.Equal(t, structs.CommitStatusError, statuses[4].State) + assert.Equal(t, commitstatus.CommitStatusError, statuses[4].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext)) statuses, maxResults, err = db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{ @@ -75,110 +75,110 @@ func Test_CalcCommitStatus(t *testing.T) { { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, { - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, { - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusError, + State: commitstatus.CommitStatusError, }, { - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusError, + State: commitstatus.CommitStatusFailure, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusWarning, + State: commitstatus.CommitStatusWarning, }, { - State: structs.CommitStatusPending, + State: commitstatus.CommitStatusPending, }, { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusWarning, + State: commitstatus.CommitStatusPending, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, { - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, }, }, { statuses: []*git_model.CommitStatus{ { - State: structs.CommitStatusFailure, + State: commitstatus.CommitStatusFailure, }, { - State: structs.CommitStatusError, + State: commitstatus.CommitStatusError, }, { - State: structs.CommitStatusWarning, + State: commitstatus.CommitStatusWarning, }, }, expected: &git_model.CommitStatus{ - State: structs.CommitStatusError, + State: commitstatus.CommitStatusFailure, }, }, } for _, kase := range kases { - assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses)) + assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses), "statuses: %v", kase.statuses) } } @@ -208,7 +208,7 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) { Creator: user2, SHA: commit.ID, CommitStatus: &git_model.CommitStatus{ - State: structs.CommitStatusFailure, + State: commitstatus.CommitStatusFailure, TargetURL: "https://example.com/tests/", Context: "compliance/lint-backend", }, @@ -220,7 +220,7 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) { Creator: user2, SHA: commit.ID, CommitStatus: &git_model.CommitStatus{ - State: structs.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://example.com/tests/", Context: "compliance/lint-backend", }, @@ -270,9 +270,9 @@ func TestGetCountLatestCommitStatus(t *testing.T) { }) assert.NoError(t, err) assert.Len(t, commitStatuses, 2) - assert.Equal(t, structs.CommitStatusFailure, commitStatuses[0].State) + assert.Equal(t, commitstatus.CommitStatusFailure, commitStatuses[0].State) assert.Equal(t, "ci/awesomeness", commitStatuses[0].Context) - assert.Equal(t, structs.CommitStatusError, commitStatuses[1].State) + assert.Equal(t, commitstatus.CommitStatusError, commitStatuses[1].State) assert.Equal(t, "deploy/awesomeness", commitStatuses[1].Context) count, err := git_model.CountLatestCommitStatus(db.DefaultContext, repo1.ID, sha1) diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index b562aab500..55e67a1243 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -5,6 +5,7 @@ package issues import ( "context" + "strconv" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/renderhelper" @@ -114,7 +115,9 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu } var err error - rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil { return nil, err } diff --git a/models/organization/org_list.go b/models/organization/org_list.go index 78ac0e704a..81457191fe 100644 --- a/models/organization/org_list.go +++ b/models/organization/org_list.go @@ -50,8 +50,8 @@ type SearchOrganizationsOptions struct { // FindOrgOptions finds orgs options type FindOrgOptions struct { db.ListOptions - UserID int64 - IncludePrivate bool + UserID int64 + IncludeVisibility structs.VisibleType } func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { @@ -65,11 +65,10 @@ func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { func (opts FindOrgOptions) ToConds() builder.Cond { var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} if opts.UserID > 0 { - cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate))) - } - if !opts.IncludePrivate { - cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}) + cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludeVisibility == structs.VisibleTypePrivate))) } + // public=0, limited=1, private=2 + cond = cond.And(builder.Lte{"`user`.visibility": opts.IncludeVisibility}) return cond } @@ -77,6 +76,16 @@ func (opts FindOrgOptions) ToOrders() string { return "`user`.lower_name ASC" } +func DoerViewOtherVisibility(doer, other *user_model.User) structs.VisibleType { + if doer == nil || other == nil { + return structs.VisibleTypePublic + } + if doer.IsAdmin || doer.ID == other.ID { + return structs.VisibleTypePrivate + } + return structs.VisibleTypeLimited +} + // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID // are allowed to create repos. func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { diff --git a/models/organization/org_list_test.go b/models/organization/org_list_test.go index e859d87c84..a2a25c6f91 100644 --- a/models/organization/org_list_test.go +++ b/models/organization/org_list_test.go @@ -10,25 +10,32 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) -func TestCountOrganizations(t *testing.T) { +func TestOrgList(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("CountOrganizations", testCountOrganizations) + t.Run("FindOrgs", testFindOrgs) + t.Run("GetUserOrgsList", testGetUserOrgsList) + t.Run("LoadOrgListTeams", testLoadOrgListTeams) + t.Run("DoerViewOtherVisibility", testDoerViewOtherVisibility) +} + +func testCountOrganizations(t *testing.T) { expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{}) assert.NoError(t, err) - cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true}) + cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate}) assert.NoError(t, err) assert.Equal(t, expected, cnt) } -func TestFindOrgs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - +func testFindOrgs(t *testing.T) { orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: 4, - IncludePrivate: true, + UserID: 4, + IncludeVisibility: structs.VisibleTypePrivate, }) assert.NoError(t, err) if assert.Len(t, orgs, 1) { @@ -36,22 +43,20 @@ func TestFindOrgs(t *testing.T) { } orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: 4, - IncludePrivate: false, + UserID: 4, }) assert.NoError(t, err) assert.Empty(t, orgs) total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: 4, - IncludePrivate: true, + UserID: 4, + IncludeVisibility: structs.VisibleTypePrivate, }) assert.NoError(t, err) assert.EqualValues(t, 1, total) } -func TestGetUserOrgsList(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) +func testGetUserOrgsList(t *testing.T) { orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) assert.NoError(t, err) if assert.Len(t, orgs, 1) { @@ -61,8 +66,7 @@ func TestGetUserOrgsList(t *testing.T) { } } -func TestLoadOrgListTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) +func testLoadOrgListTeams(t *testing.T) { orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) assert.NoError(t, err) assert.Len(t, orgs, 1) @@ -71,3 +75,10 @@ func TestLoadOrgListTeams(t *testing.T) { assert.Len(t, teamsMap, 1) assert.Len(t, teamsMap[3], 5) } + +func testDoerViewOtherVisibility(t *testing.T) { + assert.Equal(t, structs.VisibleTypePublic, organization.DoerViewOtherVisibility(nil, nil)) + assert.Equal(t, structs.VisibleTypeLimited, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 2})) + assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 1})) + assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1, IsAdmin: true}, &user_model.User{ID: 2})) +} diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go index 7c40eded44..ae0fbf0abd 100644 --- a/models/renderhelper/repo_comment.go +++ b/models/renderhelper/repo_comment.go @@ -44,30 +44,31 @@ type RepoCommentOptions struct { DeprecatedRepoName string // it is only a patch for the non-standard "markup" api DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api CurrentRefPath string // eg: "branch/main" or "commit/11223344" + FootnoteContextID string // the extra context ID for footnotes, used to avoid conflicts with other footnotes in the same page } 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 if repo != nil { helper.repoLink = repo.Link() helper.commitChecker = newCommitChecker(ctx, repo) - rctx = rctx.WithMetas(repo.ComposeCommentMetas(ctx)) + 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" } - rctx = rctx.WithHelper(helper) + metas["footnoteContextId"] = helper.opts.FootnoteContextID + rctx = rctx.WithMetas(metas).WithHelper(helper) return rctx } 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/actions/workflows.go b/modules/actions/workflows.go index 31f859953e..84b3225338 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -313,6 +313,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa matchTimes++ } case "paths": + if refName.IsTag() { + matchTimes++ + break + } filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) @@ -326,6 +330,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa } } case "paths-ignore": + if refName.IsTag() { + matchTimes++ + break + } filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index c8e1e553fe..e23431651d 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -125,6 +125,24 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: schedule", expected: true, }, + { + desc: "push to tag matches workflow with paths condition (should skip paths check)", + triggedEvent: webhook_module.HookEventPush, + payload: &api.PushPayload{ + Ref: "refs/tags/v1.0.0", + Before: "0000000", + Commits: []*api.PayloadCommit{ + { + ID: "abcdef123456", + Added: []string{"src/main.go"}, + Message: "Release v1.0.0", + }, + }, + }, + commit: nil, + yamlOn: "on:\n push:\n paths:\n - src/**", + expected: true, + }, } for _, tc := range testCases { 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/structs/commit_status.go b/modules/commitstatus/commit_status.go index 398001974d..12004474ed 100644 --- a/modules/structs/commit_status.go +++ b/modules/commitstatus/commit_status.go @@ -1,11 +1,11 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package structs +package commitstatus // CommitStatusState holds the state of a CommitStatus -// It can be "pending", "success", "error" and "failure" -type CommitStatusState string +// swagger:enum CommitStatusState +type CommitStatusState string //nolint const ( // CommitStatusPending is for when the CommitStatus is Pending @@ -22,25 +22,10 @@ const ( CommitStatusSkipped CommitStatusState = "skipped" ) -var commitStatusPriorities = map[CommitStatusState]int{ - CommitStatusError: 0, - CommitStatusFailure: 1, - CommitStatusWarning: 2, - CommitStatusPending: 3, - CommitStatusSuccess: 4, - CommitStatusSkipped: 5, -} - func (css CommitStatusState) String() string { return string(css) } -// HasHigherPriorityThan returns true if this state has higher priority than the other -// Undefined states are considered to have the highest priority like CommitStatusError(0) -func (css CommitStatusState) HasHigherPriorityThan(other CommitStatusState) bool { - return commitStatusPriorities[css] < commitStatusPriorities[other] -} - // IsPending represents if commit status state is pending func (css CommitStatusState) IsPending() bool { return css == CommitStatusPending @@ -65,3 +50,32 @@ func (css CommitStatusState) IsFailure() bool { func (css CommitStatusState) IsWarning() bool { return css == CommitStatusWarning } + +// IsSkipped represents if commit status state is skipped +func (css CommitStatusState) IsSkipped() bool { + return css == CommitStatusSkipped +} + +type CommitStatusStates []CommitStatusState //nolint + +// According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference +// > Additionally, a combined state is returned. The state is one of: +// > failure if any of the contexts report as error or failure +// > pending if there are no statuses or a context is pending +// > success if the latest status for all contexts is success +func (css CommitStatusStates) Combine() CommitStatusState { + successCnt := 0 + for _, state := range css { + switch { + case state.IsError() || state.IsFailure(): + return CommitStatusFailure + case state.IsPending(): + case state.IsSuccess() || state.IsWarning() || state.IsSkipped(): + successCnt++ + } + } + if successCnt > 0 && successCnt == len(css) { + return CommitStatusSuccess + } + return CommitStatusPending +} diff --git a/modules/commitstatus/commit_status_test.go b/modules/commitstatus/commit_status_test.go new file mode 100644 index 0000000000..10d8f20aa4 --- /dev/null +++ b/modules/commitstatus/commit_status_test.go @@ -0,0 +1,201 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package commitstatus + +import "testing" + +func TestCombine(t *testing.T) { + tests := []struct { + name string + states CommitStatusStates + expected CommitStatusState + }{ + // 0 states + { + name: "empty", + states: CommitStatusStates{}, + expected: CommitStatusPending, + }, + // 1 state + { + name: "pending", + states: CommitStatusStates{CommitStatusPending}, + expected: CommitStatusPending, + }, + { + name: "success", + states: CommitStatusStates{CommitStatusSuccess}, + expected: CommitStatusSuccess, + }, + { + name: "error", + states: CommitStatusStates{CommitStatusError}, + expected: CommitStatusFailure, + }, + { + name: "failure", + states: CommitStatusStates{CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "warning", + states: CommitStatusStates{CommitStatusWarning}, + expected: CommitStatusSuccess, + }, + // 2 states + { + name: "pending and success", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess}, + expected: CommitStatusPending, + }, + { + name: "pending and error", + states: CommitStatusStates{CommitStatusPending, CommitStatusError}, + expected: CommitStatusFailure, + }, + { + name: "pending and failure", + states: CommitStatusStates{CommitStatusPending, CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "pending and warning", + states: CommitStatusStates{CommitStatusPending, CommitStatusWarning}, + expected: CommitStatusPending, + }, + { + name: "success and error", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusError}, + expected: CommitStatusFailure, + }, + { + name: "success and failure", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "success and warning", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning}, + expected: CommitStatusSuccess, + }, + { + name: "error and failure", + states: CommitStatusStates{CommitStatusError, CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "error and warning", + states: CommitStatusStates{CommitStatusError, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "failure and warning", + states: CommitStatusStates{CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + // 3 states + { + name: "pending, success and warning", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusWarning}, + expected: CommitStatusPending, + }, + { + name: "pending, success and error", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError}, + expected: CommitStatusFailure, + }, + { + name: "pending, success and failure", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "pending, error and failure", + states: CommitStatusStates{CommitStatusPending, CommitStatusError, CommitStatusFailure}, + expected: CommitStatusFailure, + }, + { + name: "success, error and warning", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "success, failure and warning", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "error, failure and warning", + states: CommitStatusStates{CommitStatusError, CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "success, warning and skipped", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning, CommitStatusSkipped}, + expected: CommitStatusSuccess, + }, + // All success + { + name: "all success", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess}, + expected: CommitStatusSuccess, + }, + // All pending + { + name: "all pending", + states: CommitStatusStates{CommitStatusPending, CommitStatusPending, CommitStatusPending}, + expected: CommitStatusPending, + }, + { + name: "all skipped", + states: CommitStatusStates{CommitStatusSkipped, CommitStatusSkipped, CommitStatusSkipped}, + expected: CommitStatusSuccess, + }, + // 4 states + { + name: "pending, success, error and warning", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "pending, success, failure and warning", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "pending, error, failure and warning", + states: CommitStatusStates{CommitStatusPending, CommitStatusError, CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "success, error, failure and warning", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusFailure, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "mixed states", + states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning}, + expected: CommitStatusFailure, + }, + { + name: "mixed states with all success", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning}, + expected: CommitStatusPending, + }, + { + name: "all success with warning", + states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess, CommitStatusWarning}, + expected: CommitStatusSuccess, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.states.Combine() + if result != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) + } +} 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/common/footnote.go b/modules/markup/common/footnote.go index 9a4f18ed7f..26ab60bc1e 100644 --- a/modules/markup/common/footnote.go +++ b/modules/markup/common/footnote.go @@ -409,9 +409,9 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt _, _ = w.Write(n.Name) _, _ = w.WriteString(`"><a href="#fn:`) _, _ = w.Write(n.Name) - _, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`) + _, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`) // FIXME: here and below, need to keep the classes _, _ = w.WriteString(is) - _, _ = w.WriteString(`</a></sup>`) + _, _ = w.WriteString(` </a></sup>`) // the style doesn't work at the moment, so add a space to separate the names } return ast.WalkContinue, nil } diff --git a/modules/markup/html.go b/modules/markup/html.go index 7c3bd93699..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>"), @@ -320,6 +320,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod } processNodeAttrID(node) + processFootnoteNode(ctx, node) // FIXME: the footnote processing should be done in the "footnote.go" renderer directly if isEmojiNode(node) { // TextNode emoji will be converted to `<span class="emoji">`, then the next iteration will visit the "span" diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go index c68429641f..39cd9dcf6a 100644 --- a/modules/markup/html_issue_test.go +++ b/modules/markup/html_issue_test.go @@ -30,6 +30,7 @@ func TestRender_IssueList(t *testing.T) { rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{ "user": "test-user", "repo": "test-repo", "markupAllowShortIssuePattern": "true", + "footnoteContextId": "12345", }) out, err := markdown.RenderString(rctx, input) require.NoError(t, err) @@ -69,4 +70,22 @@ func TestRender_IssueList(t *testing.T) { </ul>`, ) }) + + t.Run("IssueFootnote", func(t *testing.T) { + test( + "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz", + `<p>foo<sup id="fnref:user-content-1-12345"><a href="#fn:user-content-1-12345" rel="nofollow">1 </a></sup><sup id="fnref:user-content-2-12345"><a href="#fn:user-content-2-12345" rel="nofollow">2 </a></sup></p> +<div> +<hr/> +<ol> +<li id="fn:user-content-1-12345"> +<p>bar <a href="#fnref:user-content-1-12345" rel="nofollow">↩︎</a></p> +</li> +<li id="fn:user-content-2-12345"> +<p>baz <a href="#fnref:user-content-2-12345" rel="nofollow">↩︎</a></p> +</li> +</ol> +</div>`, + ) + }) } diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go index 68858b024a..f67437465c 100644 --- a/modules/markup/html_node.go +++ b/modules/markup/html_node.go @@ -15,6 +15,14 @@ func isAnchorIDUserContent(s string) bool { return strings.HasPrefix(s, "user-content-") || strings.Contains(s, ":user-content-") } +func isAnchorIDFootnote(s string) bool { + return strings.HasPrefix(s, "fnref:user-content-") || strings.HasPrefix(s, "fn:user-content-") +} + +func isAnchorHrefFootnote(s string) bool { + return strings.HasPrefix(s, "#fnref:user-content-") || strings.HasPrefix(s, "#fn:user-content-") +} + func processNodeAttrID(node *html.Node) { // Add user-content- to IDs and "#" links if they don't already have them, // and convert the link href to a relative link to the host root @@ -27,6 +35,18 @@ func processNodeAttrID(node *html.Node) { } } +func processFootnoteNode(ctx *RenderContext, node *html.Node) { + for idx, attr := range node.Attr { + if (attr.Key == "id" && isAnchorIDFootnote(attr.Val)) || + (attr.Key == "href" && isAnchorHrefFootnote(attr.Val)) { + if footnoteContextID := ctx.RenderOptions.Metas["footnoteContextId"]; footnoteContextID != "" { + node.Attr[idx].Val = attr.Val + "-" + footnoteContextID + } + continue + } + } +} + func processNodeA(ctx *RenderContext, node *html.Node) { for idx, attr := range node.Attr { if attr.Key == "href" { 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/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 2310895fc3..99f590c950 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -223,7 +223,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno <dd>This is another definition of the second term.</dd> </dl> <h3 id="user-content-footnotes">Footnotes</h3> -<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p> +<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1 </a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2 </a></sup></p> <div> <hr/> <ol> 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/packages/container/metadata.go b/modules/packages/container/metadata.go index 2a41fb9105..2fce7d976a 100644 --- a/modules/packages/container/metadata.go +++ b/modules/packages/container/metadata.go @@ -4,6 +4,7 @@ package container import ( + "errors" "fmt" "io" "strings" @@ -83,7 +84,8 @@ func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) { func parseOCIImageConfig(r io.Reader) (*Metadata, error) { var image oci.Image - if err := json.NewDecoder(r).Decode(&image); err != nil { + // EOF means empty input, still use the default data + if err := json.NewDecoder(r).Decode(&image); err != nil && !errors.Is(err, io.EOF) { return nil, err } diff --git a/modules/packages/container/metadata_test.go b/modules/packages/container/metadata_test.go index 665499b2e6..74b0a379c6 100644 --- a/modules/packages/container/metadata_test.go +++ b/modules/packages/container/metadata_test.go @@ -11,6 +11,7 @@ import ( oci "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParseImageConfig(t *testing.T) { @@ -59,3 +60,9 @@ func TestParseImageConfig(t *testing.T) { assert.Equal(t, projectURL, metadata.ProjectURL) assert.Equal(t, repositoryURL, metadata.RepositoryURL) } + +func TestParseOCIImageConfig(t *testing.T) { + metadata, err := parseOCIImageConfig(strings.NewReader("")) + require.NoError(t, err) + assert.Equal(t, &Metadata{Type: TypeOCI, Platform: DefaultPlatform, ImageLayers: []string{}}, metadata) +} 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/setting/ssh.go b/modules/setting/ssh.go index da8cdf58d2..900fc6ade2 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -51,9 +51,6 @@ var SSH = struct { StartBuiltinServer: false, Domain: "", Port: 22, - ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"}, - ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"}, - ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, MinimumKeySizeCheck: true, MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, @@ -107,21 +104,20 @@ func loadSSHFrom(rootCfg ConfigProvider) { homeDir = strings.ReplaceAll(homeDir, "\\", "/") SSH.RootPath = filepath.Join(homeDir, ".ssh") - serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",") - if len(serverCiphers) > 0 { - SSH.ServerCiphers = serverCiphers - } - serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",") - if len(serverKeyExchanges) > 0 { - SSH.ServerKeyExchanges = serverKeyExchanges - } - serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",") - if len(serverMACs) > 0 { - SSH.ServerMACs = serverMACs - } + if err = sec.MapTo(&SSH); err != nil { log.Fatal("Failed to map SSH settings: %v", err) } + + serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",") + SSH.ServerCiphers = util.Iif(len(serverCiphers) > 0, serverCiphers, nil) + + serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",") + SSH.ServerKeyExchanges = util.Iif(len(serverKeyExchanges) > 0, serverKeyExchanges, nil) + + serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",") + SSH.ServerMACs = util.Iif(len(serverMACs) > 0, serverMACs, nil) + for i, key := range SSH.ServerHostKeys { if !filepath.IsAbs(key) { SSH.ServerHostKeys[i] = filepath.Join(AppDataPath, key) diff --git a/modules/ssh/init.go b/modules/ssh/init.go index fdc11632e2..cfb0d5693a 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) func Init() error { @@ -23,9 +24,11 @@ func Init() error { if setting.SSH.StartBuiltinServer { Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - log.Info("SSH server started on %s. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", + log.Info("SSH server started on %q. Ciphers: %v, key exchange algorithms: %v, MACs: %v", net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), - setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs, + util.Iif[any](setting.SSH.ServerCiphers == nil, "default", setting.SSH.ServerCiphers), + util.Iif[any](setting.SSH.ServerKeyExchanges == nil, "default", setting.SSH.ServerKeyExchanges), + util.Iif[any](setting.SSH.ServerMACs == nil, "default", setting.SSH.ServerMACs), ) return nil } diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index ff0ad34a0d..3fea4851c7 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -333,7 +333,7 @@ func sshConnectionFailed(conn net.Conn, err error) { log.Warn("Failed authentication attempt from %s", conn.RemoteAddr()) } -// Listen starts a SSH server listens on given port. +// Listen starts an SSH server listening on given port. func Listen(host string, port int, ciphers, keyExchanges, macs []string) { srv := ssh.Server{ Addr: net.JoinHostPort(host, strconv.Itoa(port)), diff --git a/modules/structs/commit_status_test.go b/modules/structs/commit_status_test.go deleted file mode 100644 index c11daf248d..0000000000 --- a/modules/structs/commit_status_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package structs - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNoBetterThan(t *testing.T) { - tests := []struct { - s1, s2 CommitStatusState - higher bool - }{ - {CommitStatusError, CommitStatusFailure, true}, - {CommitStatusFailure, CommitStatusWarning, true}, - {CommitStatusWarning, CommitStatusPending, true}, - {CommitStatusPending, CommitStatusSuccess, true}, - {CommitStatusSuccess, CommitStatusSkipped, true}, - - {CommitStatusError, "unknown-xxx", false}, - {"unknown-xxx", CommitStatusFailure, true}, - } - for _, tt := range tests { - assert.Equal(t, tt.higher, tt.s1.HasHigherPriorityThan(tt.s2), "s1=%s, s2=%s, expected=%v", tt.s1, tt.s2, tt.higher) - } - assert.False(t, CommitStatusError.HasHigherPriorityThan(CommitStatusError)) -} diff --git a/modules/structs/status.go b/modules/structs/status.go index c1d8b902ec..a9779541ff 100644 --- a/modules/structs/status.go +++ b/modules/structs/status.go @@ -5,17 +5,19 @@ package structs import ( "time" + + "code.gitea.io/gitea/modules/commitstatus" ) // CommitStatus holds a single status of a single Commit type CommitStatus struct { - ID int64 `json:"id"` - State CommitStatusState `json:"status"` - TargetURL string `json:"target_url"` - Description string `json:"description"` - URL string `json:"url"` - Context string `json:"context"` - Creator *User `json:"creator"` + ID int64 `json:"id"` + State commitstatus.CommitStatusState `json:"status"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + URL string `json:"url"` + Context string `json:"context"` + Creator *User `json:"creator"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time @@ -24,19 +26,19 @@ type CommitStatus struct { // CombinedStatus holds the combined state of several statuses for a single commit type CombinedStatus struct { - State CommitStatusState `json:"state"` - SHA string `json:"sha"` - TotalCount int `json:"total_count"` - Statuses []*CommitStatus `json:"statuses"` - Repository *Repository `json:"repository"` - CommitURL string `json:"commit_url"` - URL string `json:"url"` + State commitstatus.CommitStatusState `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []*CommitStatus `json:"statuses"` + Repository *Repository `json:"repository"` + CommitURL string `json:"commit_url"` + URL string `json:"url"` } // CreateStatusOption holds the information needed to create a new CommitStatus for a Commit type CreateStatusOption struct { - State CommitStatusState `json:"state"` - TargetURL string `json:"target_url"` - Description string `json:"description"` - Context string `json:"context"` + State commitstatus.CommitStatusState `json:"state"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + Context string `json:"context"` } 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_ga-IE.ini b/options/locale/locale_ga-IE.ini index 79df1d26d0..3804339585 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -1652,6 +1652,7 @@ issues.save=Sábháil issues.label_title=Ainm issues.label_description=Cur sÃos issues.label_color=Dath +issues.label_color_invalid=Dath neamhbhailà issues.label_exclusive=Eisiach issues.label_archive=Lipéad Cartlann issues.label_archived_filter=Taispeáin lipéid cartlainne diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 6e4528b056..68f5e42d47 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=Вибір коміта @@ -480,81 +520,81 @@ Content=ЗміÑÑ‚ SSPISeparatorReplacement=Розділювач SSPIDefaultLanguage=Типова мова -require_error=` не може бути пуÑтим.` -alpha_dash_error=` повинен міÑтити тільки літерно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') та підкреÑÐ»ÐµÐ½Ð½Ñ ('_'). ` -alpha_dash_dot_error=` повинен міÑтити тільки літерно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') , підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та точки ('.'). ` -git_ref_name_error=` повинен бути правильним поÑилальним ім'Ñм Git.` +require_error=` не може бути порожнім.` +alpha_dash_error=` повинен міÑтити тільки алфавітно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-') та підкреÑÐ»ÐµÐ½Ð½Ñ ('_').` +alpha_dash_dot_error=` повинен міÑтити тільки алфавітно-цифрові Ñимволи, Ð´ÐµÑ„Ñ–Ñ ('-'), підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та крапку ('.').` +git_ref_name_error=` повинен бути правильно Ñформованим ім'Ñм-поÑиланнÑм на Git.` size_error=` повинен бути розмір %s.` -min_size_error=` повинен бути принаймні %s Ñимволів.` -max_size_error=` повинен бути не більш Ñк %s Ñимволів.` -email_error=` не Ñ” адреÑою електронної пошти.` +min_size_error=` має міÑтити принаймні %s Ñимволів.` +max_size_error=` має міÑтити не більше %s Ñимволів.` +email_error=` не Ñ” дійÑною адреÑою електронної пошти.` url_error=`"%s" не Ñ” дійÑною URL-адреÑою.` include_error=` повинен міÑтити підрÑдок "%s".` -glob_pattern_error=` неприпуÑтимий шаблон glob: %s.` -regex_pattern_error=` неприпуÑтимий шаблон regex: %s.` +glob_pattern_error=` недійÑний шаблон glob: %s.` +regex_pattern_error=` недійÑний шаблон регулÑрного виразу: %s.` unknown_error=Ðевідома помилка: captcha_incorrect=Код CAPTCHA неправильний. -password_not_match=Паролі не Ñпівпадають. -lang_select_error=Оберіть мову з переліку. +password_not_match=Паролі не збігаютьÑÑ. +lang_select_error=Оберіть мову зі ÑпиÑку. -username_been_taken=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача вже зайнÑто. -username_change_not_local_user=Ðелокальні кориÑтувачі не можуть змінити Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. +username_been_taken=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача вже зайнÑте. +username_change_not_local_user=Ðелокальні кориÑтувачі не можуть змінювати Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. username_has_not_been_changed=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не змінено -repo_name_been_taken=Ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ñ–Ñ‚Ð¾Ñ€Ñ–ÑŽ вже викориÑтовуєтьÑÑ. -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=Ðазва команди вже зайнÑто. -team_no_units_error=Дозволити доÑтуп до принаймні одного розділу репозитарію. -email_been_used=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа вже викориÑтовуєтьÑÑ. -email_invalid=ÐдреÑа електронної пошти помилкова. +repo_name_been_taken=Ðазва Ñховища вже викориÑтовуєтьÑÑ. +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=Ðазва команди вже зайнÑта. +team_no_units_error=Дозволити доÑтуп до принаймні одного розділу Ñховища. +email_been_used=ÐдреÑа електронної пошти вже викориÑтовуєтьÑÑ. +email_invalid=ÐдреÑа електронної пошти недійÑна. openid_been_used=ÐдреÑа OpenID '%s' вже викориÑтовуєтьÑÑ. username_password_incorrect=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або пароль. -password_complexity=Пароль не відповідає вимогам до ÑкладноÑті: -password_lowercase_one=Принаймні одна буква в нижньому регіÑтрі -password_uppercase_one=Принаймні одна буква в верхньому регіÑтрі +password_complexity=Пароль не відповідає вимогам ÑкладноÑті: +password_lowercase_one=Принаймні один Ñимвол нижнього регіÑтру +password_uppercase_one=Принаймні один Ñимвол верхнього регіÑтру password_digit_one=Принаймні одна цифра -password_special_one=Принаймні один Ñпеціальний Ñимвол (пунктуаціÑ, дужки, лапки тощо) -enterred_invalid_repo_name=Ðевірно введено ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ. -enterred_invalid_org_name=Ðевірно введено ім'Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ—. -enterred_invalid_owner_name=Ім'Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ влаÑника не Ñ” дійÑним. -enterred_invalid_password=Введений вами пароль некоректний. +password_special_one=Принаймні один Ñпеціальний Ñимвол (розділові знаки, дужки, лапки тощо) +enterred_invalid_repo_name=Ви ввели неправильну назву Ñховища. +enterred_invalid_org_name=Ви ввели неправильну назву організації. +enterred_invalid_owner_name=Ім'Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ влаÑника недійÑне. +enterred_invalid_password=Ви ввели неправильний пароль. unset_password=КориÑтувач не вÑтановив пароль. -user_not_exist=Даний кориÑтувач не Ñ–Ñнує. +user_not_exist=КориÑтувач не Ñ–Ñнує. team_not_exist=Команда не Ñ–Ñнує. -last_org_owner=Ви не можете видалити оÑтаннього кориÑтувача з команди 'влаÑники'. У кожній команді має бути принаймні один влаÑник. +last_org_owner=Ви не можете видалити оÑтаннього кориÑтувача з групи 'влаÑників'. Ð’ організації має бути принаймні один влаÑник. cannot_add_org_to_team=Організацію неможливо додати Ñк учаÑника команди. -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 auth_failed=Помилка автентифікації: %v target_branch_not_exist=Цільової гілки не Ñ–Ñнує. +target_ref_not_exist=Цільове поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ Ñ–Ñнує %s admin_cannot_delete_self=Ви не можете видалити Ñебе, допоки ви адмініÑтратор. Будь лаÑка, Ñпочатку видаліть права адмініÑтратора. [user] -change_avatar=Змінити Ñвій аватар… -repositories=Репозиторії +change_avatar=Змінити аватар… +repositories=Сховища activity=Публічна активніÑть -followers=Читачі +followers=ПоÑлідовники show_more=Показати більше -starred=Обрані Репозиторії -watched=ВідÑтежувані репозиторії +starred=Обрані Ñховища +watched=ВідÑтежувані Ñховища code=Код -projects=Проєкт +projects=Проєкти overview=ОглÑд -following=Читає -follow=ПідпиÑатиÑÑ -unfollow=ВідпиÑатиÑÑ +follow=Стежити +unfollow=Ðе Ñтежити user_bio=Ð‘Ñ–Ð¾Ð³Ñ€Ð°Ñ„Ñ–Ñ -disabled_public_activity=Цей кориÑтувач вимкнув публічний показ діÑльноÑті. +disabled_public_activity=Цей кориÑтувач вимкнув публічну видиміÑть активноÑті. show_on_map=Показати це міÑце на карті settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача @@ -572,6 +612,10 @@ block.title=Заблокувати кориÑтувача block.info=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не дозволÑÑ” йому взаємодіÑти зі Ñховищами, наприклад, відкривати або коментувати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð°Ð±Ð¾ задачі. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. block.info_2=Ñлідкують за вашим обліковим запиÑом block.info_3=надÑилати вам ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ @згадавши ваше ім'Ñ +block.note=Примітка +block.note.title=Ðеобов’Ñзкова примітка: +block.list=Заблоковані кориÑтувачі +block.list.none=Ви не заблокували жодного кориÑтувача. [settings] profile=Профіль @@ -580,12 +624,13 @@ appearance=Зовнішній виглÑд password=Пароль security=Безпека avatar=Ðватар -ssh_gpg_keys=SSH / GPG ключі +ssh_gpg_keys=Ключі SSH / GPG social=Соціальні облікові запиÑи applications=Додатки orgs=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми repos=Репозиторії delete=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ +twofa=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (TOTP) account_link=Прив'Ñзані облікові запиÑи organization=Організації @@ -598,11 +643,12 @@ update_profile=Оновити профіль update_language=Оновити мову update_language_success=Мову оновлено. update_profile_success=Профіль уÑпішно оновлено. -change_username=Ваше Ім'Ñ ÐºÑ€Ð¸Ñтувача було змінено. +change_username=Ваше ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача змінено. continue=Продовжити cancel=Відмінити language=Мова ui=Тема +hidden_comment_types=Приховані типи коментарів comment_type_group_reference=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ comment_type_group_label=Мітка comment_type_group_milestone=Етап @@ -623,13 +669,13 @@ keep_activity_private_popup=Показувати вашу активніÑть Ð lookup_avatar_by_mail=Знайти Ðватар за адреÑою електронної пошти federated_avatar_lookup=Знайти зовнішній аватар -enable_custom_avatar=Увімкнути кориÑтувацькі аватари -choose_new_avatar=Оберіть новий аватар +enable_custom_avatar=Увімкнути кориÑтувацький аватар +choose_new_avatar=Обрати новий аватар update_avatar=Оновити аватар delete_current_avatar=Видалити поточний аватар uploaded_avatar_not_a_image=Завантажений файл не Ñ” зображеннÑм. uploaded_avatar_is_too_big=Розмір завантаженого файлу (%d KiB) перевищує макÑимальний розмір (%d KiB). -update_avatar_success=Ваш аватар був змінений. +update_avatar_success=Ваш аватар оновлено. update_user_avatar_success=Ðватар кориÑтувача оновлено. cropper_prompt=Ви можете відредагувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ збереженнÑм. Відредаговане Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ðµ збережено Ñк PNG. @@ -638,64 +684,64 @@ old_password=Поточний пароль new_password=Ðовий пароль retype_new_password=Підтвердити новий пароль password_incorrect=Поточний пароль неправильний. -change_password_success=Ваш пароль був оновлений. Тепер увійдіть в ÑиÑтему, викориÑтовуючи новий пароль. -password_change_disabled=Ðелокальні акаунти не можуть змінити пароль через Gitea. +change_password_success=Ваш пароль оновлено. Відтепер входьте в ÑиÑтему, викориÑтовуючи новий пароль. +password_change_disabled=Ðелокальні кориÑтувачі не можуть оновити Ñвій пароль через Ð²ÐµÐ±Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Gitea. emails=ÐдреÑа електронної пошти -manage_emails=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами ел. пошти -manage_themes=Виберіть тему за замовчуваннÑм -manage_openid=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ OpenID +manage_emails=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами електронної пошти +manage_themes=Обрати типову тему +manage_openid=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами OpenID theme_desc=Ð¦Ñ Ñ‚ÐµÐ¼Ð° буде типовою Ð´Ð»Ñ Ð²Ñього Ñайту. primary=ОÑновний activated=Ðктивовано requires_activation=Потрібна Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ -primary_email=Зробити оÑновним +primary_email=Зробити оÑновною activate_email=ÐадіÑлати активацію -activations_pending=Ðктивації в очікуванні +activations_pending=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— delete_email=Видалити email_deletion=Видалити адреÑу електронної пошти -email_deletion_desc=Електронна адреÑа та пов'Ñзана з нею Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ видалена з вашого облікового запиÑу. Git коміти, здійÑнені через цю електронну адреÑу, залишитьÑÑ Ð±ÐµÐ· змін. Продовжити? -email_deletion_success=ÐдреÑу електронної пошти було видалено. +email_deletion_desc=ÐдреÑа електронної пошти та пов'Ñзана з нею Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ видалена з вашого облікового запиÑу. Коміти Git, здійÑнені через цю адреÑу електронну пошту, залишитьÑÑ Ð±ÐµÐ· змін. Продовжити? +email_deletion_success=ÐдреÑу електронної пошти видалено. theme_update_success=Тему оновлено. -theme_update_error=Вибрана тема не Ñ–Ñнує. +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_openid=Додати OpenID URI add_email_success=Додано нову адреÑу електронної пошти. -email_preference_set_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти уÑпішно вÑтановлені. -add_openid_success=Ðова адреÑа OpenID була додана. +email_preference_set_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти уÑпішно вÑтановлено. +add_openid_success=Додано нову адреÑу OpenID. keep_email_private=Приховати адреÑу електронної пошти -openid_desc=OpenID дозволÑÑ” делегувати аутентифікацію зовнішньому поÑтачальнику поÑлуг. +openid_desc=OpenID дозволÑÑ” делегувати автентифікацію зовнішньому поÑтачальнику поÑлуг. -manage_ssh_keys=Керувати SSH ключами -manage_ssh_principals=Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ SSH Ñертифікатами кориÑтувачів -manage_gpg_keys=Керувати GPG ключами +manage_ssh_keys=Керувати ключами SSH +manage_ssh_principals=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ð¾ÑÑ‚Ñми Ñертифікатів SSH +manage_gpg_keys=Керувати ключами SSH add_key=Додати ключ -ssh_desc=Ці відкриті SSH-ключі пов'Ñзані з вашим обліковим запиÑом. Відповідні приватні ключі дозволÑють отримати повний доÑтуп до ваших репозиторіїв. -principal_desc=Ці наÑтройки SSH Ñертифікатів вказані у вашому обліковому запиÑÑ– та надають повний доÑтуп до ваших репозиторіїв. -gpg_desc=Ці публічні ключі GPG пов'Ñзані з вашим обліковим запиÑом. Тримайте Ñвої приватні ключі в безпеці, оÑкільки вони дозволÑють здійÑнювати перевірку комітів. -ssh_helper=<strong>Потрібна допомога?</strong> ДивітьÑÑ Ð³Ñ–Ð´ на GitHub з <a href="%s"> генерації ключів SSH</a> або Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ <a href="%s">типових неполадок SSH</a>. -gpg_helper=<strong> Потрібна допомога? </strong> ПереглÑньте поÑібник GitHub <a href="%s"> про GPG </a>. -add_new_key=Додати SSH ключ -add_new_gpg_key=Додати GPG ключ +ssh_desc=Ці публічні ключі SSH пов'Ñзані з вашим обліковим запиÑом. Відповідні приватні ключі надають повний доÑтуп до ваших Ñховищ. +principal_desc=Ці ідентифікатори Ñертифікатів SSH прив'Ñзані до вашого облікового запиÑу Ñ– надають повний доÑтуп до ваших Ñховищ. +gpg_desc=Ці публічні ключі GPG пов'Ñзані з вашим обліковим запиÑом. Зберігайте Ñвої приватні ключі в безпеці, оÑкільки вони дозволÑють підтверджувати коміти. +ssh_helper=<strong>Потрібна допомога?</strong> ОзнайомтеÑÑ Ð· інÑтрукцією GitHub щодо <a href="%s">ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð²Ð»Ð°Ñних ключів SSH</a> або Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ <a href="%s">типових неполадок SSH.</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 ключ вже був додано до Ñервера. +add_new_principal=Додати ідентичніÑть +ssh_key_been_used=Цей ключ SSH вже було додано до Ñервера. ssh_key_name_used=Ключ SSH з таким ім'Ñм вже Ñ–Ñнує у вашому обліковому запиÑÑ–. -ssh_principal_been_used=Цей кориÑтувач вже був доданий на Ñервер. +ssh_principal_been_used=Цю ідентичніÑть вже було додано до Ñервера. gpg_key_id_used=Публічний ключ GPG з таким Ñамим ідентифікатором вже Ñ–Ñнує. -gpg_no_key_email_found=Цей ключ GPG не відповідає жодній активованій поштовій адреÑÑ–, Ñка пов'Ñзана з вашим обліковим запиÑом. Його вÑе рівно можна додати, Ñкщо ви підпишете наданий токен. -gpg_key_matched_identities=Відповідні отримувачі: -gpg_key_matched_identities_long=Вбудовані ідентифікатори цього ключа збігаютьÑÑ Ð· наÑтупними активованими адреÑами електронної пошти вказаного кориÑтувача. Коміти, Ñкі відповідають цим адреÑам, можуть бути підтверджені цим ключем. +gpg_no_key_email_found=Цей ключ GPG не відповідає жодній активованій адреÑÑ– електронної пошти, пов'Ñзаній з вашим обліковим запиÑом. Його вÑе одно можна додати, Ñкщо ви підпишете наданий токен. +gpg_key_matched_identities=Відповідні ідентичноÑті: +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_invalid_token_signature=Ðадані ключ GPG, Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñ– токен не збігаютьÑÑ Ð°Ð±Ð¾ токен заÑтарілий. gpg_token_required=Вам потрібно надати Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð¸Ð¶Ñ‡ÐµÐ²ÐºÐ°Ð·Ð°Ð½Ð¾Ð³Ð¾ токена gpg_token=Токен gpg_token_help=Ви можете Ñтворити Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð° допомогою: @@ -703,127 +749,129 @@ gpg_token_signature=ТекÑтовий (armored) Ð¿Ñ–Ð´Ð¿Ð¸Ñ GPG key_signature_gpg_placeholder=`ПочинаєтьÑÑ Ð· "-----BEGIN PGP SIGNATURE-----"` verify_gpg_key_success=Ключ GPG '%s' перевірено. ssh_key_verified=Перевірений ключ -ssh_key_verify=Підтвердити +ssh_key_verify=Перевірити ssh_token_required=Вам потрібно надати Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð¸Ð¶Ñ‡ÐµÐ²ÐºÐ°Ð·Ð°Ð½Ð¾Ð³Ð¾ токена ssh_token=Токен ssh_token_help=Ви можете Ñтворити Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð° допомогою: verify_ssh_key_success=Ключ SSH '%s' перевірено. subkeys=Підключі -key_id=ID ключа -key_name=Ім'Ñ ÐºÐ»ÑŽÑ‡Ð° +key_id=Ідентифікатор ключа +key_name=Ðазва ключа key_content=ЗміÑÑ‚ principal_content=ЗміÑÑ‚ -add_key_success=SSH ключ '%s' додано. -add_gpg_key_success=GPG ключ '%s' додано. +add_key_success=Ключ SSH '%s' додано. +add_gpg_key_success=Ключ GPG '%s' додано. delete_key=Видалити -ssh_key_deletion=Видалити SSH ключ -gpg_key_deletion=Видалити GPG ключ -ssh_principal_deletion=Видалити SSH Ñертифікат кориÑтувача +ssh_key_deletion=Видалити ключ SSH +gpg_key_deletion=Видалити ключ GPG +ssh_principal_deletion=Видалити ідентичніÑть Ñертифікату SSH ssh_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити? -gpg_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ GPG ключа ÑкаÑовує перевірку підпиÑаних ним комітів. Продовжити? -ssh_principal_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити? -ssh_key_deletion_success=SSH ключ був видалений. -gpg_key_deletion_success=GPG було видалено. -ssh_principal_deletion_success=КориÑтувача видалено. +gpg_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° GPG ÑкаÑовує перевірку підпиÑаних ним комітів. Продовжити? +ssh_principal_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ð¾Ñті Ñертифіката SSH ÑкаÑовує доÑтуп до вашого облікового запиÑу. Продовжити? +ssh_key_deletion_success=Ключ SSH видалено. +gpg_key_deletion_success=Ключ GPG видалено. +ssh_principal_deletion_success=ІдентичніÑть видалено. added_on=Додано %s valid_until_date=ДійÑно до %s valid_forever=ДійÑний завжди -last_used=ОÑтаннє викориÑÑ‚Ð°Ð½Ð½Ñ -no_activity=Жодної діÑльноÑті -can_read_info=Читати -can_write_info=ÐапиÑати -key_state_desc=Цей ключ викориÑтовувавÑÑ Ð² оÑтанні 7 днів -token_state_desc=Цей токен викориÑтовувавÑÑ Ð² оÑтанні 7 днів -principal_state_desc=УчаÑтник був на Ñайті в оÑтанні 7 днів +last_used=ВоÑтаннє викориÑтано +no_activity=ÐÐµÑ‰Ð¾Ð´Ð°Ð²Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ–Ñть відÑÑƒÑ‚Ð½Ñ +can_read_info=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ +can_write_info=Ð—Ð°Ð¿Ð¸Ñ +key_state_desc=Цей ключ викориÑтовувавÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів +token_state_desc=Цей токен викориÑтовувавÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів +principal_state_desc=Ð¦Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ‡Ð½Ñ–Ñть викориÑтовувалаÑÑ Ð¿Ñ€Ð¾Ñ‚Ñгом оÑтанніх 7 днів show_openid=Показати у профілю -hide_openid=Ðе показувати у профілі +hide_openid=Приховати з профілю ssh_disabled=SSH вимкнено ssh_signonly=SSH наразі вимкнено, тому ці ключі викориÑтовуютьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ підпиÑу комітів. -ssh_externally_managed=Цей ключ SSH має зовнішнє ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача -manage_social=Керувати зв'Ñзаними обліковими запиÑами Ñоціальних мереж +ssh_externally_managed=Цей ключ SSH керуєтьÑÑ Ð·Ð·Ð¾Ð²Ð½Ñ– Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача +manage_social=Керувати пов'Ñзаними обліковими запиÑами Ñоціальних мереж unbind=Від'єднати -manage_access_token=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð°Ð¼Ð¸ доÑтупу -generate_new_token=Згенерувати новий токен -tokens_desc=Ці токени надають доÑтуп до вашого облікового запиÑу за допомогою Gitea API. -token_name=Ім'Ñ Ñ‚Ð¾ÐºÐµÐ½Ñƒ +manage_access_token=Керувати токенами доÑтупу +generate_new_token=Створити новий токен +tokens_desc=Ці токени надають доÑтуп до вашого облікового запиÑу за допомогою API Gitea. +token_name=Ðазва токену generate_token=Згенерувати токен -generate_token_success=Ваш новий токен був Ñтворений. Скопіюйте його зараз, оÑкільки він не буде показаний знову. +generate_token_success=Ваш новий токен Ñтворено. Скопіюйте його зараз, оÑкільки він не буде показаний знову. generate_token_name_duplicate=Ðазва програми <strong>%s</strong> вже викориÑтовуєтьÑÑ. Будь лаÑка, викориÑтайте нову. delete_token=Видалити access_token_deletion=Видалити токен доÑтупу access_token_deletion_cancel_action=Відмінити access_token_deletion_confirm_action=Видалити -delete_token_success=Токен був знищений. Програми, що викориÑтовують його, більше не мають доÑтупу до вашого облікового запиÑу. +delete_token_success=Токен знищено. Додатки, що викориÑтовують його, більше не мають доÑтупу до вашого облікового запиÑу. permissions_access_all=Ð’ÑÑ– (загальнодоÑтупні, приватні та з обмеженим доÑтупом) permission_not_set=Ðе вÑтановлено permission_no_access=Ðемає доÑтупу permission_read=Прочитані permission_write=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ– Ð·Ð°Ð¿Ð¸Ñ permission_anonymous_read=Ðнонімне Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ -permission_everyone_read=УÑÑ– читають -permission_everyone_write=УÑÑ– пишуть - -manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð°Ð¼Ð¸ OAuth2 -edit_oauth2_application=Редагувати програму OAuth2 -oauth2_applications_desc=Програми OAuth2 дають можливіÑть вашим Ñтороннім програмам надійно аутентифікувати кориÑтувачів у цьому екземплÑрі Gitea. -remove_oauth2_application=Видалити програму OAuth2 -remove_oauth2_application_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¸ OAuth2 ÑкаÑовує доÑтуп до вÑÑ–Ñ… підпиÑаних маркерів доÑтупу. Продовжити? -remove_oauth2_application_success=Програму видалено. -create_oauth2_application=Створити нову програму OAuth2 -create_oauth2_application_button=Створити програму +permission_everyone_read=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… +permission_everyone_write=Ð—Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… +permissions_list=Дозволи: + +manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ°Ð¼Ð¸ OAuth2 +edit_oauth2_application=Редагувати додаток OAuth2 +oauth2_applications_desc=Додатки OAuth2 дозволÑють вашому Ñторонньому додатку безпечно автентифікувати кориÑтувачів у цьому екземплÑрі Gitea. +remove_oauth2_application=Видалити додаток OAuth2 +remove_oauth2_application_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ° OAuth2 ÑкаÑує доÑтуп до вÑÑ–Ñ… підпиÑаних токенів доÑтупу. Продовжити? +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_application_name=Ðазва додатка save_application=Зберегти -oauth2_client_id=ID Клієнта +oauth2_client_id=Ідентифікатор клієнта oauth2_client_secret=Ключ клієнта oauth2_regenerate_secret=Відновити ключ -oauth2_regenerate_secret_hint=Ви втратили Ñвій ключ? +oauth2_regenerate_secret_hint=Втратили ключ? oauth2_application_edit=Редагувати oauth2_application_create_description=Програми OAuth2 надають вашим Ñтороннім програмам доÑтуп до облікових запиÑів кориÑтувачів у цьому екземплÑрі. authorized_oauth2_applications=Ðвторизовані програми OAuth2 revoke_key=Відкликати revoke_oauth2_grant=СкаÑувати доÑтуп -revoke_oauth2_grant_description=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— програми третьої Ñторони не дозволить їй отримувати доÑтуп до ваших даних. Ви впевнені? +revoke_oauth2_grant_description=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñтороннього додатка не дозволить йому отримувати доÑтуп до ваших даних. Ви впевнені? -twofa_is_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° даний Ñ‡Ð°Ñ <strong>викориÑтовує</strong> двофакторну автентифікацію. -twofa_not_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– не викориÑтовує двофакторну автентифікаціїю. +twofa_is_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– <strong>викориÑтовує</strong> двофакторну автентифікацію. +twofa_not_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– не викориÑтовує двофакторну автентифікацію. twofa_disable=Вимкнути двофакторну автентифікацію twofa_enroll=Увімкнути двофакторну автентифікацію -twofa_disable_note=При необхідноÑті можна відключити двофакторну автентифікацію. +twofa_disable_note=За потреби ви можете вимкнути двофакторну автентифікацію. twofa_disable_desc=Ð’Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації зробить ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼ÐµÐ½Ñˆ безпечним. Продовжити? -twofa_disabled=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð°. -scan_this_image=ПроÑкануйте це Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¸Ð¼ додатком Ð´Ð»Ñ Ð´Ð²ÑƒÑ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації: -or_enter_secret=Ðбо введіть Ñекрет: %s +twofa_disabled=Двофакторну автентифікацію вимкнено. +scan_this_image=ВідÑкануйте це Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¸Ð¼ додатком Ð´Ð»Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації: +or_enter_secret=Ðбо введіть код: %s then_enter_passcode=І введіть пароль, Ñкий відображаєтьÑÑ Ð² додатку: passcode_invalid=Ðекоректний пароль. Спробуй ще раз. -twofa_failed_get_secret=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ Ñекрет. +twofa_failed_get_secret=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ код. -manage_account_links=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¸Ð¼Ð¸ запиÑами -manage_account_links_desc=Ці зовнішні акаунти прив'Ñзані до вашого аккаунту Gitea. +manage_account_links=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²'Ñзаними обліковими запиÑами +manage_account_links_desc=Ці зовнішні облікові запиÑи прив'Ñзані до вашого облікового запиÑу Gitea. account_links_not_available=Ðаразі немає зовнішніх облікових запиÑів, пов'Ñзаних із вашим обліковим запиÑом Gitea. link_account=Прив'Ñзати обліковий Ð·Ð°Ð¿Ð¸Ñ -remove_account_link=Видалити облікові запиÑи +remove_account_link=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ remove_account_link_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²'Ñзаного облікового запиÑу відкликає його доÑтуп до вашого облікового запиÑу Gitea. Продовжити? -remove_account_link_success=Зв'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾. +remove_account_link_success=Прив'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾. -orgs_none=Ви не Ñ” учаÑником будь-Ñкої організації. +orgs_none=Ви не Ñ” членом організації. -delete_account=Видалити ваш обліковий Ð·Ð°Ð¿Ð¸Ñ -delete_prompt=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить обліковий Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. Це <strong>ÐЕ МОЖЛИВО</strong> відмінити. -delete_with_all_comments=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð¾Ð»Ð¾Ð´ÑˆÐ¸Ð¹ за %s днів. Щоб уникнути коментарів-привидів, вÑÑ– запити/PR коментрарі будуть видалені з ним. -confirm_delete_account=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ -delete_account_title=Видалити цей обліковий Ð·Ð°Ð¿Ð¸Ñ +delete_account=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ +delete_prompt=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить ваш обліковий запиÑ. Її <strong>ÐЕ МОЖЛИВО</strong> ÑкаÑувати. +delete_with_all_comments=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼Ð¾Ð»Ð¾Ð´ÑˆÐ¸Ð¹ за %s днів. Щоб уникнути коментарів-привидів, уÑÑ– ваші коментарі будуть видалені разом з ним. +confirm_delete_account=Підтвердити Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ +delete_account_title=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ delete_account_desc=Ви впевнені, що хочете оÑтаточно видалити цей обліковий запиÑ? -email_notifications.enable=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ email -email_notifications.onmention=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ email тільки коли згадують -email_notifications.disable=Вимкнути email ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ -email_notifications.submit=Ðалаштувати параметри email +email_notifications.enable=Увімкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою +email_notifications.onmention=ПовідомлÑти електронною поштою коли згадують +email_notifications.disable=Вимкнути ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою +email_notifications.submit=Ðалаштувати параметри електронної пошти +email_notifications.andyourown=І ваші влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ visibility=ВидиміÑть кориÑтувача visibility.public=Публічний @@ -832,22 +880,22 @@ visibility.private=Приватний [repo] owner=ВлаÑник -owner_helper=ДеÑкі організації можуть не відображатиÑÑ Ñƒ випадаючому ÑпиÑку через макÑимальну кількіÑть репозиторііїв. -repo_name=Ðазва репозиторію -repo_size=Розмір репозиторію +owner_helper=ДеÑкі організації можуть не відображатиÑÑ Ñƒ ÑпиÑку через Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð° макÑимальну кількіÑть Ñховищ. +repo_name=Ðазва Ñховища +repo_size=Розмір Ñховища template=Шаблон -template_select=Оберіть шаблон. -template_helper=Зробити репозиторій шаблоном -template_description=Шаблонні репозиторії дозволÑють кориÑтувачам генерувати нові репозиторії із такою ж Ñтруктурою директорій, файлами та додатковими налаштуваннÑми. +template_select=Обрати шаблон. +template_helper=Зробити Ñховище шаблоном +template_description=Шаблонні Ñховища дозволÑють кориÑтувачам Ñтворювати нові Ñховища з такою ж Ñтруктурою каталогів, файлами та додатковими налаштуваннÑми. visibility=ВидиміÑть -visibility_description=Тільки влаÑник або члени організації Ñкі мають віповідні права, зможуть побачити. +visibility_description=Тільки влаÑник або члени організації, Ñкщо вони мають дозвіл, зможуть його побачити. visibility_helper=Зробити Ñховище приватним visibility_helper_forced=ÐдмініÑтратор вашого Ñайту налаштував параметри: вÑÑ– нові репозиторії будуть приватними. visibility_fork_helper=(Ці зміни вплинуть на вÑÑ– форки.) clone_helper=Потрібна допомога у клонуванні? Відвідайте Ñторінку <a target="_blank" rel="noopener" href="%s">Допомога</a>. fork_repo=Форкнути репозиторій fork_from=Форк з -fork_visibility_helper=Ðеможливо змінити видиміÑть форкнутого репозиторію. +fork_visibility_helper=Ðеможливо змінити видиміÑть розгалуженого Ñховища. all_branches=УÑÑ– гілки view_all_branches=ПереглÑнути вÑÑ– гілки use_template=ЗаÑтоÑувати цей шаблон @@ -855,64 +903,64 @@ open_with_editor=Відкрити в %s download_zip=Завантажити ZIP download_tar=Завантажити TAR.GZ download_bundle=Завантажити BUNDLE -generate_repo=Згенерувати репозиторій -generate_from=Генерувати з +generate_repo=Створити Ñховище +generate_from=Створити з repo_desc=ÐžÐ¿Ð¸Ñ -repo_desc_helper=Введіть короткий Ð¾Ð¿Ð¸Ñ (опціонально) +repo_desc_helper=Введіть короткий Ð¾Ð¿Ð¸Ñ (необов'Ñзково) repo_no_desc=Ðемає опиÑу repo_lang=Мови -repo_gitignore_helper=Виберіть шаблон .gitignore. -repo_gitignore_helper_desc=Оберіть з ÑпиÑку мовних шаблонів файли, Ñкі не будуть відÑтежуватиÑÑŒ. Типові артефакти, Ñкі генеруютьÑÑ Ð·Ð° допомогою інÑтрументів побудови кожної мови, за замовчуваннÑм включені до .gitignor. +repo_gitignore_helper=Обрати шаблон .gitignore. +repo_gitignore_helper_desc=Оберіть з ÑпиÑку мовних шаблонів файли, Ñкі не Ñлід відÑтежувати. Типові артефакти, що генеруютьÑÑ Ñ–Ð½Ñтрументами збірки кожної мови, за замовчуваннÑм включені до .gitignor. issue_labels=Мітки задачі -issue_labels_helper=Вибрати мітку Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–. +issue_labels_helper=Виберіть набір міток задачі. license=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ -license_helper=Виберіть ліцензійний файл. -license_helper_desc=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ñ€ÐµÐ³ÑƒÐ»ÑŽÑ” те, що інші можуть Ñ– не можуть робити з вашим кодом. Ðе впевнені, що Ñаме підходить Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a> +license_helper=Обрати файл ліцензії. +license_helper_desc=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡Ð°Ñ”, що інші можуть робити з вашим кодом, а що ні. Ðе впевнені, Ñка підходить Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">Вибір ліцензії.</a> multiple_licenses=Кілька ліцензій object_format=Формат об'єкту readme=README readme_helper=Виберіть шаблон README. -readme_helper_desc=Це міÑце, де ви можете напиÑати повний Ð¾Ð¿Ð¸Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту. -auto_init=Ініціалізувати репозиторій (Додає .gitignore, LICENSE та README) +readme_helper_desc=Тут ви можете повніÑтю опиÑати ваш проєкт. +auto_init=Ініціалізувати Ñховище (додає файли .gitignore, ліцензію та README) trust_model_helper=Виберіть модель довіри Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñу. Можливі варіанти: -trust_model_helper_collaborator=Співавтор: підпиÑи довіри від Ñпівавторів -trust_model_helper_committer=УчаÑник: довірені підпиÑи учаÑтників -trust_model_helper_collaborator_committer=Співавтор+Комітер: довірчі підпиÑи від Ñпівавторів, Ñкі відповідають комітеру -trust_model_helper_default=За замовчуваннÑм: викориÑтовувати Ñтандартну модель довіри Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— уÑтановки -create_repo=Створити репозиторій +trust_model_helper_collaborator=Співавтор: довірÑти підпиÑам Ñпівавторів +trust_model_helper_committer=Комітер: довірÑти підпиÑам, Ñкі відповідають комітерам +trust_model_helper_collaborator_committer=Співавтор+Комітер: довірÑти підпиÑам Ñпівавторів, Ñкі відповідають комітеру +trust_model_helper_default=Типово: викориÑтовувати Ñтандартну модель довіри Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— уÑтановки +create_repo=Створити Ñховище default_branch=Головна гілка default_branch_label=типово -default_branch_helper=Гілка за замовчуваннÑм Ñ” базовою гілкою Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° комітів коду. +default_branch_helper=Типова гілка Ñ” базовою гілкою Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° комітів. mirror_prune=ОчиÑтити -mirror_prune_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ñтарілих поÑилань Ñкі ви відÑлідковуєте -mirror_interval_invalid=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ñ” неприпуÑтимим. +mirror_prune_desc=Видалити заÑтарілі поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° віддалені відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ +mirror_interval_invalid=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний. mirror_sync=Ñинхронізовано mirror_sync_on_commit=Синхронізувати, коли надÑилаютьÑÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ð¸ -mirror_address=ÐšÐ»Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð· URL-адреÑи -mirror_address_desc=ПоміÑтіть будь-Ñкі необхідні облікові дані у розділі ÐвторизаціÑ. -mirror_lfs=Склад великих файлів (LFS) -mirror_lfs_desc=Ðктивувати дзеркальне Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… LFS. +mirror_address=Клонувати з URL-адреÑи +mirror_address_desc=Введіть необхідні облікові дані в розділі ÐвторизаціÑ. +mirror_lfs=Сховище великих файлів (LFS) +mirror_lfs_desc=Ðктивувати Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… LFS. mirror_lfs_endpoint=Кінцева точка LFS -mirror_lfs_endpoint_desc=Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ñпробує викориÑтовувати url Ð´Ð»Ñ ÐºÐ»Ð¾Ð½Ñƒ щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначити LFS-Ñервер</a>. Ви також можете вказати кінцеву точку кориÑтувача, Ñкщо дані репозиторію LFS зберігаютьÑÑ Ð² іншому міÑці. +mirror_lfs_endpoint_desc=Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ñпробує викориÑтати URL-адреÑу клону Ð´Ð»Ñ <a target="_blank" rel="noopener noreferrer" href="%s">Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñервера LFS</a>. Ви також можете вказати влаÑну кінцеву точку, Ñкщо дані LFS Ñховища зберігаютьÑÑ Ð² іншому міÑці. mirror_last_synced=ОÑÑ‚Ð°Ð½Ð½Ñ ÑÐ¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ mirror_password_placeholder=(без змін) -mirror_password_blank_placeholder=(відключено) +mirror_password_blank_placeholder=(Ðе вÑтановлено) mirror_password_help=Змініть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача, щоб видалити збережений пароль. watchers=СпоÑтерігачі -stargazers=Зацікавлені +stargazers=Шанувальники stars_remove_warning=Це видалить уÑÑ– зірки з цього Ñховища. forks=Форки stars=Зірки -reactions_more=додати %d більше -unit_disabled=ÐдмініÑтратор Ñайту вимкнув цей розділ репозиторію. +reactions_more=Ñ– ще %d +unit_disabled=ÐдмініÑтратор Ñайту вимкнув цей розділ Ñховища. language_other=Інші -adopt_search=Введіть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ неприйнÑтних репозиторіїв... (залиште порожнім, щоб знайти вÑÑ–) -adopt_preexisting_label=ПрийнÑті файли -adopt_preexisting=ПрийнÑти вже Ñ–Ñнуючі файли -adopt_preexisting_content=Створити репозиторій з %s -adopt_preexisting_success=ПрийнÑти файли та Ñтворити репозиторій з %s +adopt_search=Введіть ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ неприйнÑтих Ñховищ... (залиште порожнім, щоб знайти вÑÑ–) +adopt_preexisting_label=ПрийнÑти файли +adopt_preexisting=ПрийнÑти попередньо Ñтворені файли +adopt_preexisting_content=Створити Ñховище з %s +adopt_preexisting_success=ПрийнÑти файли та Ñтворити Ñховище з %s delete_preexisting_label=Видалити -delete_preexisting=Видалити Ñ–Ñнуючі файли +delete_preexisting=Видалити попередньо Ñтворені файли delete_preexisting_content=Видалити файли з %s delete_preexisting_success=Видалено неприйнÑті файли в %s blame_prior=ПереглÑнути анотацію, що передує цій зміні @@ -920,9 +968,9 @@ user_search_tooltip=Показує не більше 30 кориÑтувачів tree_path_not_found=ШлÑÑ… %[1]s не Ñ–Ñнує в %[2]s -transfer.accept=Дозволити транÑфер +transfer.accept=Дозволити Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ transfer.accept_desc=`ПереміÑтити до "%s"` -transfer.reject=Відхилити транÑфер +transfer.reject=Відхилити Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ transfer.reject_desc=`СкаÑувати Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð´Ð¾ "%s"` desc.private=Приватний @@ -930,34 +978,34 @@ desc.public=Публічний desc.public_access=Публічний доÑтуп desc.template=Шаблон desc.internal=Внутрішній -desc.archived=Ðрхівний +desc.archived=Ðрхівований desc.sha256=SHA256 template.items=Елементи шаблону template.git_content=ВміÑÑ‚ Git (типова гілка) -template.git_hooks=Перехоплювачі Git -template.webhooks=Webhook'и +template.git_hooks=Хуки Git +template.webhooks=Веб-хуки template.topics=Теми template.avatar=Ðватар template.issue_labels=Мітки задачі template.one_item=Слід обрати хоча б один елемент шаблону -template.invalid=Слід обрати шаблонний репозиторій +template.invalid=Слід обрати шаблонне Ñховище -archive.issue.nocomment=Цей репозиторій архівовано. Ви не можете коментувати задачі. -archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквеÑти. +archive.issue.nocomment=Це Ñховище архівовано. Ви не можете коментувати задачі. +archive.pull.nocomment=Це Ñховище архівовано. Ви не можете коментувати запити на злиттÑ. -form.reach_limit_of_creation_1=Ви вже доÑÑгли ліміту в %d репозиторіїв. -form.reach_limit_of_creation_n=Ви доÑÑгли макÑимальної кількоÑті %d Ñтворених репозиторіїв. +form.reach_limit_of_creation_1=Ви доÑÑгли макÑимальної кількоÑті %d Ñховища. +form.reach_limit_of_creation_n=Ви доÑÑгли макÑимальної кількоÑті %d Ñховищ. need_auth=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ migrate_options=Параметри міграції migrate_service=Ð¡ÐµÑ€Ð²Ñ–Ñ Ð¼Ñ–Ð³Ñ€Ð°Ñ†Ñ–Ñ— migrate_options_mirror_helper=Це Ñховище буде дзеркалом -migrate_options_lfs=ПеренеÑÐµÐ½Ð½Ñ LFS файлів +migrate_options_lfs=ПеренеÑÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² LFS migrate_options_lfs_endpoint.label=Кінцева точка LFS -migrate_options_lfs_endpoint.description=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ намагатиÑÑ Ð²Ð¸ÐºÐ¾Ñ€Ð¸Ñтовувати ваш Git віддалено, щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначати LFS Ñервер</a>. Ви також можете вказати Ñвою кінцеву точку, Ñкщо дані репозиторію LFS зберігаютьÑÑ Ð² іншому міÑці. -migrate_options_lfs_endpoint.description.local=Також підтримуютьÑÑ ÑˆÐ»Ñхи на локальному Ñервері. -migrate_items=Деталі міграції +migrate_options_lfs_endpoint.description=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñпробує викориÑтати ваш Git віддалено, щоб <a target="_blank" rel="noopener noreferrer" href="%s">визначати Ñервер LFS</a>. Ви також можете вказати влаÑну кінцеву точку, Ñкщо дані Ñховища LFS зберігаютьÑÑ Ð² іншому міÑці. +migrate_options_lfs_endpoint.description.local=Також підтримуєтьÑÑ ÑˆÐ»ÑÑ… до локального Ñервера. +migrate_items=Елементи міграції migrate_items_wiki=Вікі migrate_items_milestones=Етапи migrate_items_labels=Мітки @@ -967,21 +1015,21 @@ migrate_items_merge_requests=Запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ migrate_items_releases=Релізи migrate_repo=ПеренеÑти репозиторій migrate.clone_address=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ / клонувати з URL-адреÑи -migrate.clone_address_desc=URL-адреÑа HTTP(S) або Git "clone" Ñ–Ñнуючого Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ +migrate.clone_address_desc=URL-адреÑа HTTP(S) або Git "clone" Ñ–Ñнуючого Ñховища migrate.clone_local_path=або шлÑÑ… до локального Ñерверу migrate.permission_denied=Вам не дозволено імпортувати локальні репозиторії. migrate.permission_denied_blocked=Ви не можете імпортувати з заборонених вузлів, будь лаÑка, попроÑіть адмініÑтратора перевірити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS. -migrate.invalid_lfs_endpoint=Помилкова кінцева точка LFS. +migrate.invalid_lfs_endpoint=Кінцева точка LFS недійÑна. migrate.failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ: %v migrate.migrate_items_options=Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¸Ñ… елементів потрібен токен доÑтупу migrated_from=ПеренеÑено з <a href="%[1]s">%[2]s</a> migrated_from_fake=ПеренеÑено з %[1]s -migrate.migrate=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð· %s +migrate.migrate=Мігрувати з %s migrate.migrating=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b>... migrate.migrating_failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b> не вдалаÑÑ. migrate.migrating_failed.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ½ÐµÑти: %s migrate.migrating_failed_no_addr=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ. -migrate.git.description=ПеренеÑÐµÐ½Ð½Ñ Ð»Ð¸ÑˆÐµ репозиторію з будь-Ñкої Ñлужби Git. +migrate.git.description=ПеренеÑти Ñховище з будь-Ñкого ÑервіÑу Git'у. migrate.gitlab.description=ПеренеÑти дані з gitlab.com та інших екземплÑрів GitLab. migrate.gitea.description=ПеренеÑти дані з gitea.com та інших екземплÑрів Gitea. migrate.gogs.description=ПеренеÑти дані з notabug.org та інших екземплÑрів Gogs. @@ -995,26 +1043,31 @@ 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=форк від generated_from=згенеровано з -fork_from_self=Ви не можете форкнути репозиторій, так Ñк ви його влаÑник. +fork_from_self=Ви не можете форкнути влаÑне Ñховище. fork_guest_user=Увійдіть, щоб зробити форк репозитарію. watch_guest_user=Увійдіть, щоб Ñлідкувати за цим репозиторієм. star_guest_user=Увійдіть, щоб додати в обране цей репозиторій. unwatch=Ðе Ñтежити -watch=Слідкувати +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=Цей репозиторій порожній. +empty_message=Це Ñховище порожнє. code=Код code.desc=ДоÑтуп до коду, файлів, комітів та гілок. @@ -1028,25 +1081,31 @@ tags=Теги issues=Задачі pulls=Запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ projects=Проєкти +actions=Дії labels=Мітки org_labels_desc=Мітки Ñ€Ñ–Ð²Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— можуть викориÑтовуватиÑÑ <strong>в уÑÑ–Ñ… репозиторіÑÑ…</strong> цієї організації org_labels_desc_manage=керувати milestone=Етап -milestones=Етап +milestones=Етапи commits=Коміти commit=Коміт release=Реліз releases=Релізи tag=Тег -released_this=випущені релізи +released_this=випуÑтив(-ла) file_raw=Ðеформатований file_history=ІÑÑ‚Ð¾Ñ€Ñ–Ñ file_view_source=ПереглÑнути вихідний код -file_view_rendered=ПереглÑнути відрендерено file_view_raw=ПереглÑд Raw file_permalink=ПоÑтійне поÑÐ¸Ð»Ð°Ð½Ð½Ñ -file_too_large=Цей файл завеликий щоб бути показаним. +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=Відмінити ÐµÐºÑ€Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ @@ -1059,10 +1118,10 @@ generated=Створено commit_graph=Графік комітів commit_graph.select=Виберіть гілки commit_graph.hide_pr_refs=Приховати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -commit_graph.monochrome=Монохром +commit_graph.monochrome=Монохромний commit_graph.color=Колір commit.contained_in=Цей коміт міÑтитьÑÑ Ð²: -blame=Ð—Ð²Ð¸Ð½ÑƒÐ²Ð°Ñ‡ÐµÐ½Ð½Ñ +blame=ÐÐ½Ð¾Ñ‚Ð°Ñ†Ñ–Ñ download_file=Завантажити файл normal_view=Звичайний виглÑд line=Ñ€Ñдок @@ -1072,47 +1131,49 @@ from_comment=(коментар) editor.add_file=Додати файл editor.new_file=Ðовий файл editor.upload_file=Завантажити файл -editor.edit_file=Ð ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ +editor.edit_file=Редагувати файл editor.preview_changes=Попередній переглÑд змін editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейÑÑ–. editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейÑÑ–. editor.edit_this_file=Редагувати файл editor.this_file_locked=Файл заблоковано -editor.must_be_on_a_branch=Ви повинні бути у гілці щоб зробити, або запропонувати зміни до цього файлу. -editor.fork_before_edit=Ðеобхідно зробити форк цього репозиторій, щоб внеÑти або запропонувати зміни в цей файл. +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.name_your_file=Дайте назву файлу… -editor.filename_help=Щоб додати каталог, наберіть його назву, а потім - коÑу риÑку ('/'). Щоб видалити каталог, перейдіть до початку Ð¿Ð¾Ð»Ñ Ñ– натиÑніть backspace. +editor.name_your_file=Ðазвіть файл… +editor.filename_help=Щоб додати каталог, наберіть його назву, а потім - прÑмий Ñлеш ('/'). Щоб видалити каталог, перейдіть до початку Ð¿Ð¾Ð»Ñ Ñ– натиÑніть видалити ліворуч. editor.or=або editor.cancel_lower=СкаÑувати editor.commit_signed_changes=ВнеÑти підпиÑані зміни -editor.commit_changes=Закомітити зміни editor.add_tmpl=Додати '{filename}' editor.add=Додати %s editor.update=Оновити %s editor.delete=Видалити %s editor.commit_message_desc=Додати необов'Ñзковий розширений опиÑ… -editor.signoff_desc=Додатиь Signed-off-by комітом в конці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ñƒ комітів. -editor.commit_directly_to_this_branch=Зробіть коміт прÑмо в гілку <strong class="branch-name">%s</strong>. +editor.signoff_desc=Додати «ПідпиÑано комітером» в кінці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ. +editor.commit_directly_to_this_branch=Зробити коміт безпоÑередньо в гілку <strong class="branch-name">%s</strong>. editor.create_new_branch=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту та відкрити запит на злиттÑ. editor.create_new_branch_np=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту. editor.propose_file_change=Запропонувати зміну файлу -editor.new_branch_name_desc=Ім'Ñ Ð½Ð¾Ð²Ð¾Ñ— гілки… +editor.new_branch_name_desc=Ðазва нової гілки… editor.cancel=Відмінити -editor.filename_cannot_be_empty=Ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ не може бути порожнім. +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.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. editor.upload_files_to_dir=`Завантажити файли до "%s"` editor.no_commit_to_branch=Ðе вдалоÑÑ Ð²Ð½ÐµÑти коміт безпоÑередньо до гілки, тому що: -editor.user_no_push_to_branch=КориÑтувач не може здійÑнити пуш до гілки editor.require_signed_commit=Гілка вимагає підпиÑаного коміту commits.desc=ПереглÑнути Ñ–Ñторію зміни коду. @@ -1123,17 +1184,21 @@ commits.search_all=УÑÑ– гілки commits.author=Ðвтор commits.message=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ commits.date=Дата -commits.older=Давніше -commits.newer=Ðовіше +commits.older=Старіші +commits.newer=Ðовіші commits.signed_by=ПідпиÑано commits.signed_by_untrusted_user=ПідпиÑаний недовіреним кориÑтувачем -commits.signed_by_untrusted_user_unmatched=ПідпиÑаний недовіреним кориÑтувачем, Ñкий не відповідає комітеру +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=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню ÑиÑтему відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡. @@ -1143,27 +1208,31 @@ projects.description_placeholder=ÐžÐ¿Ð¸Ñ projects.create=Створити проєкт projects.title=Ðазва projects.new=Ðовий проєкт -projects.new_subheader=Координуйте, відÑтежуйте та оновлюйте інформацію про виконувану роботу в одному міÑці, аби проєкти залишалиÑÑ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€Ð¸Ð¼Ð¸ та за розкладом. +projects.new_subheader=Координуйте, відÑтежуйте та оновлюйте Ñвою роботу в одному міÑці, щоб проєкти залишалиÑÑ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€Ð¸Ð¼Ð¸ та виконувалиÑÑ Ð·Ð° графіком. +projects.create_success=Проєкт "%s" Ñтворено. projects.deletion=Видалити проєкт projects.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ñ”ÐºÑ‚Ñƒ видалÑÑ” його з уÑÑ–Ñ… пов'Ñзаних задач. Продовжити? projects.deletion_success=Проєкт видалено. -projects.edit=Редагувати проєкти +projects.edit=Редагувати проєкт projects.edit_subheader=Проєкти організовують задачі та відÑтежують прогреÑ. -projects.modify=Оновити проєкт +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.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=Видалити Ñтовпець projects.column.color=Колір projects.open=Відкрити projects.close=Закрити -projects.card_type.desc=Попередній переглÑд карток +projects.card_type.desc=Попередні переглÑди картки projects.card_type.images_and_text=Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ– текÑÑ‚ projects.card_type.text_only=Лише текÑÑ‚ @@ -1176,7 +1245,7 @@ issues.filter_reviewers=Фільтр рецензентів issues.filter_no_results=Ðемає результатів issues.filter_no_results_placeholder=Спробуйте налаштувати Ñвої фільтри пошуку. issues.new=Ðова задача -issues.new.title_empty=Заголовок не може бути пуÑтим +issues.new.title_empty=Заголовок не може бути порожнім issues.new.labels=Мітки issues.new.no_label=Без мітки issues.new.clear_labels=ОчиÑтити мітки @@ -1187,16 +1256,19 @@ issues.new.open_projects=Відкриті проєкти issues.new.closed_projects=Закриті проєкти issues.new.no_items=Ðемає елементів issues.new.milestone=Етап -issues.new.no_milestone=Етап відÑутній +issues.new.no_milestone=Етапи відÑутні issues.new.clear_milestone=ОчиÑтити етап issues.new.assignees=Виконавці issues.new.clear_assignees=Прибрати виконавців -issues.new.no_assignees=Ðемає Ð²Ð¸ÐºÐ¾Ð½Ð°Ð²Ñ†Ñ +issues.new.no_assignees=Ðемає виконавців issues.new.no_reviewers=Ðемає рецензентів -issues.choose.get_started=Початок роботи +issues.choose.get_started=Розпочати issues.choose.open_external_link=Відкрити issues.choose.blank=Типово -issues.choose.blank_about=Створити задачу із шаблону за замовчуваннÑм. +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=Ðова мітка @@ -1206,7 +1278,7 @@ issues.create_label=Створити мітку issues.label_templates.title=Завантажити визначений набір міток issues.label_templates.info=Ще немає міток. ÐатиÑніть 'Ðова мітка' або викориÑтовуйте попередньо визначений набір міток: issues.label_templates.helper=Оберіть набір міток -issues.label_templates.use=ВикориÑтовувати набір міток +issues.label_templates.use=ВикориÑтати набір міток issues.add_label=додано %s з міткою %s issues.add_labels=додано %s з мітками %s issues.remove_label=видалено %s з міткою %s @@ -1250,19 +1322,18 @@ 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=Ðайновіші issues.filter_sort.oldest=ÐайÑтаріші -issues.filter_sort.recentupdate=Ðещодавно оновлено +issues.filter_sort.recentupdate=Ðещодавно оновлені issues.filter_sort.leastupdate=Ðайдавніше оновлені issues.filter_sort.mostcomment=Ðайбільш коментовані issues.filter_sort.leastcomment=Ðайменш коментовані -issues.filter_sort.nearduedate=Ðайновіша дата -issues.filter_sort.farduedate=ÐайÑтаріша дата -issues.filter_sort.moststars=Ðайбільш обраних -issues.filter_sort.feweststars=Ðайменш обраних +issues.filter_sort.nearduedate=Ðайближчий термін +issues.filter_sort.farduedate=Ðайвіддаленіший термін +issues.filter_sort.moststars=Ðайбільш фаворизовані +issues.filter_sort.feweststars=Ðайменш фаворизовані issues.filter_sort.mostforks=Ðайбільше форків issues.filter_sort.fewestforks=Ðайменше форків issues.action_open=Відкрити @@ -1273,7 +1344,7 @@ issues.action_milestone_no_select=Етап відÑутній issues.action_assignee=Виконавець issues.action_assignee_no_select=Ðемає Ð²Ð¸ÐºÐ¾Ð½Ð°Ð²Ñ†Ñ issues.opened_by=%[1]s відкрито <a href="%[2]s">%[3]s</a> -issues.opened_by_fake=відкрито %[1]s кориÑтувачем %[2]s +issues.opened_by_fake=%[1]s відкрито кориÑтувачем %[2]s issues.previous=Попередній issues.next=Далі issues.open_title=Відкрито @@ -1288,15 +1359,13 @@ issues.context.reference_issue=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð² новій задачі issues.context.edit=Редагувати issues.context.delete=Видалити issues.reopen_issue=Відкрити знову -issues.reopen_comment_issue=Прокоментувати та відкрити знову +issues.reopen_comment_issue=Повторно відкрити з коментарем issues.create_comment=Коментар -issues.closed_at=`закрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` -issues.reopened_at=`повторно відкрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.closed_at=`закрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.reopened_at=`повторно відкрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.commit_ref_at=`згадано цю задачу в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>` -issues.ref_issue_from=`<a href="%[3]s">поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_pull_from=`<a href="%[3]s">поÑлавÑÑ Ð½Ð° цей запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_closing_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкі закриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` -issues.ref_reopening_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкі повторно відкриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_closed_from=`<a href="%[3]s">закрив цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_reopened_from=`<a href="%[3]s">повторно відкрито цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_from=`із %[1]s` @@ -1308,15 +1377,16 @@ issues.re_request_review=Повторно попроÑити рецензію issues.is_stale=З чаÑу оÑтанньої перевірки в цей PR було внеÑено деÑкі зміни issues.remove_request_review=Видалити запит Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ issues.remove_request_review_block=Ðеможливо видалити запит Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ -issues.dismiss_review=Відхилити рецензiÑŽ -issues.dismiss_review_warning=Ви впевнені, що хочете відхилити цей відгук? -issues.sign_in_require_desc=<a href="%s">ПідпишітьÑÑ</a> щоб приєднатиÑÑ Ð´Ð¾ обговореннÑ. +issues.dismiss_review=Відхилити рецензію +issues.dismiss_review_warning=Ви впевнені, що хочете відхилити рецензію? +issues.sign_in_require_desc=<a href="%s">Увійдіть</a>, щоб приєднатиÑÑ Ð´Ð¾ розмови. issues.edit=Редагувати issues.cancel=Відмінити issues.save=Зберегти issues.label_title=Ðазва мітки issues.label_description=ÐžÐ¿Ð¸Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ -issues.label_color=Колір мітки +issues.label_color=Колір +issues.label_exclusive_order=ПорÑдок ÑÐ¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ issues.label_count=%d міток issues.label_open_issues=%d відкритих задач issues.label_edit=Редагувати @@ -1324,9 +1394,9 @@ 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.reverse_alphabetically=У зворотному алфавітному порÑдку issues.label.filter_sort.by_size=Ðайменший розмір issues.label.filter_sort.reverse_by_size=Ðайбільший розмір issues.num_participants=%d учаÑників @@ -1335,17 +1405,18 @@ issues.attachment.download=`ÐатиÑніть щоб завантажити "%s issues.subscribe=ПідпиÑатиÑÑ issues.unsubscribe=ВідпиÑатиÑÑ issues.unpin=Відкріпити -issues.lock=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ -issues.unlock=Ð Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ -issues.lock_duplicate=Задача не може бути заблокованим двічі. +issues.max_pinned=Ви не можете прикріпити більше задач +issues.lock=Блокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ +issues.unlock=Розблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ +issues.lock_duplicate=ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– не може бути заблоковано двічі. issues.unlock_error=Ðе можливо розблокувати задачу, Ñка не заблокована. issues.lock_with_reason=заблоковано Ñк <strong>%s</strong> та обмежене Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñпівавторів %s issues.lock_no_reason=заблоковано та обмежене Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñпівавторів %s -issues.unlock_comment=розблоковане Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %s +issues.unlock_comment=розблокував(ла) Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ %s issues.lock_confirm=Заблокувати issues.unlock_confirm=Розблокувати issues.lock.notice_1=- Інші кориÑтувачі не можуть додавати нові коментарі до цієї задачі. -issues.lock.notice_2=- Ви й інші Ñпівавтори, Ñкі мають доÑтуп до цього репозиторію, можете залишати коментарі, Ñкі інші можуть бачити. +issues.lock.notice_2=- Ви та інші Ñпівавтори, Ñкі мають доÑтуп до цього Ñховища, вÑе ще можете залишати коментарі, Ñкі можуть бачити інші. issues.lock.notice_3=- Ви завжди зможете розблокувати цю задачу в майбутньому. issues.unlock.notice_1=- Кожен зможе прокоментувати цю задачу ще раз. issues.unlock.notice_2=- Ви завжди зможете заблокувати цю задачу в майбутньому. @@ -1356,6 +1427,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,21 +1435,23 @@ 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=Ð§Ð°Ñ Ð½Ðµ введено. issues.time_spent_total=Загальний витрачений Ñ‡Ð°Ñ issues.time_spent_from_all_authors=`Загальний витрачений чаÑ: %s` -issues.due_date=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ -issues.invalid_due_date_format=Дата Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ Ð¼Ð°Ñ” бути в форматі 'ррр-мм-дд'. -issues.error_modifying_due_date=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ дату завершеннÑ. +issues.due_date=Строк Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ +issues.invalid_due_date_format=Формат Ñтроку Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ бути 'рррр-мм-дд'. +issues.error_modifying_due_date=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ термін виконаннÑ. issues.error_removing_due_date=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ дату завершеннÑ. issues.push_commit_1=додав %d коміт %s issues.push_commits_n=додав %d коміти(-ів) %s -issues.force_push_codes=`примуÑово залито %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s` +issues.force_push_codes=`примуÑово надіÑлано %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s` issues.force_push_compare=ПорівнÑти issues.due_date_form=рррр-мм-дд issues.due_date_form_add=Додати дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ @@ -1387,8 +1461,13 @@ issues.due_date_not_set=Термін Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вÑтановлеРissues.due_date_added=додав(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s issues.due_date_remove=видалив(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s issues.due_date_overdue=ПроÑтрочено -issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами допуÑтимого діапазону. Будь лаÑка викориÑтовуйте формат 'yyyy-mm-dd'. +issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами діапазону. Будь лаÑка, викориÑтовуйте формат 'рррр-мм-дд'. 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=Видалити @@ -1396,39 +1475,44 @@ issues.dependency.remove_info=Видалити цю залежніÑть issues.dependency.added_dependency=`додав нову залежніÑть %s` issues.dependency.removed_dependency=`видалив залежніÑть %s` issues.dependency.pr_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾ наÑтупними задачами -issues.dependency.issue_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†Ñ–Ñ”Ñ— задачи заблоковано наÑтупними задачами -issues.dependency.issue_close_blocks=Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° блокує Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… задач +issues.dependency.issue_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†Ñ–Ñ”Ñ— задачі заблоковано наÑтупними задачами +issues.dependency.issue_close_blocks=Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° блокує Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð½Ð°Ñтупних задач issues.dependency.pr_close_blocks=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±Ð»Ð¾ÐºÑƒÑ” Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… задач -issues.dependency.issue_close_blocked=Вам потрібно закрити вÑÑ– задачі, що блокують цю задачу, перед Ñ—Ñ— закриттÑм. +issues.dependency.issue_close_blocked=Перш ніж закрити це завданнÑ, вам потрібно закрити вÑÑ– завданнÑ, що блокують його. issues.dependency.pr_close_blocked=Вам потрібно закрити вÑÑ– задачі, що блокують цей запит, перед його злиттÑм. issues.dependency.blocks_short=Блоки issues.dependency.blocked_by_short=Залежить від issues.dependency.remove_header=Видалити залежніÑть issues.dependency.issue_remove_text=Це призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñті з цієї задачі. Продовжити? -issues.dependency.pr_remove_text=Це призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾Ñті з цього пулл-реквеÑту. Продовжити? +issues.dependency.pr_remove_text=Це вилучить залежніÑть з цього запиту на злиттÑ. Продовжити? issues.dependency.setting=Увімкнути залежноÑті Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ та запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ issues.dependency.add_error_same_issue=Ви не можете зробити задачу залежною від Ñебе. -issues.dependency.add_error_dep_issue_not_exist=ЗалежніÑть Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– не Ñ–Ñнує. +issues.dependency.add_error_dep_issue_not_exist=Залежної задачі не Ñ–Ñнує. issues.dependency.add_error_dep_not_exist=ЗалежніÑть не Ñ–Ñнує. issues.dependency.add_error_dep_exists=ЗалежніÑть уже Ñ–Ñнує. issues.dependency.add_error_cannot_create_circular=Ви не можете Ñтворити залежніÑть з двома задачами, Ñкі блокують одна одну. issues.dependency.add_error_dep_not_same_repo=Обидві задачі повинні бути в одному репозиторії. -issues.review.self.approval=Ви не можете Ñхвалити влаÑний пулл-реквеÑÑ‚. +issues.review.self.approval=Ви не можете затвердити влаÑний запит на злиттÑ. issues.review.self.rejection=Ви не можете надіÑлати запит на зміну на влаÑний пулл-реквеÑÑ‚. -issues.review.approve=зміни затверджено %s -issues.review.dismissed=відхилено відгук %s %s +issues.review.approve=затвердив(ла) ці зміни %s +issues.review.comment=рецензовано %s +issues.review.dismissed=відхилив(ла) рецензію %s %s issues.review.dismissed_label=Відхилено issues.review.left_comment=додав коментар -issues.review.content.empty=Запрошуючи зміни, ви зобов'Ñзані залишити коментар з поÑÑненнÑми Ñвоїх побажань відноÑно Pull Request'а. +issues.review.content.empty=Вам потрібно залишити коментар із зазначеннÑм бажаної зміни (змін). issues.review.reject=зробив запит змін %s -issues.review.wait=попроÑив рецензію %s +issues.review.wait=запроÑили на рецензію %s 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 +1521,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=видалено @@ -1444,20 +1529,25 @@ 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=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ +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_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>` @@ -1468,113 +1558,140 @@ pulls.change_target_branch_at=`змінена цільова гілка з <b>%s pulls.tab_conversation=ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ pulls.tab_commits=Коміти pulls.tab_files=Змінені файли -pulls.reopen_to_merge=Будь лаÑка перевідкрийте цей запит щоб здіÑнити операцію злиттÑ. +pulls.reopen_to_merge=Будь лаÑка, заново відкрийте цей запит щоб виконати злиттÑ. pulls.cant_reopen_deleted_branch=Цей запит не можна повторно відкрити, оÑкільки гілку видалено. pulls.merged=Злито +pulls.closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾ pulls.manually_merged=Ручне Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -pulls.is_closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ»Ð¾ закрито. -pulls.title_wip_desc=`<a href="#">Почніть заголовок з <strong>%s</strong></a> щоб запобігти випадковому злиттю запитів.` -pulls.cannot_merge_work_in_progress=Цей пулл-реквеÑÑ‚ позначений Ñк прийнÑтий в опрацюваннÑ. -pulls.still_in_progress=Ð’Ñе ще в процеÑÑ–? +pulls.merged_info_text=Гілку %s тепер можна видалити. +pulls.is_closed=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¾. +pulls.title_wip_desc=`<a href="#">Почніть заголовок з <strong>%s</strong></a> щоб запобігти випадковому об'єднанню.` +pulls.cannot_merge_work_in_progress=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ Ñк незавершений. +pulls.still_in_progress=Ще не закінчено? pulls.add_prefix=Додати Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong> pulls.remove_prefix=Видалити Ð¿Ñ€ÐµÑ„Ñ–ÐºÑ <strong>%s</strong> -pulls.data_broken=ЗміÑÑ‚ цього запиту було порушено внаÑлідок Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— Форком. Цей запит Ñ‚ÑгнетьÑÑ Ñ‡ÐµÑ€ÐµÐ· відÑутніÑть інформації про вилученнÑ. -pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою. +pulls.data_broken=Збій цього запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‡ÐµÑ€ÐµÐ· відÑутніÑть інформації про форк. +pulls.files_conflicted=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð°Ñ” зміни, що конфліктують з цільовою гілкою. +pulls.is_checking=Перевірка конфліктів об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (merge) ... pulls.required_status_check_failed=ДеÑкі необхідні перевірки виконані з помилками. pulls.required_status_check_missing=Декілька з необхідних перевірок відÑутні. pulls.required_status_check_administrator=Як адмініÑтратор ви вÑе одно можете об'єднати цей запит на злиттÑ. -pulls.can_auto_merge_desc=Цей запит можна об'єднати автоматично. -pulls.cannot_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ може бути злитий автоматично через конфлікти. -pulls.cannot_auto_merge_helper=Злийте вручну Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚Ñ–Ð². -pulls.num_conflicting_files_1=%d конфліктуючий файл -pulls.num_conflicting_files_n=%d конфліктуючі файли +pulls.can_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¼Ð¾Ð¶Ð½Ð° об'єднати автоматично. +pulls.cannot_auto_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ може бути об'єднано автоматично через конфлікти. +pulls.cannot_auto_merge_helper=Об'єднайте вручну Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚Ñ–Ð². +pulls.num_conflicting_files_1=%d конфліктний файл +pulls.num_conflicting_files_n=%d конфліктних файлів pulls.approve_count_1=%d ÑÑ…Ð²Ð°Ð»ÐµÐ½Ð½Ñ pulls.approve_count_n=%d Ñхвалень pulls.reject_count_1=%d запит на зміну -pulls.reject_count_n=%d запити на зміну -pulls.waiting_count_1=очікуєтьÑÑ %d Ñ€ÐµÑ†ÐµÐ½Ð·Ñ–Ñ +pulls.reject_count_n=%d запитів на зміну +pulls.waiting_count_1=очікуєтьÑÑ %d рецензій pulls.waiting_count_n=очікуєтьÑÑ %d рецензії(й) -pulls.wrong_commit_id=id коміту повинен бути id коміту в цільовій гілці - -pulls.no_merge_desc=Цей запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ злити, оÑкільки вÑÑ– параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾. -pulls.no_merge_helper=Увімкніть параметри Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² налаштуваннÑÑ… Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð°Ð±Ð¾ злийте запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ€ÑƒÑ‡Ð½Ñƒ. -pulls.no_merge_wip=Цей пулл-реквеÑÑ‚ не можливо об'єднати, тому-що він вже виконуєтьÑÑ. -pulls.no_merge_not_ready=Цей запит не готовий до злиттÑ, перевірте ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ€ÐµÑ†ÐµÐ½Ð·Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ñ– ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸. -pulls.no_merge_access=Ви не авторизовані, щоб виконати цей запит на злиттÑ. -pulls.merge_pull_request=Створити коміт зі злиттÑм +pulls.wrong_commit_id=ідентифікатор коміту повинен бути ідентифікатором коміту в цільовій гілці + +pulls.no_merge_desc=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ об'єднати, оÑкільки вÑÑ– параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñховищ вимкнено. +pulls.no_merge_helper=Увімкніть параметри об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð² налаштуваннÑÑ… Ñховища або об'єднайте запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ€ÑƒÑ‡Ð½Ñƒ. +pulls.no_merge_wip=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ об'єднаний, оÑкільки він позначений Ñк незавершений. +pulls.no_merge_not_ready=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‰Ðµ не готовий до об'єднаннÑ, перевірте ÑтатуÑи Ñ€ÐµÑ†ÐµÐ½Ð·Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° перевірки. +pulls.no_merge_access=Ви не авторизовані об'єднувати цей запит на злиттÑ. +pulls.merge_pull_request=Створити коміт об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ pulls.rebase_merge_pull_request=Перебазувати, а потім виконати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¾Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñм -pulls.rebase_merge_commit_pull_request=Перебазувати, а потім Ñтворити коміт Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.rebase_merge_commit_pull_request=Перебазувати, а потім Ñтворити коміт об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ pulls.squash_merge_pull_request=Створити зварений (squash) коміт pulls.merge_manually=Об’єднано вручну -pulls.merge_commit_id=ID коміту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -pulls.require_signed_wont_sign=Гілка вимагає підпиÑаних комітів, але це Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ буде підпиÑано +pulls.merge_commit_id=Ідентифікатор коміту Ð¾Ð±â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ +pulls.require_signed_wont_sign=Гілка вимагає підпиÑаних комітів, але це об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑано -pulls.invalid_merge_option=Цей параметр Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ можна викориÑтовувати Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Pull Request'а. -pulls.merge_conflict=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ вдалоÑÑ: Був конфлікт при злиттÑ. Підказка: Ñпробуйте іншу Ñтратегію +pulls.invalid_merge_option=Цей параметр об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ можна викориÑтовувати Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ запиту на злиттÑ. +pulls.merge_conflict=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вдалоÑÑ: Під Ñ‡Ð°Ñ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ð¸Ð½Ð¸Ðº конфлікт. Підказка: Спробуйте іншу Ñтратегію pulls.merge_conflict_summary=Помилка -pulls.rebase_conflict=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ вдалоÑÑ: відбувÑÑ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚ під Ñ‡Ð°Ñ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ: %[1]s. Підказка: Ñпробуйте іншу Ñтратегію +pulls.rebase_conflict=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вдалоÑÑ: відбувÑÑ ÐºÐ¾Ð½Ñ„Ð»Ñ–ÐºÑ‚ під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ±Ð°Ð·ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ: %[1]s. Підказка: Ñпробуйте іншу Ñтратегію pulls.rebase_conflict_summary=Помилка -pulls.unrelated_histories=Помилка злиттÑ: head та base Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ мають Ñпільної Ñ–Ñторії. Підказка: Ñпробуйте іншу Ñтратегію -pulls.merge_out_of_date=Помилка злиттÑ: base було оновлено, поки відбувалоÑÑ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ. Підказка: Ñпробуйте знову. pulls.push_rejected_summary=Повне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ відмову -pulls.open_unmerged_pull_exists=`Ви не можете знову відкрити, оÑкільки вже Ñ–Ñнує запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ (%d) з того ж Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð· тією ж інформацією про Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– в очікуванні.` -pulls.status_checking=ДеÑкі перевірки знаходÑтьÑÑ Ð½Ð° розглÑді +pulls.open_unmerged_pull_exists=`Ви не можете повторно відкрити цей запит на злиттÑ, оÑкільки вже Ñ–Ñнує один (#%d) з ідентичними влаÑтивоÑÑ‚Ñми.` +pulls.status_checking=ДеÑкі перевірки ще не завершені pulls.status_checks_success=Ð’ÑÑ– перевірки були уÑпішними pulls.status_checks_warning=Декілька перевірок завершилиÑÑ Ð· попередженнÑми -pulls.status_checks_failure=Декілька перевірок не були уÑпішними +pulls.status_checks_failure=ДеÑкі перевірки не Ñпрацювали pulls.status_checks_error=Декілька перевірок завершилиÑÑ Ð· помилками pulls.status_checks_requested=Обов'Ñзково pulls.status_checks_details=Подробиці -pulls.update_branch=Оновити гілку шлÑхом Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.status_checks_hide_all=Приховати вÑÑ– перевірки +pulls.status_checks_show_all=Показати вÑÑ– перевірки +pulls.update_branch=Оновити гілку шлÑхом об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ pulls.update_branch_rebase=Оновити гілку перебазуваннÑм -pulls.update_branch_success=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ пройшло уÑпішно +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.due_date=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ (необов’Ñзково) milestones.clear=ОчиÑтити milestones.invalid_due_date_format=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð¼Ð°Ñ” бути в форматі 'рррр-мм-дд'. +milestones.create_success=Етап "%s" Ñтворено. milestones.edit=Редагувати етап -milestones.edit_subheader=Створюйте етапи Ð´Ð»Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— ваших задач. +milestones.edit_subheader=Етапи впорÑдковують задачі та відÑтежують прогреÑ. milestones.cancel=Відмінити milestones.modify=Оновити етап +milestones.edit_success=Етап '%s' оновлено. milestones.deletion=Видалити етап -milestones.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ призведе до його Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð· уÑÑ–Ñ… пов'Ñзаних задач. Продовжити? -milestones.deletion_success=Етап уÑпішно видалено. +milestones.deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐµÑ‚Ð°Ð¿Ñƒ видалÑÑ” його з уÑÑ–Ñ… пов'Ñзаних з ним задач. Продовжити? +milestones.deletion_success=Етап видалено. milestones.filter_sort.name=Ðазва -milestones.filter_sort.least_complete=Менш повне -milestones.filter_sort.most_complete=Більш повне -milestones.filter_sort.most_issues=Ðайбільш задач -milestones.filter_sort.least_issues=Ðайменш задач +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=Вікі wiki.welcome=ЛаÑкаво проÑимо до Вікі. -wiki.welcome_desc=Wiki дозволÑÑ” пиÑати та ділитиÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ з Ñпівавторами. -wiki.desc=Пишіть та обмінюйтеÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ із Ñпівавторами. +wiki.welcome_desc=Wiki дозволÑÑ” пиÑати та ділитиÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ зі Ñпівавторами. +wiki.desc=Пишіть та обмінюйтеÑÑ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ”ÑŽ зі Ñпівавторами. 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 +1701,16 @@ wiki.file_revision=Ревізії Ñторінки wiki.wiki_page_revisions=Ревізії вікі Ñторінок wiki.back_to_wiki=ПовернутиÑÑŒ на Ñторінку Вікі wiki.delete_page_button=Видалити Ñторінку -wiki.page_already_exists=Вікі-Ñторінка з таким Ñамим ім'Ñм вже Ñ–Ñнує. +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 дні @@ -1599,25 +1720,25 @@ 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_1=<strong>%d</strong> Ðктивний запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ activity.active_prs_count_n=<strong>%d</strong> Ðктивні запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -activity.merged_prs_count_1=Злитий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -activity.merged_prs_count_n=Злиті запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +activity.merged_prs_count_1=Об'єднаний запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +activity.merged_prs_count_n=Об'єднані запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ activity.opened_prs_count_1=Запропонований запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -activity.opened_prs_count_n=Запропонованих запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +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.prs_merged_by=%s злито %s activity.title.prs_opened_by=%s запропоновано %s -activity.merged_prs_label=Злито +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.closed_issues_count_1=Закрита задача activity.closed_issues_count_n=Закриті задачі -activity.title.issues_1=%d Задач +activity.title.issues_1=%d Задача activity.title.issues_n=%d Задач activity.title.issues_closed_from=%s закрито %s activity.title.issues_created_by=%s Ñтворена(Ñ–) %s @@ -1627,95 +1748,111 @@ activity.new_issues_count_n=Ðові Задачі activity.new_issue_label=Відкриті activity.title.unresolved_conv_1=%d Ðезавершене Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ activity.title.unresolved_conv_n=%d Ðезавершених обговорень -activity.unresolved_conv_desc=СпиÑок вÑÑ–Ñ… Ñтарих задач Ñ– Pull Request'ів з недавньої активніÑтю, але ще не закритих або прийнÑтих. +activity.unresolved_conv_desc=Ці нещодавно змінені задачі Ñ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‰Ðµ не вирішені. activity.unresolved_conv_label=Відкрити activity.title.releases_1=%d Реліз activity.title.releases_n=%d Релізів activity.title.releases_published_by=%s опубліковано %s activity.published_release_label=Опубліковано -activity.no_git_activity=У цей період не було здійÑнено жодних дій. -activity.git_stats_exclude_merges=Ðе враховуючи злиттÑ, +activity.no_git_activity=За цей період не було жодної активноÑті комітів. +activity.git_stats_exclude_merges=Ðе враховуючи об'єднаннÑ, 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_pushed_1=відправив(ла) +activity.git_stats_pushed_n=відправили activity.git_stats_commit_1=%d коміт -activity.git_stats_commit_n=%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_files_changed_1=змінено +activity.git_stats_file_n=%d файлів +activity.git_stats_files_changed_1=змінив(ла) activity.git_stats_files_changed_n=змінено activity.git_stats_additions=Ñ– були activity.git_stats_addition_1=%d добавка -activity.git_stats_addition_n=%d добавки +activity.git_stats_addition_n=%d додатків 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=У налаштуваннÑÑ… ви можете змінювати різні параметри цього Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ -settings.options=Репозиторій +settings.desc=У налаштуваннÑÑ… ви можете керувати параметрами Ñховища +settings.options=Сховище settings.public_access=Публічний доÑтуп settings.collaboration=Співавтори settings.collaboration.admin=ÐдмініÑтратор settings.collaboration.write=Ð—Ð°Ð¿Ð¸Ñ -settings.collaboration.read=Читати +settings.collaboration.read=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ settings.collaboration.owner=ВлаÑник settings.collaboration.undefined=Ðе визначено settings.hooks=Веб-хуки settings.githooks=Git хуки settings.basic_settings=Базові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.mirror_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð»Ð° -settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій +settings.mirror_settings.docs.doc_link_title=Як віддзеркалити Ñховища? +settings.mirror_settings.mirrored_repository=Віддзеркалене Ñховища settings.mirror_settings.direction=ÐапрÑмок settings.mirror_settings.direction.pull=Pull settings.mirror_settings.direction.push=Push settings.mirror_settings.last_update=ОÑтаннє Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ -settings.mirror_settings.push_mirror.none=Ðе налаштовано дзеркало push -settings.mirror_settings.push_mirror.remote_url=URL віддаленого Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–Ñ git -settings.mirror_settings.push_mirror.add=Додати Push дзеркало +settings.mirror_settings.push_mirror.remote_url=URL віддаленого Ñховища Git settings.sync_mirror=Синхронізувати зараз settings.site=Веб-Ñайт settings.update_settings=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -settings.branches.update_default_branch=Оновити гілку за замовчуваннÑм -settings.advanced_settings=Додаткові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -settings.wiki_desc=Увімкнути репозиторії Вікі -settings.use_internal_wiki=ВикориÑтовувати вбудовані Вікі -settings.use_external_wiki=ВикориÑтовувати зовнішні Вікі +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=ВикориÑтовувати вбудовану Вікі +settings.use_external_wiki=ВикориÑтовувати зовнішню Вікі settings.external_wiki_url=URL зовнішньої вікі -settings.external_wiki_url_error=Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ URL-адреÑа wiki не Ñ” допуÑтимою URL-адреÑою. -settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адреÑу, коли вони клацають по вкладці. +settings.external_wiki_url_error=Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ URL-адреÑа Вікі Ñ” недійÑною. +settings.external_wiki_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° URL-адреÑу зовнішньої вікі при натиÑканні вкладки. settings.issues_desc=Увімкнути відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ в репозиторію settings.use_internal_issue_tracker=ВикориÑтовувати вбудовану ÑиÑтему відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ settings.use_external_issue_tracker=ВикориÑтовувати зовнішню ÑиÑтему обліку задач settings.external_tracker_url=URL зовнішньої ÑиÑтеми відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ -settings.external_tracker_url_error=URL зовнішнього баг-трекера не Ñ” допуÑтимою URL-адреÑою. -settings.external_tracker_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° зовнішню URL-адреÑу, коли натиÑкають вкладку 'Задачі'. +settings.external_tracker_url_error=URL-адреÑа зовнішнього трекера задач Ñ” недійÑною. +settings.external_tracker_url_desc=Відвідувачі перенаправлÑютьÑÑ Ð½Ð° URL-адреÑу зовнішнього трекера задач при натиÑканні на вкладку. settings.tracker_url_format=Формат URL зовнішнього трекера задач -settings.tracker_url_format_error=Ðеправильний формат URL-адреÑи зовнішнього баг-трекера. -settings.tracker_issue_style=Формат номеру Ð´Ð»Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ñ— ÑиÑтеми обліку задач +settings.tracker_url_format_error=Формат URL-адреÑи зовнішнього трекера проблем недійÑний. settings.tracker_issue_style.numeric=Цифровий -settings.tracker_issue_style.alphanumeric=Буквено-цифровий +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=Враховувати тільки учаÑників розробки в підрахунку чаÑу +settings.allow_only_contributors_to_track_time=Лише відÑтежувати Ñ‡Ð°Ñ Ñпівавторів settings.pulls_desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð² репозиторій settings.pulls.ignore_whitespace=Ігнорувати пробіл у конфліктах -settings.pulls.enable_autodetect_manual_merge=Увімкнути Ð°Ð²Ñ‚Ð¾Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¾Ð³Ð¾ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ (Примітка: у деÑких оÑобливий випадках можуть виникнуть помилки) +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_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=Ðовий влаÑник вже має репозиторій з такою назвою. Будь лаÑка, виберіть інше ім'Ñ. -settings.convert=Перетворити на звичайний репозиторій +settings.new_owner_has_same_repo=Ðовий влаÑник вже має Ñховище з такою назвою. Будь лаÑка, виберіть іншу назву. +settings.convert=Перетворити на звичайне Ñховище settings.convert_desc=Ви можете Ñконвертувати це дзеркало у звичайний репозиторій. Це не може бути ÑкаÑовано. settings.convert_notices_1=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒ дзеркало у звичайний репозиторій Ñ– не може бути ÑкаÑована. settings.convert_confirm=Перетворити репозиторій @@ -1822,6 +1959,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 +2005,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 +2035,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 +2047,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 +2085,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 +2172,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 +2196,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 +2219,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 +2294,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 +2346,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 +2360,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 +2376,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 +2384,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 +2418,7 @@ monitor=Моніторинг first_page=Перша last_page=ОÑÑ‚Ð°Ð½Ð½Ñ total=Разом: %d +settings=ÐдмініÑтративні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ dashboard.statistic=ПідÑумок dashboard.system_status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑиÑтеми @@ -2271,6 +2485,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 +2497,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 +2525,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 +2543,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 +2556,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 +2575,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 +2621,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 +2633,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 +2676,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 +2692,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 +2707,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 +2733,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 +2765,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 +2779,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 +2791,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 +2822,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 +2838,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 +2860,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 +2877,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 +2925,6 @@ seconds=%d Ñекунди minutes=%d хвилини hours=%d години days=%d дні -weeks=%d тижднів months=%d міÑÑці years=%d роки raw_seconds=Ñекунди @@ -2667,6 +2932,7 @@ raw_minutes=хвилини [dropzone] default_message=ПеретÑгніть файли або натиÑніть тут, щоб завантажити. +invalid_input_type=Ðеможливо завантажити файли цього типу. file_too_big=Розмір файлу ({{filesize}} MB), що більше ніж макÑимальний розмір: ({{maxFilesize}} MB). remove_file=Видалити файл @@ -2697,14 +2963,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 +3056,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..989802b9db 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=æœç´¢åˆå¹¶è¯·æ±‚... @@ -262,20 +262,20 @@ host=æ•°æ®åº“主机 user=用户å password=æ•°æ®åº“用户密ç db_name=æ•°æ®åº“åç§° -db_schema=Schema +db_schema=æž¶æž„ db_schema_helper=留空则数æ®åº“ä¸é»˜è®¤å€¼ä¸º("public")。 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=æŸ¥çœ‹æ‰€æœ‰æ ‡ç¾ @@ -1066,25 +1066,25 @@ download_tar=下载 TAR.GZ download_bundle=下载 BUNDLE generate_repo=生æˆä»“库 generate_from=生æˆè‡ª -repo_desc=仓库æè¿° +repo_desc=æè¿° repo_desc_helper=è¾“å…¥ç®€è¦æè¿° (å¯é€‰) repo_no_desc=æ— è¯¦ç»†ä¿¡æ¯ -repo_lang=编程è¯è¨€ +repo_lang=è¯è¨€ repo_gitignore_helper=选择 .gitignore 模æ¿ã€‚ repo_gitignore_helper_desc=从常è§è¯è¨€çš„æ¨¡æ¿åˆ—表ä¸é€‰æ‹©å¿½ç•¥è·Ÿè¸ªçš„æ–‡ä»¶ã€‚é»˜è®¤æƒ…å†µä¸‹ï¼Œç”±å¼€å‘æˆ–构建工具生æˆçš„特殊文件都包å«åœ¨ .gitignore ä¸ã€‚ 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=å¤´åƒ @@ -1161,15 +1161,15 @@ template.issue_labels=工啿 ‡ç¾ template.one_item=必须至少选择一个模æ¿é¡¹ template.invalid=必须选择一个模æ¿ä»“库 -archive.title=该仓库已被归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶å’Œå…‹éš†å®ƒï¼Œä½†ä¸èƒ½æŽ¨é€ã€åˆ›å»ºå·¥å•或åˆå¹¶è¯·æ±‚。 -archive.title_date=该仓库已于 %s 归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶æˆ–克隆它,但ä¸èƒ½æŽ¨é€ã€åˆ›å»ºå·¥å•或åˆå¹¶è¯·æ±‚。 +archive.title=该仓库已被归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶å’Œå…‹éš†å®ƒï¼Œä½†ä¸èƒ½æŽ¨é€ã€æ‰“开工啿ˆ–åˆå¹¶è¯·æ±‚。 +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.name_pattern_not_allowed=仓库åç§°ä¸ä¸å…许使用 %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=æŽˆæƒ migrate_options=è¿ç§»é€‰é¡¹ @@ -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=æŸ¥æ‰¾æ ‡ç¾ 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=æ ‡ç¾ released_this=å‘布 tagged_this=å·²æ ‡è®° file.title=%s ä½äºŽ %s @@ -1336,12 +1336,12 @@ editor.cannot_edit_non_text_files=网页ä¸èƒ½ç¼–辑二进制文件。 editor.edit_this_file=编辑文件 editor.this_file_locked=文件已é”定 editor.must_be_on_a_branch=您必须在æŸä¸ªåˆ†æ”¯ä¸Šæ‰èƒ½å¯¹æ¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作。 -editor.fork_before_edit=您必须在派生这个仓库æ‰èƒ½å¯¹æ¤æ–‡ä»¶è¿›è¡Œä¿®æ”¹æ“作 +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,54 +1352,54 @@ 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=在æäº¤æ—¥å¿—æ¶ˆæ¯æœ«å°¾æ·»åŠ ç¾ç½²äººä¿¡æ¯ã€‚ editor.commit_directly_to_this_branch=直接æäº¤è‡³ <strong class="branch-name">%s</strong> 分支。 editor.create_new_branch=ä¸ºæ¤æäº¤åˆ›å»ºä¸€ä¸ª <strong>新的分支</strong> å¹¶å‘èµ·åˆå¹¶è¯·æ±‚。 editor.create_new_branch_np=ä¸ºæ¤æäº¤åˆ›å»º <strong>新分支</strong>。 -editor.propose_file_change=æè®®æ–‡ä»¶æ›´æ”¹ +editor.propose_file_change=建议文件更改 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=å¼€å¯ä¸ @@ -1613,8 +1613,8 @@ issues.close=å…³é—å·¥å• issues.comment_pull_merged_at=å·²åˆå¹¶æäº¤ %[1]s 到 %[2]s %[3]s issues.comment_manually_pull_merged_at=手动åˆå¹¶æäº¤ %[1]s 到 %[2]s %[3]s issues.close_comment_issue=è¯„è®ºå¹¶å…³é— -issues.reopen_issue=釿–°å¼€å¯ -issues.reopen_comment_issue=è¯„è®ºå¹¶é‡æ–°å¼€å¯ +issues.reopen_issue=釿–°æ‰“å¼€ +issues.reopen_comment_issue=è¯„è®ºå¹¶é‡æ–°æ‰“å¼€ issues.create_comment=评论 issues.comment.blocked_user=æ— æ³•åˆ›å»ºæˆ–ç¼–è¾‘è¯„è®ºï¼Œå› ä¸ºæ‚¨å·²è¢«ä»“åº“æ‰€æœ‰è€…æˆ–å·¥å•创建者å±è”½ã€‚ issues.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闿¤å·¥å•` @@ -1650,40 +1650,41 @@ issues.edit=编辑 issues.cancel=å–æ¶ˆ issues.save=ä¿å˜ issues.label_title=æ ‡ç¾åç§° -issues.label_description=æ ‡ç¾æè¿° -issues.label_color=æ ‡ç¾é¢œè‰² -issues.label_exclusive=äº’æ–¥æ ‡ç¾ +issues.label_description=æè¿° +issues.label_color=颜色 +issues.label_color_invalid=æ— æ•ˆçš„é¢œè‰² +issues.label_exclusive=互斥 issues.label_archive=å½’æ¡£æ ‡ç¾ -issues.label_archived_filter=æ˜¾ç¤ºå˜æ¡£æ ‡ç¾ -issues.label_archive_tooltip=åœ¨æ ‡ç¾æœç´¢æ—¶ï¼Œé»˜è®¤æƒ…å†µä¸‹å˜æ¡£æ ‡ç¾å°†è¢«æŽ’除在外。 +issues.label_archived_filter=æ˜¾ç¤ºå·²å½’æ¡£æ ‡ç¾ +issues.label_archive_tooltip=åœ¨æ ‡ç¾æœç´¢æ—¶ï¼Œé»˜è®¤æƒ…å†µä¸‹å·²å½’æ¡£æ ‡ç¾å°†è¢«æŽ’除在外。 issues.label_exclusive_desc=å‘½åæ ‡ç¾ä¸º <code>scope/item</code> 以使其与其他以 <code>scope/</code> å¼€å¤´çš„æ ‡ç¾äº’斥。 issues.label_exclusive_warning=åœ¨ç¼–è¾‘å·¥å•æˆ–åˆå¹¶è¯·æ±‚çš„æ ‡ç¾æ—¶ï¼Œä»»ä½•冲çªçš„èŒƒå›´æ ‡ç¾éƒ½å°†è¢«åˆ 除。 issues.label_exclusive_order=排åºé¡ºåº issues.label_exclusive_order_tooltip=在åŒä¸€ä¸ªèŒƒå›´å†…çš„äº’æ–¥æ ‡ç¾å°†æŒ‰ç…§è¿™ä¸ªæ•°å—è¿›è¡ŒæŽ’åº issues.label_count=%d ä¸ªæ ‡ç¾ -issues.label_open_issues=%d 个开å¯çš„å·¥å• +issues.label_open_issues=%d 个已打开工å•/åˆå¹¶è¯·æ±‚ issues.label_edit=编辑 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=æ— æ³•ä¿å˜å¯¹è¯„论的更改。其内容似乎已被其他用户更改。 请刷新页é¢å¹¶é‡æ–°ç¼–辑以é¿å…覆盖他们的更改 @@ -1992,7 +1993,7 @@ milestones.new=新的里程碑 milestones.closed=于 %så…³é— milestones.update_ago=已更新 %s milestones.no_due_date=æš‚æ— æˆªæ¢æ—¥æœŸ -milestones.open=å¼€å¯ +milestones.open=打开 milestones.close=å…³é— milestones.new_subheader=里程碑å¯ä»¥å¸®åŠ©æ‚¨ç»„ç»‡å·¥å•并跟踪其进度。 milestones.completeness=<strong>%d%%</strong> å·²å®Œæˆ @@ -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,95 +2056,95 @@ 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=贡献者 -activity.navbar.recent_commits=最近的æäº¤ +activity.navbar.recent_commits=最近æäº¤ activity.period.filter_label=周期: activity.period.daily=1 天 activity.period.halfweekly=3 天 -activity.period.weekly=1周 -activity.period.monthly=1 个月 -activity.period.quarterly=3个月 -activity.period.semiyearly=6 个月 -activity.period.yearly=1å¹´ +activity.period.weekly=1 周 +activity.period.monthly=1 月 +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.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.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.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=åˆ›å»ºå·¥å• -activity.new_issues_count_n=åˆ›å»ºå·¥å• +activity.new_issues_count_1=å·²æ‰“å¼€çš„å·¥å• +activity.new_issues_count_n=å·²æ‰“å¼€çš„å·¥å• activity.new_issue_label=打开的 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_pushed_1=å·²ç»æŽ¨é€ -activity.git_stats_pushed_n=å·²ç»æŽ¨é€ -activity.git_stats_commit_1=%d æäº¤ -activity.git_stats_commit_n=%d æäº¤ +activity.git_stats_exclude_merges=排除åˆå¹¶åŽï¼Œ +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_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_files_changed_1=å·²ç»æ”¹å˜ -activity.git_stats_files_changed_n=å·²ç»æ”¹å˜ -activity.git_stats_additions=而且 -activity.git_stats_addition_1=新增 %d 行 -activity.git_stats_addition_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=并且已有 +activity.git_stats_addition_1=%d 行新增 +activity.git_stats_addition_n=%d 行新增 activity.git_stats_and_deletions=å’Œ -activity.git_stats_deletion_1=åˆ é™¤ %d 行 -activity.git_stats_deletion_n=åˆ é™¤ %d 行 +activity.git_stats_deletion_1=%d è¡Œåˆ é™¤ +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,22 +2196,22 @@ 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=外部工å•管ç†ç³»ç»Ÿçš„ç¼–å·æ ¼å¼ 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=æ£åˆ™è¡¨è¾¾å¼ 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=å¯ç”¨æ—¶é—´è·Ÿè¸ª @@ -2220,15 +2221,15 @@ settings.pulls.ignore_whitespace=å¿½ç•¥ç©ºç™½å†²çª 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.pulls.default_allow_edits_from_maintainers=默认å…许维护者编辑 +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.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=ç¦ç”¨å¼ºåˆ¶æŽ¨é€ @@ -2485,13 +2486,13 @@ settings.protect_merge_whitelist_committers_desc=ä»…å…许白åå•用户或团é settings.protect_merge_whitelist_users=åˆå¹¶ç™½åå•用户: settings.protect_merge_whitelist_teams=åˆå¹¶ç™½åå•团队: settings.protect_check_status_contexts=å¯ç”¨çŠ¶æ€æ£€æŸ¥ -settings.protect_status_check_patterns=çŠ¶æ€æ£€æŸ¥æ¨¡å¼ï¼š -settings.protect_status_check_patterns_desc=输入模å¼ï¼ŒæŒ‡å®šå“ªäº›çŠ¶æ€æ£€æŸ¥å¿…须通过,æ‰èƒ½å°†åˆ†æ”¯åˆå¹¶åˆ°ç¬¦åˆæ¤è§„则的分支ä¸åŽ»ã€‚æ¯ä¸€è¡ŒæŒ‡å®šä¸€ä¸ªæ¨¡å¼ï¼Œæ¨¡å¼ä¸èƒ½ä¸ºç©ºã€‚ +settings.protect_status_check_patterns=çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ï¼š +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_no_valid_status_check_patterns=æ²¡æœ‰æœ‰æ•ˆçš„çŠ¶æ€æ£€æŸ¥è§„则。 +settings.protect_invalid_status_check_pattern=æ— æ•ˆçš„çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ï¼šã€Œ%sã€ã€‚ +settings.protect_no_valid_status_check_patterns=æ²¡æœ‰æœ‰æ•ˆçš„çŠ¶æ€æ£€æŸ¥è¡¨è¾¾å¼ã€‚ settings.protect_required_approvals=所需的批准: settings.protect_required_approvals_desc=åªå…许åˆå¹¶æœ‰è¶³å¤Ÿå®¡æ ¸çš„åˆå¹¶è¯·æ±‚ã€‚è¦æ±‚çš„å®¡æ ¸å¿…é¡»æ¥è‡ªç™½å啿ˆ–者有æƒé™çš„用户或团队。 settings.protect_approvals_whitelist_enabled=仅列入白åå•的用户或团队æ‰å¯æ‰¹å‡† @@ -2504,18 +2505,18 @@ settings.ignore_stale_approvals=忽略过期批准 settings.ignore_stale_approvals_desc=对旧æäº¤ï¼ˆè¿‡æœŸå®¡æ ¸ï¼‰çš„æ‰¹å‡†å°†ä¸è®¡å…¥ PR çš„æ‰¹å‡†æ•°ã€‚å¦‚æžœè¿‡æœŸå®¡æŸ¥å·²è¢«é©³å›žï¼Œåˆ™ä¸Žæ¤æ— 关。 settings.require_signed_commits=需è¦ç¾åæäº¤ settings.require_signed_commits_desc=æ‹’ç»æŽ¨é€æœªç¾åæˆ–æ— æ³•éªŒè¯çš„æäº¤åˆ°åˆ†æ”¯ -settings.protect_branch_name_pattern=å—ä¿æŠ¤çš„åˆ†æ”¯åç§°æ¨¡å¼ -settings.protect_branch_name_pattern_desc=åˆ†æ”¯ä¿æŠ¤çš„å称匹é…è§„åˆ™ã€‚è¯æ³•请å‚阅 <a href="%s">文档</a> 。如:main, release/** -settings.protect_patterns=规则 -settings.protect_protected_file_patterns=å—ä¿æŠ¤çš„æ–‡ä»¶æ¨¡å¼(ä½¿ç”¨åˆ†å· ';' 分隔): -settings.protect_protected_file_patterns_desc=å³ä½¿ç”¨æˆ·æœ‰æƒæ·»åŠ ã€ç¼–è¾‘æˆ–åˆ é™¤æ¤åˆ†æ”¯ä¸çš„æ–‡ä»¶ï¼Œä¹Ÿä¸å…许直接更改å—ä¿æŠ¤çš„æ–‡ä»¶ã€‚ å¯ä»¥ä½¿ç”¨åˆ†å· (';') 分隔多个模å¼ã€‚ è§<a href='%[1]s'>%[2]s</a>文档了解模å¼è¯æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code> -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.protect_branch_name_pattern=å—ä¿æŠ¤çš„åˆ†æ”¯åç§°è¡¨è¾¾å¼ +settings.protect_branch_name_pattern_desc=åˆ†æ”¯ä¿æŠ¤çš„å称匹é…表达å¼ã€‚è¯æ³•请å‚阅 <a href="%s">文档</a> 。如:main, release/** +settings.protect_patterns=è¡¨è¾¾å¼ +settings.protect_protected_file_patterns=å—ä¿æŠ¤çš„æ–‡ä»¶è¡¨è¾¾å¼ï¼ˆä½¿ç”¨åˆ†å·ã€Œ;ã€åˆ†éš”): +settings.protect_protected_file_patterns_desc=å³ä½¿ç”¨æˆ·æœ‰æƒæ·»åŠ ã€ç¼–è¾‘æˆ–åˆ é™¤æ¤åˆ†æ”¯ä¸çš„æ–‡ä»¶ï¼Œä¹Ÿä¸å…许直接更改å—ä¿æŠ¤çš„æ–‡ä»¶ã€‚ å¯ä»¥ä½¿ç”¨åˆ†å·ã€Œ;ã€åˆ†éš”多个表达å¼ã€‚ è§<a href='%[1]s'>%[2]s</a>文档了解表达å¼è¯æ³•。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>。 +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=æ ‡ç¾ä¿æŠ¤ +settings.tags.protection.pattern=æ ‡ç¾è¡¨è¾¾å¼ 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=ä¿æŠ¤æ ‡ç¾ +settings.tags.protection.none=没有å—ä¿æŠ¤çš„æ ‡ç¾ã€‚ +settings.tags.protection.pattern.description=您å¯ä»¥ä½¿ç”¨å•个å称或 glob 表达å¼åŒ¹é…或æ£åˆ™è¡¨è¾¾å¼æ¥åŒ¹é…å¤šä¸ªæ ‡ç¾ã€‚了解详情请访问 <a target="_blank" rel="noopener" href="%s">ä¿æŠ¤æ ‡ç¾æŒ‡å—</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=å·²å½’æ¡£ä»“åº“çš„æ ‡ç¾è®¾ç½®ä¸å¯ç”¨ã€‚ +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=æ ‡ç¾ 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=åˆ é™¤æ ‡ç¾ 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_desc=åˆ é™¤å‘布åªä¼šä»Ž Gitea ä¸ç§»é™¤å‘布。这ä¸ä¼šå½±å“ Git çš„æ ‡ç¾ä»¥åŠæ‚¨ä»“库的内容和历å²ã€‚是å¦ç»§ç»ï¼Ÿ +release.deletion_success=该å‘å¸ƒå·²åˆ é™¤ã€‚ +release.deletion_tag_desc=将从仓库ä¸åˆ é™¤æ¤æ ‡ç¾ã€‚仓库内容和历å²è®°å½•ä¿æŒä¸å˜ã€‚ç»§ç»å—? +release.deletion_tag_success=è¯¥æ ‡ç¾å·²åˆ 除。 +release.tag_name_already_exist=ä½¿ç”¨æ¤æ ‡ç¾åç§°çš„å‘布已ç»å˜åœ¨ã€‚ release.tag_name_invalid=æ ‡ç¾åç§°æ— æ•ˆã€‚ -release.tag_name_protected=Gitæ ‡ç¾åç§°å·²å—ä¿æŠ¤ã€‚ -release.tag_already_exist=æ¤ Gitæ ‡ç¾ åç§°å·²å˜åœ¨ +release.tag_name_protected=æ ‡ç¾åå·²å—ä¿æŠ¤ã€‚ +release.tag_already_exist=æ¤æ ‡ç¾åå·²å˜åœ¨ã€‚ 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=站点åç§° @@ -3304,16 +3305,16 @@ config.db_type=类型 config.db_host=主机 config.db_name=æ•°æ®åº“åç§° config.db_user=用户å -config.db_schema=æž¶æž„æ¨¡å¼ +config.db_schema=æž¶æž„ 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> @@ -3475,10 +3476,10 @@ rename_repo=é‡å‘½å仓库 <code>%[1]s</code> 为 <a href="%[2]s">%[3]s</a> commit_repo=推é€åˆ°äº†ä»“库 <a href="%[1]s">%[4]s</a> çš„ <a href="%[2]s">%[3]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>` +reopen_issue=`釿–°æ‰“å¼€äº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>` create_pull_request=`创建了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>` close_pull_request=`å…³é—了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>` -reopen_pull_request=`釿–°å¼€å¯äº†åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>` +reopen_pull_request=`釿–°æ‰“开了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>` comment_issue=`è¯„è®ºäº†å·¥å• <a href="%[1]s">%[3]s#%[2]s</a>` comment_pull=`评论了åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]s</a>` merge_pull_request=`åˆå¹¶äº†åˆå¹¶è¯·æ±‚ <a href="%[1]s">%[3]s#%[2]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 @@ -3703,7 +3704,7 @@ owner.settings.cleanuprules.preview=清ç†è§„则预览 owner.settings.cleanuprules.preview.overview=%d ä¸ªè½¯ä»¶åŒ…è®¡åˆ’è¢«åˆ é™¤ã€‚ owner.settings.cleanuprules.preview.none=清ç†è§„则与任何软件包都ä¸åŒ¹é…。 owner.settings.cleanuprules.enabled=å¯ç”¨ -owner.settings.cleanuprules.pattern_full_match=应用规则到完整软件包åç§° +owner.settings.cleanuprules.pattern_full_match=应用表达å¼åˆ°å®Œæ•´è½¯ä»¶åŒ…åç§° owner.settings.cleanuprules.keep.title=与这些规则相匹é…的版本å³ä½¿ä¸Žä¸‹é¢çš„åˆ é™¤è§„åˆ™ç›¸åŒ¹é…,也将予以ä¿ç•™ã€‚ owner.settings.cleanuprules.keep.count=ä¿ç•™æœ€æ–°çš„ owner.settings.cleanuprules.keep.count.1=æ¯ä¸ªè½¯ä»¶åŒ…1个版本 @@ -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..59ce5b33e0 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,11 +113,11 @@ "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": { - "node": ">= 18.0.0" + "node": ">= 20.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -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..4faf34900a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "engines": { - "node": ">= 18.0.0" + "node": ">= 20.0.0" }, "dependencies": { "@citation-js/core": "0.7.18", @@ -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/packages/container/container.go b/routers/api/packages/container/container.go index a235923aac..53f1f130f8 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" packages_module "code.gitea.io/gitea/modules/packages" container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/setting" @@ -50,7 +51,7 @@ type containerHeaders struct { Range string Location string ContentType string - ContentLength int64 + ContentLength optional.Option[int64] } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#legacy-docker-support-http-headers @@ -64,8 +65,8 @@ func setResponseHeaders(resp http.ResponseWriter, h *containerHeaders) { if h.ContentType != "" { resp.Header().Set("Content-Type", h.ContentType) } - if h.ContentLength != 0 { - resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength, 10)) + if h.ContentLength.Has() { + resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength.Value(), 10)) } if h.UploadUUID != "" { resp.Header().Set("Docker-Upload-Uuid", h.UploadUUID) @@ -403,12 +404,7 @@ func EndUploadBlob(ctx *context.Context) { } return } - doClose := true - defer func() { - if doClose { - uploader.Close() - } - }() + defer uploader.Close() if ctx.Req.Body != nil { if err := uploader.Append(ctx, ctx.Req.Body); err != nil { @@ -441,11 +437,10 @@ func EndUploadBlob(ctx *context.Context) { return } - if err := uploader.Close(); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - doClose = false + // There was a strange bug: the "Close" fails with error "close .../tmp/package-upload/....: file already closed" + // AFAIK there should be no other "Close" call to the uploader between NewBlobUploader and this line. + // At least it's safe to call Close twice, so ignore the error. + _ = uploader.Close() if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -511,7 +506,7 @@ func HeadBlob(ctx *context.Context) { setResponseHeaders(ctx.Resp, &containerHeaders{ ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest), - ContentLength: blob.Blob.Size, + ContentLength: optional.Some(blob.Blob.Size), Status: http.StatusOK, }) } @@ -650,7 +645,7 @@ func HeadManifest(ctx *context.Context) { setResponseHeaders(ctx.Resp, &containerHeaders{ ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest), ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: manifest.Blob.Size, + ContentLength: optional.Some(manifest.Blob.Size), Status: http.StatusOK, }) } @@ -714,14 +709,14 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) headers := &containerHeaders{ ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest), ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: pfd.Blob.Size, + ContentLength: optional.Some(pfd.Blob.Size), Status: http.StatusOK, } if u != nil { headers.Status = http.StatusTemporaryRedirect headers.Location = u.String() - headers.ContentLength = 0 // do not set Content-Length for redirect responses + headers.ContentLength = optional.None[int64]() // do not set Content-Length for redirect responses setResponseHeaders(ctx.Resp, headers) return } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 85dd547ac1..c29f4e4622 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) { Location: optional.FromPtr(form.Location), Description: optional.FromPtr(form.Description), IsActive: optional.FromPtr(form.Active), - IsAdmin: optional.FromPtr(form.Admin), + IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin), Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]), AllowGitHook: optional.FromPtr(form.AllowGitHook), AllowImportLocal: optional.FromPtr(form.AllowImportLocal), 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/api/v1/org/org.go b/routers/api/v1/org/org.go index 27c646896a..adb117c4e8 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -26,12 +26,10 @@ import ( func listUserOrgs(ctx *context.APIContext, u *user_model.User) { listOptions := utils.GetListOptions(ctx) - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID) - opts := organization.FindOrgOptions{ - ListOptions: listOptions, - UserID: u.ID, - IncludePrivate: showPrivate, + ListOptions: listOptions, + UserID: u.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u), } orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts) if err != nil { diff --git a/routers/common/markup.go b/routers/common/markup.go index 4c77ff33ed..00b2dd07c6 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -76,7 +76,11 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur }) rctx = rctx.WithMarkupType(markdown.MarkupName) case "comment": - rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) + rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{ + DeprecatedOwnerName: repoOwnerName, + DeprecatedRepoName: repoName, + FootnoteContextID: "preview", + }) rctx = rctx.WithMarkupType(markdown.MarkupName) case "wiki": rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 83e207c717..27577cd35b 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/web/explore" @@ -292,9 +293,9 @@ func ViewUser(ctx *context.Context) { ctx.Data["EmailsTotal"] = len(emails) orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ - ListOptions: db.ListOptionsAll, - UserID: u.ID, - IncludePrivate: true, + ListOptions: db.ListOptionsAll, + UserID: u.ID, + IncludeVisibility: structs.VisibleTypePrivate, }) if err != nil { ctx.ServerError("FindOrgs", err) @@ -431,7 +432,7 @@ func EditUserPost(ctx *context.Context) { Website: optional.Some(form.Website), Location: optional.Some(form.Location), IsActive: optional.Some(form.Active), - IsAdmin: optional.Some(form.Admin), + IsAdmin: user_service.UpdateOptionFieldFromValue(form.Admin), AllowGitHook: optional.Some(form.AllowGitHook), AllowImportLocal: optional.Some(form.AllowImportLocal), MaxRepoCreation: optional.Some(form.MaxRepoCreation), diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 69b9d285b7..87edbc357b 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -613,7 +613,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. if user_model.CountUsers(ctx, nil) == 1 { opts := &user_service.UpdateOptions{ IsActive: optional.Some(true), - IsAdmin: optional.Some(true), + IsAdmin: user_service.UpdateOptionFieldFromValue(true), SetLastLogin: true, } if err := user_service.UpdateUser(ctx, u, opts); err != nil { diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 96c1dcf358..a13b987aab 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -193,8 +193,8 @@ func SignInOAuthCallback(ctx *context.Context) { source := authSource.Cfg.(*oauth2.Source) isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) - u.IsAdmin = isAdmin.ValueOrDefault(false) - u.IsRestricted = isRestricted.ValueOrDefault(false) + u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue + u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted) if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { // error already handled @@ -258,11 +258,11 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[ return claimValueToStringSet(groupClaims) } -func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) { +func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin optional.Option[user_service.UpdateOptionField[bool]], isRestricted optional.Option[bool]) { groups := getClaimedGroups(source, gothUser) if source.AdminGroup != "" { - isAdmin = optional.Some(groups.Contains(source.AdminGroup)) + isAdmin = user_service.UpdateOptionFieldFromSync(groups.Contains(source.AdminGroup)) } if source.RestrictedGroup != "" { isRestricted = optional.Some(groups.Contains(source.RestrictedGroup)) diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go index d42afafe9e..59b97c1717 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -20,7 +20,7 @@ func SSHInfo(rw http.ResponseWriter, req *http.Request) { return } rw.Header().Set("content-type", "text/json;charset=UTF-8") - _, err := rw.Write([]byte(`{"type":"gitea","version":1}`)) + _, err := rw.Write([]byte(`{"type":"agit","version":1}`)) if err != nil { log.Error("fail to write result: err: %v", err) rw.WriteHeader(http.StatusInternalServerError) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 1ea881c2a7..9dd6988825 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -457,6 +457,9 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m } if !ctx.Repo.CanRead(unit_model.TypeActions) { for _, commit := range commits { + if commit.Status == nil { + continue + } commit.Status.HideActionsURL(ctx) git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index e70e8fdd7b..a4747964c6 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -364,7 +364,9 @@ func UpdateIssueContent(ctx *context.Context) { } } - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: "0", + }) content, err := markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 9b51999fbd..c2a7f6b682 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -8,6 +8,7 @@ import ( "fmt" "html/template" "net/http" + "strconv" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/renderhelper" @@ -278,7 +279,9 @@ func UpdateCommentContent(ctx *context.Context) { var renderedContent template.HTML if comment.Content != "" { - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) renderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 13b9d83da4..2897652d51 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "sort" + "strconv" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" @@ -624,7 +625,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue comment.Issue = issue if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview { - rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) @@ -700,7 +703,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue } } } else if comment.Type.HasContentSupport() { - rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) @@ -757,6 +762,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue } if !ctx.Repo.CanRead(unit.TypeActions) { for _, commit := range comment.Commits { + if commit.Status == nil { + continue + } commit.Status.HideActionsURL(ctx) git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) } @@ -981,7 +989,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) { var err error - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: "0", // Set footnote context ID to 0 for the issue content + }) issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 2baf434e75..8e586adde9 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -113,7 +114,9 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) cacheUsers[r.PublisherID] = r.Publisher } - rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(r.ID, 10), + }) r.RenderedNote, err = markdown.RenderString(rctx, r.Note) if err != nil { return nil, err 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/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 3df6051975..f43433fb0d 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -140,13 +140,6 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked") } - // Assume file is not editable first. - if fInfo.isLFSFile { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") - } else if !isRepresentableAsText { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") - } - // read all needed attributes which will be used later // there should be no performance different between reading 2 or 4 here attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{ @@ -243,21 +236,6 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["FileContent"] = fileContent ctx.Data["LineEscapeStatus"] = statuses } - if !fInfo.isLFSFile { - if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) { - if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { - ctx.Data["CanEditFile"] = false - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") - } else { - ctx.Data["CanEditFile"] = true - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") - } - } else if !ctx.Repo.RefFullName.IsBranch() { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") - } - } case fInfo.st.IsPDF(): ctx.Data["IsPDFFile"] = true @@ -307,17 +285,49 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { } } - if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) { - if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { - ctx.Data["CanDeleteFile"] = false - ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") - } else { - ctx.Data["CanDeleteFile"] = true - ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file") - } - } else if !ctx.Repo.RefFullName.IsBranch() { + prepareToRenderButtons(ctx, fInfo.isLFSFile, isRepresentableAsText, lfsLock) +} + +func prepareToRenderButtons(ctx *context.Context, isLFSFile, isRepresentableAsText bool, lfsLock *git_model.LFSLock) { + // archived or mirror repository, the buttons should not be shown + if ctx.Repo.Repository.IsArchived || !ctx.Repo.Repository.CanEnableEditor() { + return + } + + // The buttons should not be shown if it's not a branch + if !ctx.Repo.RefFullName.IsBranch() { + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { + return + } + + if isLFSFile { + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") + } else if !isRepresentableAsText { + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") + } + + if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { + if !isLFSFile { // lfs file cannot be edited after fork + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") + } ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") + return + } + + // it's a lfs file and the user is not the owner of the lock + if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { + ctx.Data["CanEditFile"] = false + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") + ctx.Data["CanDeleteFile"] = false + ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") + return + } + + if !isLFSFile { // lfs file cannot be edited + ctx.Data["CanEditFile"] = true + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") } + ctx.Data["CanDeleteFile"] = true + ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file") } diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index d3b196b6a3..2bd0abc4c0 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -47,13 +47,12 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) { ctx.Data["RenderedDescription"] = content } - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, + UserID: ctx.ContextUser.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser), ListOptions: db.ListOptions{ Page: 1, - // query one more results (without a separate counting) to see whether we need to add the "show more orgs" link + // query one more result (without a separate counting) to see whether we need to add the "show more orgs" link PageSize: setting.UI.User.OrgPagingNum + 1, }, }) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 58acfb9518..f6d50cf5fe 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -63,21 +63,9 @@ func userProfile(ctx *context.Context) { ctx.Data["Title"] = ctx.ContextUser.DisplayName() ctx.Data["PageIsUserProfile"] = true - // prepare heatmap data - if setting.Service.EnableUserHeatmap { - data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) - if err != nil { - ctx.ServerError("GetUserHeatmapDataByUser", err) - return - } - ctx.Data["HeatmapData"] = data - ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) - } - profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer) - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) - prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob) + prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob) // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them @@ -90,7 +78,7 @@ func userProfile(ctx *context.Context) { ctx.HTML(http.StatusOK, tplProfile) } -func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { +func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page // if there is not a profile readme, the overview tab should be treated as the repositories tab tab := ctx.FormString("tab") @@ -173,8 +161,20 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["Cards"] = following total = int(numFollowing) case "activity": + // prepare heatmap data + if setting.Service.EnableUserHeatmap { + data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserHeatmapDataByUser", err) + return + } + ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) + } + date := ctx.FormString("date") pagingNum = setting.UI.FeedPagingNum + showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctx.ContextUser, Actor: ctx.Doer, @@ -265,8 +265,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb } case "organizations": orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, + UserID: ctx.ContextUser.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser), ListOptions: db.ListOptions{ Page: page, PageSize: pagingNum, diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 368837ce1b..98995cd69c 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/typesniffer" @@ -206,8 +207,8 @@ func Organization(ctx *context.Context) { PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), }, - UserID: ctx.Doer.ID, - IncludePrivate: ctx.IsSigned, + UserID: ctx.Doer.ID, + IncludeVisibility: structs.VisibleTypePrivate, } if opts.Page <= 0 { diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 5d17df8047..ef241e5091 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -14,9 +14,9 @@ import ( git_model "code.gitea.io/gitea/models/git" user_model "code.gitea.io/gitea/models/user" actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/commitstatus" git "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - api "code.gitea.io/gitea/modules/structs" webhook_module "code.gitea.io/gitea/modules/webhook" commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" @@ -147,18 +147,18 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status) } -func toCommitStatus(status actions_model.Status) api.CommitStatusState { +func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState { switch status { case actions_model.StatusSuccess: - return api.CommitStatusSuccess + return commitstatus.CommitStatusSuccess case actions_model.StatusFailure, actions_model.StatusCancelled: - return api.CommitStatusFailure + return commitstatus.CommitStatusFailure case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning: - return api.CommitStatusPending + return commitstatus.CommitStatusPending case actions_model.StatusSkipped: - return api.CommitStatusSkipped + return commitstatus.CommitStatusSkipped default: - return api.CommitStatusError + return commitstatus.CommitStatusError } } 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/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index a2e8c2b86a..6005a4744a 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -58,7 +58,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u opts := &user_service.UpdateOptions{} if source.AdminFilter != "" && user.IsAdmin != sr.IsAdmin { // Change existing admin flag only if AdminFilter option is set - opts.IsAdmin = optional.Some(sr.IsAdmin) + opts.IsAdmin = user_service.UpdateOptionFieldFromSync(sr.IsAdmin) } if !sr.IsAdmin && source.RestrictedFilter != "" && user.IsRestricted != sr.IsRestricted { // Change existing restricted flag only if RestrictedFilter option is set diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 416b67dd46..7b401c5c96 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -162,7 +162,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { IsActive: optional.Some(true), } if source.AdminFilter != "" { - opts.IsAdmin = optional.Some(su.IsAdmin) + opts.IsAdmin = user_service.UpdateOptionFieldFromSync(su.IsAdmin) } // Change existing restricted flag only if RestrictedFilter option is set if !su.IsAdmin && source.RestrictedFilter != "" { diff --git a/services/context/repo.go b/services/context/repo.go index 61841aa90b..32d54c88ff 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 } @@ -123,7 +123,8 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) - canCommit := r.CanEnableEditor(ctx, doer) && userCanPush + canEnableEditor := r.CanEnableEditor(ctx, doer) + canCommit := canEnableEditor && userCanPush if requireSigned { canCommit = canCommit && sign } @@ -139,7 +140,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use return CanCommitToBranchResults{ CanCommitToBranch: canCommit, - EditorEnabled: r.CanEnableEditor(ctx, doer), + EditorEnabled: canEnableEditor, UserCanPush: userCanPush, RequireSigned: requireSigned, WillSign: sign, 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/convert/status.go b/services/convert/status.go index 4fffbfbe5e..b4864a0307 100644 --- a/services/convert/status.go +++ b/services/convert/status.go @@ -5,6 +5,7 @@ package convert import ( "context" + "net/url" git_model "code.gitea.io/gitea/models/git" user_model "code.gitea.io/gitea/models/user" @@ -32,39 +33,29 @@ func ToCommitStatus(ctx context.Context, status *git_model.CommitStatus) *api.Co return apiStatus } +func ToCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) []*api.CommitStatus { + apiStatuses := make([]*api.CommitStatus, len(statuses)) + for i, status := range statuses { + apiStatuses[i] = ToCommitStatus(ctx, status) + } + return apiStatuses +} + // ToCombinedStatus converts List of CommitStatus to a CombinedStatus func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, repo *api.Repository) *api.CombinedStatus { if len(statuses) == 0 { return nil } - retStatus := &api.CombinedStatus{ - SHA: statuses[0].SHA, + combinedStatus := git_model.CalcCommitStatus(statuses) + + return &api.CombinedStatus{ + State: combinedStatus.State, + Statuses: ToCommitStatuses(ctx, statuses), + SHA: combinedStatus.SHA, TotalCount: len(statuses), Repository: repo, - URL: "", // never set or used? - State: api.CommitStatusSuccess, - } - - retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses)) - for _, status := range statuses { - retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status)) - if status.State.HasHigherPriorityThan(retStatus.State) { - retStatus.State = status.State - } - } - // According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference - // > Additionally, a combined state is returned. The state is one of: - // > failure if any of the contexts report as error or failure - // > pending if there are no statuses or a context is pending - // > success if the latest status for all contexts is success - switch retStatus.State { - case api.CommitStatusSkipped: - retStatus.State = api.CommitStatusSuccess // all skipped means success - case api.CommitStatusPending, api.CommitStatusSuccess: - // use the current state for pending or success - default: - retStatus.State = api.CommitStatusFailure // otherwise, it is a failure + CommitURL: repo.URL + "/commits/" + url.PathEscape(combinedStatus.SHA), + URL: repo.URL + "/commits/" + url.PathEscape(combinedStatus.SHA) + "/status", } - return retStatus } diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go index 52a73c9572..4173b0fe87 100644 --- a/services/oauth2_provider/access_token.go +++ b/services/oauth2_provider/access_token.go @@ -16,7 +16,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "github.com/golang-jwt/jwt/v5" ) @@ -231,12 +233,11 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server }, nil } -// returns a list of "org" and "org:team" strings, -// that the given user is a part of. +// GetOAuthGroupsForUser returns a list of "org" and "org:team" strings, that the given user is a part of. func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) { orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ - UserID: user.ID, - IncludePrivate: !onlyPublicGroups, + UserID: user.ID, + IncludeVisibility: util.Iif(onlyPublicGroups, api.VisibleTypePublic, api.VisibleTypePrivate), }) if err != nil { return nil, fmt.Errorf("GetUserOrgList: %w", err) diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index 58d26c5a00..d3a0f718a7 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -10,67 +10,56 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/structs" "github.com/gobwas/glob" "github.com/pkg/errors" ) // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts -func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState { - // matchedCount is the number of `CommitStatus.Context` that match any context of `requiredContexts` - matchedCount := 0 - returnedStatus := structs.CommitStatusSuccess - - if len(requiredContexts) > 0 { - requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts)) - for _, ctx := range requiredContexts { - if gp, err := glob.Compile(ctx); err != nil { - log.Error("glob.Compile %s failed. Error: %v", ctx, err) - } else { - requiredContextsGlob[ctx] = gp - } - } +func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) commitstatus.CommitStatusState { + if len(commitStatuses) == 0 { + return commitstatus.CommitStatusPending + } - for _, gp := range requiredContextsGlob { - var targetStatus structs.CommitStatusState - for _, commitStatus := range commitStatuses { - if gp.Match(commitStatus.Context) { - targetStatus = commitStatus.State - matchedCount++ - break - } - } + if len(requiredContexts) == 0 { + return git_model.CalcCommitStatus(commitStatuses).State + } - // If required rule not match any action, then it is pending - if targetStatus == "" { - if structs.CommitStatusPending.HasHigherPriorityThan(returnedStatus) { - returnedStatus = structs.CommitStatusPending - } - break - } + requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts)) + for _, ctx := range requiredContexts { + if gp, err := glob.Compile(ctx); err != nil { + log.Error("glob.Compile %s failed. Error: %v", ctx, err) + } else { + requiredContextsGlob[ctx] = gp + } + } - if targetStatus.HasHigherPriorityThan(returnedStatus) { - returnedStatus = targetStatus + requiredCommitStatuses := make([]*git_model.CommitStatus, 0, len(commitStatuses)) + for _, gp := range requiredContextsGlob { + for _, commitStatus := range commitStatuses { + if gp.Match(commitStatus.Context) { + requiredCommitStatuses = append(requiredCommitStatuses, commitStatus) + break } } } + if len(requiredCommitStatuses) == 0 { + return commitstatus.CommitStatusPending + } - if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess { - if len(commitStatuses) == 0 { - // "no statuses" should mean "pending" - return structs.CommitStatusPending - } - status := git_model.CalcCommitStatus(commitStatuses) - if status.State == structs.CommitStatusSkipped { - return structs.CommitStatusSuccess // if all statuses are skipped, return success - } - return status.State + returnedStatus := git_model.CalcCommitStatus(requiredCommitStatuses).State + if len(requiredCommitStatuses) == len(requiredContexts) { + return returnedStatus } - return returnedStatus + if returnedStatus == commitstatus.CommitStatusFailure { + return commitstatus.CommitStatusFailure + } + // even if part of success, return pending + return commitstatus.CommitStatusPending } // IsPullCommitStatusPass returns if all required status checks PASS @@ -91,7 +80,7 @@ func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) ( } // GetPullRequestCommitStatusState returns pull request merged commit status state -func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) { +func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (commitstatus.CommitStatusState, error) { // Ensure HeadRepo is loaded if err := pr.LoadHeadRepo(ctx); err != nil { return "", errors.Wrap(err, "LoadHeadRepo") diff --git a/services/pull/commit_status_test.go b/services/pull/commit_status_test.go index 9cb20d6c5d..b985a9de8e 100644 --- a/services/pull/commit_status_test.go +++ b/services/pull/commit_status_test.go @@ -8,7 +8,7 @@ import ( "testing" git_model "code.gitea.io/gitea/models/git" - "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/commitstatus" "github.com/stretchr/testify/assert" ) @@ -17,64 +17,64 @@ func TestMergeRequiredContextsCommitStatus(t *testing.T) { cases := []struct { commitStatuses []*git_model.CommitStatus requiredContexts []string - expected structs.CommitStatusState + expected commitstatus.CommitStatusState }{ { commitStatuses: []*git_model.CommitStatus{}, requiredContexts: []string{}, - expected: structs.CommitStatusPending, + expected: commitstatus.CommitStatusPending, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build xxx", State: structs.CommitStatusSkipped}, + {Context: "Build xxx", State: commitstatus.CommitStatusSkipped}, }, requiredContexts: []string{"Build*"}, - expected: structs.CommitStatusSuccess, + expected: commitstatus.CommitStatusSuccess, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build 1", State: structs.CommitStatusSkipped}, - {Context: "Build 2", State: structs.CommitStatusSuccess}, - {Context: "Build 3", State: structs.CommitStatusSuccess}, + {Context: "Build 1", State: commitstatus.CommitStatusSkipped}, + {Context: "Build 2", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 3", State: commitstatus.CommitStatusSuccess}, }, requiredContexts: []string{"Build*"}, - expected: structs.CommitStatusSuccess, + expected: commitstatus.CommitStatusSuccess, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build 1", State: structs.CommitStatusSuccess}, - {Context: "Build 2", State: structs.CommitStatusSuccess}, - {Context: "Build 2t", State: structs.CommitStatusPending}, + {Context: "Build 1", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2t", State: commitstatus.CommitStatusPending}, }, requiredContexts: []string{"Build*", "Build 2t*"}, - expected: structs.CommitStatusPending, + expected: commitstatus.CommitStatusPending, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build 1", State: structs.CommitStatusSuccess}, - {Context: "Build 2", State: structs.CommitStatusSuccess}, - {Context: "Build 2t", State: structs.CommitStatusFailure}, + {Context: "Build 1", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2t", State: commitstatus.CommitStatusFailure}, }, requiredContexts: []string{"Build*", "Build 2t*"}, - expected: structs.CommitStatusFailure, + expected: commitstatus.CommitStatusFailure, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build 1", State: structs.CommitStatusSuccess}, - {Context: "Build 2", State: structs.CommitStatusSuccess}, - {Context: "Build 2t", State: structs.CommitStatusSuccess}, + {Context: "Build 1", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2t", State: commitstatus.CommitStatusSuccess}, }, requiredContexts: []string{"Build*", "Build 2t*", "Build 3*"}, - expected: structs.CommitStatusPending, + expected: commitstatus.CommitStatusPending, }, { commitStatuses: []*git_model.CommitStatus{ - {Context: "Build 1", State: structs.CommitStatusSuccess}, - {Context: "Build 2", State: structs.CommitStatusSuccess}, - {Context: "Build 2t", State: structs.CommitStatusSuccess}, + {Context: "Build 1", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2", State: commitstatus.CommitStatusSuccess}, + {Context: "Build 2t", State: commitstatus.CommitStatusSuccess}, }, requiredContexts: []string{"Build*", "Build *", "Build 2t*", "Build 1*"}, - expected: structs.CommitStatusSuccess, + expected: commitstatus.CommitStatusSuccess, }, } for i, c := range cases { 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/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index f369a303e6..44cf61df43 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -14,12 +14,12 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/notify" ) @@ -47,7 +47,7 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal return nil } -func updateCommitStatusCache(repoID int64, branchName string, state api.CommitStatusState, targetURL string) error { +func updateCommitStatusCache(repoID int64, branchName string, state commitstatus.CommitStatusState, targetURL string) error { c := cache.GetCache() bs, err := json.Marshal(commitStatusCacheValue{ State: state.String(), @@ -127,7 +127,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep for i, repo := range repos { if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil { results[i] = &git_model.CommitStatus{ - State: api.CommitStatusState(cv.State), + State: commitstatus.CommitStatusState(cv.State), TargetURL: cv.TargetURL, } } else { 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/user/update.go b/services/user/update.go index 4a39f4f783..c4fd2d60fe 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -15,6 +15,26 @@ import ( "code.gitea.io/gitea/modules/structs" ) +type UpdateOptionField[T any] struct { + FieldValue T + FromSync bool +} + +func UpdateOptionFieldFromValue[T any](value T) optional.Option[UpdateOptionField[T]] { + return optional.Some(UpdateOptionField[T]{FieldValue: value}) +} + +func UpdateOptionFieldFromSync[T any](value T) optional.Option[UpdateOptionField[T]] { + return optional.Some(UpdateOptionField[T]{FieldValue: value, FromSync: true}) +} + +func UpdateOptionFieldFromPtr[T any](value *T) optional.Option[UpdateOptionField[T]] { + if value == nil { + return optional.None[UpdateOptionField[T]]() + } + return UpdateOptionFieldFromValue(*value) +} + type UpdateOptions struct { KeepEmailPrivate optional.Option[bool] FullName optional.Option[string] @@ -32,7 +52,7 @@ type UpdateOptions struct { DiffViewStyle optional.Option[string] AllowCreateOrganization optional.Option[bool] IsActive optional.Option[bool] - IsAdmin optional.Option[bool] + IsAdmin optional.Option[UpdateOptionField[bool]] EmailNotificationsPreference optional.Option[string] SetLastLogin bool RepoAdminChangeTeamAccess optional.Option[bool] @@ -111,13 +131,18 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er cols = append(cols, "is_restricted") } if opts.IsAdmin.Has() { - if !opts.IsAdmin.Value() && user_model.IsLastAdminUser(ctx, u) { - return user_model.ErrDeleteLastAdminUser{UID: u.ID} + if opts.IsAdmin.Value().FieldValue /* true */ { + u.IsAdmin = opts.IsAdmin.Value().FieldValue // set IsAdmin=true + cols = append(cols, "is_admin") + } else if !user_model.IsLastAdminUser(ctx, u) /* not the last admin */ { + u.IsAdmin = opts.IsAdmin.Value().FieldValue // it's safe to change it from false to true (not the last admin) + cols = append(cols, "is_admin") + } else /* IsAdmin=false but this is the last admin user */ { //nolint + if !opts.IsAdmin.Value().FromSync { + return user_model.ErrDeleteLastAdminUser{UID: u.ID} + } + // else: syncing from external-source, this user is the last admin, so skip the "IsAdmin=false" change } - - u.IsAdmin = opts.IsAdmin.Value() - - cols = append(cols, "is_admin") } if opts.Visibility.Has() { diff --git a/services/user/update_test.go b/services/user/update_test.go index fc24a6c212..27513e8040 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -22,7 +22,11 @@ func TestUpdateUser(t *testing.T) { admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) assert.Error(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{ - IsAdmin: optional.Some(false), + IsAdmin: UpdateOptionFieldFromValue(false), + })) + + assert.NoError(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{ + IsAdmin: UpdateOptionFieldFromSync(false), })) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) @@ -38,7 +42,7 @@ func TestUpdateUser(t *testing.T) { MaxRepoCreation: optional.Some(10), IsRestricted: optional.Some(true), IsActive: optional.Some(false), - IsAdmin: optional.Some(true), + IsAdmin: UpdateOptionFieldFromValue(true), Visibility: optional.Some(structs.VisibleTypePrivate), KeepActivityPrivate: optional.Some(true), Language: optional.Some("lang"), @@ -60,7 +64,7 @@ func TestUpdateUser(t *testing.T) { assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation) assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) assert.Equal(t, opts.IsActive.Value(), user.IsActive) - assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin) + assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) assert.Equal(t, opts.Visibility.Value(), user.Visibility) assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) assert.Equal(t, opts.Language.Value(), user.Language) @@ -80,7 +84,7 @@ func TestUpdateUser(t *testing.T) { assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation) assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) assert.Equal(t, opts.IsActive.Value(), user.IsActive) - assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin) + assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) assert.Equal(t, opts.Visibility.Value(), user.Visibility) assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) assert.Equal(t, opts.Language.Value(), user.Language) diff --git a/services/user/user.go b/services/user/user.go index 1aeebff142..c7252430de 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/agit" asymkey_service "code.gitea.io/gitea/services/asymkey" @@ -177,8 +178,8 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { PageSize: repo_model.RepositoryListDefaultPageSize, Page: 1, }, - UserID: u.ID, - IncludePrivate: true, + UserID: u.ID, + IncludeVisibility: structs.VisibleTypePrivate, }) if err != nil { return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err) 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 4830976d00..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": [ @@ -21030,7 +21086,17 @@ "x-go-name": "SHA" }, "state": { - "$ref": "#/definitions/CommitStatusState" + "type": "string", + "enum": [ + "pending", + "success", + "error", + "failure", + "warning", + "skipped" + ], + "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped", + "x-go-name": "State" }, "statuses": { "type": "array", @@ -21258,7 +21324,17 @@ "x-go-name": "ID" }, "status": { - "$ref": "#/definitions/CommitStatusState" + "type": "string", + "enum": [ + "pending", + "success", + "error", + "failure", + "warning", + "skipped" + ], + "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped", + "x-go-name": "State" }, "target_url": { "type": "string", @@ -21276,11 +21352,6 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, - "CommitStatusState": { - "description": "CommitStatusState holds the state of a CommitStatus\nIt can be \"pending\", \"success\", \"error\" and \"failure\"", - "type": "string", - "x-go-package": "code.gitea.io/gitea/modules/structs" - }, "CommitUser": { "type": "object", "title": "CommitUser contains information of a user in the context of a commit.", @@ -22329,7 +22400,17 @@ "x-go-name": "Description" }, "state": { - "$ref": "#/definitions/CommitStatusState" + "type": "string", + "enum": [ + "pending", + "success", + "error", + "failure", + "warning", + "skipped" + ], + "x-go-enum-desc": "pending CommitStatusPending CommitStatusPending is for when the CommitStatus is Pending\nsuccess CommitStatusSuccess CommitStatusSuccess is for when the CommitStatus is Success\nerror CommitStatusError CommitStatusError is for when the CommitStatus is Error\nfailure CommitStatusFailure CommitStatusFailure is for when the CommitStatus is Failure\nwarning CommitStatusWarning CommitStatusWarning is for when the CommitStatus is Warning\nskipped CommitStatusSkipped CommitStatusSkipped is for when CommitStatus is Skipped", + "x-go-name": "State" }, "target_url": { "type": "string", diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index a598cf64a5..6461fe85f4 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" @@ -638,7 +639,7 @@ jobs: if len(latestCommitStatuses) == 0 { return false } - if latestCommitStatuses[0].State == api.CommitStatusPending { + if latestCommitStatuses[0].State == commitstatus.CommitStatusPending { insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context) return true } @@ -679,14 +680,14 @@ func checkCommitStatusAndInsertFakeStatus(t *testing.T, repo *repo_model.Reposit latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, latestCommitStatuses, 1) - assert.Equal(t, api.CommitStatusPending, latestCommitStatuses[0].State) + assert.Equal(t, commitstatus.CommitStatusPending, latestCommitStatuses[0].State) insertFakeStatus(t, repo, sha, latestCommitStatuses[0].TargetURL, latestCommitStatuses[0].Context) } func insertFakeStatus(t *testing.T, repo *repo_model.Repository, sha, targetURL, context string) { err := commitstatus_service.CreateCommitStatus(db.DefaultContext, repo, user_model.NewActionsUser(), sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: targetURL, Context: context, }) diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index b2db77685d..9cdd84d5ee 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" "net/http" "strconv" @@ -623,6 +624,22 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, blobContent, resp.Body.Bytes()) }) + t.Run("GetBlob/Empty", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + emptyDigestBuf := sha256.Sum256(nil) + emptyDigest := "sha256:" + hex.EncodeToString(emptyDigestBuf[:]) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, emptyDigest), strings.NewReader("")).AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "0", resp.Header().Get("Content-Length")) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/blobs/%s", url, emptyDigest)).AddTokenAuth(userToken) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "0", resp.Header().Get("Content-Length")) + }) + t.Run("GetTagList", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index c00e88b88b..24f0c03bed 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" @@ -437,8 +438,8 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { Name: gitLDAPUser.UserName, }) usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: user.ID, - IncludePrivate: true, + UserID: user.ID, + IncludeVisibility: structs.VisibleTypePrivate, }) assert.NoError(t, err) allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) 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/git_general_test.go b/tests/integration/git_general_test.go index ed60bdb58a..3b0f9589d2 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -26,6 +26,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" @@ -713,7 +714,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { commitID := path.Base(commitURL) - addCommitStatus := func(status api.CommitStatusState) func(*testing.T) { + addCommitStatus := func(status commitstatus.CommitStatusState) func(*testing.T) { return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{ State: status, TargetURL: "http://test.ci/", @@ -723,7 +724,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { } // Call API to add Pending status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusPending)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusPending)) // Cancel not existing auto merge ctx.ExpectedCode = http.StatusNotFound @@ -752,7 +753,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { assert.False(t, pr.HasMerged) // Call API to add Failure status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusFailure)) // Check pr status pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) @@ -760,7 +761,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { assert.False(t, pr.HasMerged) // Call API to add Success status for commit - t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) + t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusSuccess)) // wait to let gitea merge stuff time.Sleep(time.Second) 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/tests/integration/org_count_test.go b/tests/integration/org_count_test.go index fb71e690c2..c48008e627 100644 --- a/tests/integration/org_count_test.go +++ b/tests/integration/org_count_test.go @@ -120,8 +120,8 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca }) orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ - UserID: user.ID, - IncludePrivate: true, + UserID: user.ID, + IncludeVisibility: api.VisibleTypePrivate, }) assert.NoError(t, err) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index bf7ca3e42e..73b4c22070 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/queue" @@ -768,7 +769,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) @@ -848,7 +849,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) { }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) @@ -977,7 +978,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. }() err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "https://gitea.com", Context: "gitea/actions", }) diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 4d43847f1b..bfcb97b082 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -55,20 +56,20 @@ func TestPullCreate_CommitStatus(t *testing.T) { commitID := path.Base(commitURL) - statusList := []api.CommitStatusState{ - api.CommitStatusPending, - api.CommitStatusError, - api.CommitStatusFailure, - api.CommitStatusSuccess, - api.CommitStatusWarning, + statusList := []commitstatus.CommitStatusState{ + commitstatus.CommitStatusPending, + commitstatus.CommitStatusError, + commitstatus.CommitStatusFailure, + commitstatus.CommitStatusSuccess, + commitstatus.CommitStatusWarning, } - statesIcons := map[api.CommitStatusState]string{ - api.CommitStatusPending: "octicon-dot-fill", - api.CommitStatusSuccess: "octicon-check", - api.CommitStatusError: "gitea-exclamation", - api.CommitStatusFailure: "octicon-x", - api.CommitStatusWarning: "gitea-exclamation", + statesIcons := map[commitstatus.CommitStatusState]string{ + commitstatus.CommitStatusPending: "octicon-dot-fill", + commitstatus.CommitStatusSuccess: "octicon-check", + commitstatus.CommitStatusError: "gitea-exclamation", + commitstatus.CommitStatusFailure: "octicon-x", + commitstatus.CommitStatusWarning: "gitea-exclamation", } testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeWriteRepository) @@ -99,7 +100,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID}) - assert.Equal(t, api.CommitStatusWarning, css.State) + assert.Equal(t, commitstatus.CommitStatusSuccess, css.State) }) } diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 504d2adacc..bef957597a 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -12,6 +12,7 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -76,7 +77,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { // Call API to add status for commit ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusState(state), + State: commitstatus.CommitStatusState(state), TargetURL: "http://test.ci/", Description: "", Context: "testci", @@ -120,7 +121,7 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec assert.NotNil(t, status) if assert.Len(t, statuses, 1) { - assert.Equal(t, api.CommitStatusState(state), statuses[0].State) + assert.Equal(t, commitstatus.CommitStatusState(state), statuses[0].State) assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", statuses[0].URL) assert.Equal(t, "http://test.ci/", statuses[0].TargetURL) assert.Empty(t, statuses[0].Description) @@ -174,7 +175,7 @@ func TestRepoCommitsStatusParallel(t *testing.T) { parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) runBody := doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusPending, + State: commitstatus.CommitStatusPending, TargetURL: "http://test.ci/", Description: "", Context: "testci", @@ -205,14 +206,14 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { // Call API to add status for commit ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "testci", })) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "other_context", diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 57162de363..45a85552bd 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" @@ -770,7 +771,7 @@ func Test_WebhookStatus(t *testing.T) { // update a status for a commit via API doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{ - State: api.CommitStatusSuccess, + State: commitstatus.CommitStatusSuccess, TargetURL: "http://test.ci/", Description: "", Context: "testci", 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/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js index 9d0e07b33b..85530c7991 100644 --- a/web_src/fomantic/build/components/dropdown.js +++ b/web_src/fomantic/build/components/dropdown.js @@ -525,6 +525,7 @@ $.fn.dropdown = function(parameters) { return true; } if(settings.onShow.call(element) !== false) { + settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items module.animate.show(function() { if( module.can.click() ) { module.bind.intent(); 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> diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts index 2da481e521..a4a69540a8 100644 --- a/web_src/js/features/common-fetch-action.ts +++ b/web_src/js/features/common-fetch-action.ts @@ -56,8 +56,12 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R actionElem.classList.remove('is-loading', 'loading-icon-2px'); } -async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { +async function onFormFetchActionSubmit(formEl: HTMLFormElement, e: SubmitEvent) { e.preventDefault(); + await submitFormFetchAction(formEl, submitEventSubmitter(e)); +} + +export async function submitFormFetchAction(formEl: HTMLFormElement, formSubmitter?: HTMLElement) { if (formEl.classList.contains('is-loading')) return; formEl.classList.add('is-loading'); @@ -68,7 +72,6 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { const formMethod = formEl.getAttribute('method') || 'get'; const formActionUrl = formEl.getAttribute('action'); const formData = new FormData(formEl); - const formSubmitter = submitEventSubmitter(e); const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')]; if (submitterName) { formData.append(submitterName, submitterValue || ''); @@ -96,7 +99,7 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) { await fetchActionDoRequest(formEl, reqUrl, reqOpt); } -async function linkAction(el: HTMLElement, e: Event) { +async function onLinkActionClick(el: HTMLElement, e: Event) { // A "link-action" can post AJAX request to its "data-url" // Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading. // If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action. @@ -126,6 +129,6 @@ async function linkAction(el: HTMLElement, e: Event) { } export function initGlobalFetchAction() { - addDelegatedEventListener(document, 'submit', '.form-fetch-action', formFetchAction); - addDelegatedEventListener(document, 'click', '.link-action', linkAction); + addDelegatedEventListener(document, 'submit', '.form-fetch-action', onFormFetchActionSubmit); + addDelegatedEventListener(document, 'click', '.link-action', onLinkActionClick); } diff --git a/web_src/js/features/comp/LabelEdit.ts b/web_src/js/features/comp/LabelEdit.ts index a5bb750cdb..141c5eecfe 100644 --- a/web_src/js/features/comp/LabelEdit.ts +++ b/web_src/js/features/comp/LabelEdit.ts @@ -1,5 +1,6 @@ import {toggleElem} from '../../utils/dom.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; +import {submitFormFetchAction} from '../common-fetch-action.ts'; function nameHasScope(name: string): boolean { return /.*[^/]\/[^/].*/.test(name); @@ -70,7 +71,7 @@ export function initCompLabelEdit(pageSelector: string) { form.reportValidity(); return false; } - form.dispatchEvent(new Event('submit', {bubbles: true})); + submitFormFetchAction(form); }, }).modal('show'); }; diff --git a/web_src/js/markup/anchors.ts b/web_src/js/markup/anchors.ts index 483d72bd5b..a0d49911fe 100644 --- a/web_src/js/markup/anchors.ts +++ b/web_src/js/markup/anchors.ts @@ -5,21 +5,24 @@ const removePrefix = (str: string): string => str.replace(/^user-content-/, ''); const hasPrefix = (str: string): boolean => str.startsWith('user-content-'); // scroll to anchor while respecting the `user-content` prefix that exists on the target -function scrollToAnchor(encodedId: string): void { - if (!encodedId) return; - const id = decodeURIComponent(encodedId); - const prefixedId = addPrefix(id); - let el = document.querySelector(`#${prefixedId}`); +function scrollToAnchor(encodedId?: string): void { + // FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here + let elemId: string; + try { + elemId = decodeURIComponent(encodedId ?? ''); + } catch {} // ignore the errors, since the "encodedId" is from user's input + if (!elemId) return; + + const prefixedId = addPrefix(elemId); + // eslint-disable-next-line unicorn/prefer-query-selector + let el = document.getElementById(prefixedId); // check for matching user-generated `a[name]` - if (!el) { - el = document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`); - } + el = el ?? document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`); // compat for links with old 'user-content-' prefixed hashes - if (!el && hasPrefix(id)) { - return document.querySelector(`#${id}`)?.scrollIntoView(); - } + // eslint-disable-next-line unicorn/prefer-query-selector + el = (!el && hasPrefix(elemId)) ? document.getElementById(elemId) : el; el?.scrollIntoView(); } |