diff --git a/CHANGELOG.md b/CHANGELOG.md index cfefea3a22c8..31fca8804fac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,406 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.11.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.11.0-rc1) - 2020-01-07 +* BREAKING + * Remove unused endpoints (#9538) + * Prefix all user-generated IDs in markup (#9477) + * Enforce Gitea environment for pushes (#8982) + * Hide some user information via API if user have no enough permission (#8655) + * Move startpage/homepage translation to crowdin (#8596) +* FEATURES + * Webhooks should only show sender if it makes sense (#9601) + * Provide Default messages for merges (#9393) + * Add description to labels on create issue (#9392) + * Graceful Queues: Issue Indexing and Tasks (#9363) + * Default NO_REPLY_ADDRESS to DOMAIN (#9325) + * Allow FCGI over unix sockets (#9298) + * Graceful: Xorm, RepoIndexer, Cron and Others (#9282) + * Add API for Reactions (#9220) + * Graceful: Cancel Process on monitor pages & HammerTime (#9213) + * Graceful: Allow graceful restart for unix sockets (#9113) + * Graceful: Allow graceful restart for fcgi (#9112) + * Sign protected branches (#8993) + * Add Graceful shutdown for Windows and hooks for shutdown of goroutines (#8964) + * Add Gitea icon to Emojis (#8950) + * Expand/Collapse Files and Blob Excerpt while Reviewing/Comparing code (#8924) + * Allow Custom Reactions (#8886) + * Close/reopen issues by keywords in titles and comments (#8866) + * Allow incompletely specified Time Formats (#8816) + * Prevent upload (overwrite) of lfs locked file (#8769) + * Template Repositories (#8768) + * Add /milestones endpoint (#8733) + * Make repository management section handle lfs locks (#8726) + * Respect LFS File Lock on UI (#8719) + * Add team option to grant rights for all organization repositories (#8688) + * Enabling and disabling the commit button to prevent empty commits (web editor) (#8590) + * Add setting to disable BASIC authentication (#8586) + * Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) + * Allow Protected Branches to Whitelist Deploy Keys (#8483) + * Push to create repo (#8419) + * Sign merges, CRUD, Wiki and Repository initialisation with gpg key (#7631) + * Add basic repository lfs management (#7199) +* BUGFIXES + * Disable remove button on repository teams when have access to all (#9640) + * Clean up old references on branch delete (#9614) + * Hide public repos owned by private orgs (#9609) + * Fix access issues on milestone and issue overview pages. (#9603) + * Fix error logged when repos qs is empty (#9591) + * Dont trigger notification twice on issue assignee change (#9582) + * Fix mirror pushed commit actions (#9572) + * Allow only specific columns to be updated on issue via API (#9189) (#9539) + * Fix default avatar for ghost user (#9536) + * Fix download of release attachments with same name (#9529) + * Resolve deprecated INI conversion (#9525) + * Ignore empty avatars during database migration (#9520) + * Fix deleted branch isn't removed when push the branch again (#9516) + * Fix repository issues pagination bug when there are more than one label filter (#9512) + * Fix SetExpr failed (#9506) + * Remove obsolete file private/push_update.go (#9503) + * When recreating hooks, delete them first so they are recreated with the umask (#9502) + * Properly enforce gitea environment for pushes (#9501) + * Fix datarace on repo indexer queue (#9490) + * Add call to load repo prior to redirect in add/remove dependency code (#9484) + * Wrap the code indexer (#9476) + * Use Req.URL.RequestURI() to cope with FCGI urls (#9473) + * Set default ssh.minimum_key_sizes (#9466) + * Fixed issue with paging in /repos/{owner}/{repo}/git/trees/{sha} api (#9459) + * Fix wrong notification on merge (#9450) + * Issue with Migration rule v111 (#9449) + * Trigger webhook when deleting a branch after merging a PR (#9424) + * Add migration to sanitize repository original_url (#9423) + * Use OriginalURL instead of CloneAddr in migration logging (#9418) + * Push update after branch is restored (#9416) + * Fix wrong migration (#9381) + * Fix show repositories filter (#9234) (#9379) + * Fix Slack webhook payload title generation to work with Mattermost (#9378) + * Fix double webhook for new PR (#9375) + * AuthorizedKeysCommand should not query db directly (#9371) + * Fix missed change to GetManager() (#9361) + * Fix cache problem on dashboard (#9358) + * RepoIndexer: DefaultBranch needs to be prefixed by BranchPrefix (#9356) + * Fix protected branch using IssueID (#9348) + * Fix nondeterministic behavior (#9341) + * Fix PR/issue redirects when having external tracker (#9339) + * Remove release attachments which repository has been deleted (#9334) + * Fix issue indexer not triggered when migrating a repository (#9332) + * Add SyncTags to uploader interface (#9326) + * Fix bug that release attachment files not deleted when deleting repository (#9322) + * Only sync tags after all migration release batches are completed (#9319) + * File Edit: Author/Committer interchanged (#9297) + * prebuild CSS/JS before xgo release binaries (#9293) + * Log: Ensure FLAGS=none shows no flags (#9287) + * Make Diff Detail on Pull Request Changed File UI always on Top (#9280) + * Switch CSS minifier to cssnano (#9260) + * Fix latest docker image haven't include static files. (#9252) + * Don't link wiki revision to commit (#9244) + * Change review content column to type text in db (#9229) + * Fixed topic regex pattern and added search by topic links after save (#9219) + * Add language to user API responce (#9215) + * Correct tooltip message blocked by dependencies (#9211) + * Add SimpleMDE and Fix Image Paste for Issue/Comment Editor (#9197) + * Fix panic when diff (#9187) + * Fix #9151 - smtp logger configuration sendTos should be an array (#9154) + * Fix max length check and limit in multiple repo forms (#9148) + * Always Show Password Field on Link Account Sign-in Page (#9147) + * Properly fix displaying virtual session provider in admin panel (#9137) + * Fix race condition on indexer (#9136) + * Fix team links in HTML rendering (#9127) + * Fix race condition in ReplaceSanitizer (#9123) + * Fix what information is shown about user in API (#9115) + * Fix nil context user for template repositories (#9099) + * Hide given credentials for migrated repos. (#9097) + * Fix reCAPTCHA API URL (#9083) + * Fix password checks on admin create/edit user (#9076) + * Update golang.org/x/crypto vendor to use acme v2 (#9056) + * Ensure Written is set in GZIP ProxyResponseWriter (#9018) + * Fix wrong system notice when repository is empty (#9010) + * Fix broken link to branch from issue list (#9003) + * Fix bug when pack js (#8992) + * New review approvals shouldn't require a message (#8991) + * Shadow password correctly for session config (#8984) + * Don't send notification on pending reviews (#8943) + * Fix Notify Create Ref Error on tag creation (#8936) + * Convert EOL to UNIX-style to render MD properly (#8925) + * Migrate temp_repo.go to use git.NewCommand (#8918) + * Fix issue with user.fullname (#8902) + * Add Close() method to gogitRepository (#8901) + * Enable punctuations ending mentions (#8889) + * Fix password complexity check on registration (#8887) + * Fix require external registration password (#8885) + * Fix edit content button on migrated issue content (#8877) + * Fix permission checks for close/reopen from commit (#8875) + * Fix API Bug (fail on empty assignees) (#8873) + * Stop using git count-objects and use raw directory size for repository (#8848) + * Fix count for commit graph last page (#8843) + * Fix to close opened io resources as soon as not needed (#8839) + * Improve notification (#8835) + * Fix new user form for non-local users (#8826) + * Fix: remove duplicated signed commit icons (#8820) + * Fix (open/closed) issue count when label excluded (#8815) + * Fix SSH2 conditional in key parsing code (#8806) + * Fix 500 when edit hook (#8782) + * On windows set core.longpaths true (#8776) + * Fix commit expand button to not go to commit link (#8745) + * Avoid re-issuing redundant cross-references. (#8734) + * Fix milestone close timestamp function (#8728) + * Move webhook codes from service to webhook notification (#8712) + * Show zero lines on the line counter if the file empty (#8700) + * Fix deadline on update issue or PR via API (#8696) + * make call createMilestoneComment on newIssue func (#8678) + * Send tag create and push webhook when release created on UI (#8671) + * Prevent chrome download page as html with alt + click (#8669) + * Fix 500 when getting user as unauthenticated user (#8653) + * Graceful fixes (#8645) + * Add SubURL to redirect path (#8632) (#8634) + * Fix extra columns from `label` table (#8633) + * Add SubURL to redirect path for transferred/renamed repos (#8632) + * Fix bug when migrate from API (#8631) + * Allow to merge if file path contains " or \ (#8629) + * Prevent removal of non-empty emoji panel following selection of duplicate (#8609) + * Ensure default gpg settings not nil and found commits have reference to repo (#8604) + * Set webhook Content-Type for application/x-www-form-urlencoded (#8599) + * Fix #8582 by handling empty repos (#8587) + * Fix of the diff statistics view on pull request's (#8581) + * Fix bug on pull requests when transfer head repository (#8564) + * Fix template error on account page (#8562) + * Allow externalID to be UUID (#8551) + * Fix ignored error on editorconfig api (#8550) + * Fix user avatar name (#8547) + * Ensure that GitRepo is set on Empty repositories (#8539) + * Add missed close in ServeBlobLFS (#8527) + * Fix migrate mirror 500 bug (#8526) + * Fix password complexity regex for special characters (on master) (#8525) +* ENHANCEMENTS + * Add a /user/login landing page option (#9622) + * Some more e-mail notification fixes (#9596) + * Add branch protection option to block merge on requested changes. (#9592) + * Add footer extra links template (#9576) + * Fix for a wrong URL in activity page of repository. (#9571) + * Update default issue template (#9568) + * Change markdown rendering from blackfriday to goldmark (#9533) + * Extend file create api with dates (#9464) + * Add ActionCommentPull action (#9456) + * Response for context on retry database connection (#9444) + * Refactor webhooks to reduce code duplication (#9422) + * update couchbase deps for new license (#9419) + * Add .ignore file for search tools (#9417) + * Remove unsued struct (#9405) + * Hide not allowed Reactions (#9387) + * Remove text from action-only webhooks (#9377) + * Move PushToBaseRepo from models to services/pull (#9352) + * Site admin could view org's members (#9346) + * Sleep longer if request speed is over github limitation (#9335) + * Refactor comment (#9330) + * Refactor code indexer (#9313) + * Remove SavePatch and generate patches on the fly (#9302) + * Move some pull request functions from models to services (#9266) + * Update JS dependencies (#9255) + * Show label list on label set (#9251) + * Redirect issue if repo has configured external tracker. (#9247) + * Allow kbd tags (#9245) + * Remove unused comment actions (#9222) + * Fixed errors logging in dump.go (#9218) + * Expose release counter to repo API response (#9214) + * Make consistent links to repository in the Slack/Mattermost notificiations (#9205) + * Expose pull request counter to repo API response (#9202) + * Extend TrackedTimes API (#9200) + * Extend StopWatch API (#9196) + * Move code indexer related code to a new package (#9191) + * Docker: ask s6 to stop all service when gitea stop (#9171) + * Variable expansion in repository templates (#9163) + * Add avatar and issue labels to template repositories (#9149) + * Show single review comments in the PR conversation tab (#9143) + * Extract createComment (#9125) + * Move PushUpdateOptions from models to repofiles (#9124) + * Alternate syntax for cross references (#9116) + * Add USE_SERVICE_WORKER setting (#9110) + * Only show part of members on orgnization dashboard and add paging for orgnization members page (#9092) + * Explore page: Add topic param to pagination (#9077) (#9078) + * Markdown: Sanitizier Configuration (#9075) + * Add password requirement info on error (#9074) + * Allow authors to use act keywords in PR content (#9059) + * Move modules/gzip to gitea.com/macaron/gzip (#9058) + * Branch protection: Possibility to not use whitelist but allow anyone with write access (#9055) + * Context menus for comments, add quote reply (#9043) + * Update branch API endpoint to show effective branch protection. (#9031) + * Move git graph from models to modules/graph (#9027) + * Move merge actions to notification (#9024) + * Move mirror sync actions to notification (#9022) + * Add retry for migration http/https requests (#9019) + * Rewrite delivery of issue and comment mails (#9009) + * Add review comments to mail notifications (#8996) + * Refactor pull request review (#8954) + * Githook highlighter (#8932) + * Add git hooks and webhooks to template repositories; move to services (#8926) + * Only view branch or tag if it match refType requested. (#8899) + * Drop Admin attribute based on LDAP when login (continue #1743) (#8849) + * Add additional periods to activity page (#8829) + * Update go-org to optimize code (#8824) + * Move some actions to notification/action (#8779) + * Webhook support custom proxy (#8760) + * Fix API deadline removal (#8759) + * Mark review comment as invalidated when file is deleted (#8751) + * Move pull list code to a separate file (#8748) + * Move webhook to a standalone package under modules (#8747) + * Multi repo select on issue page (#8741) + * apply exclude label on milestone issue list (#8739) + * Move issue notifications and assignee man (#8713) + * Move issue change content from models to service (#8711) + * Move issue change status from models to service (#8691) + * Move more issue assignee code from models to issue service (#8690) + * Create PR on Current Repository by Default (#8670) + * Improve Open Graph Protocol (#8637) + * Batch hook pre- and post-receive calls (#8602) + * Improve webhooks (#8583) + * Move transfer repository and rename repository on a service package and start action notification (#8573) + * Implement/Fix PR review webhooks (#8570) + * Rewrite markdown rendering to blackfriday v2 and rewrite orgmode rendering to go-org (#8560) + * Move some repositories' operations to a standalone service package (#8557) + * Allow more than 255 characters for tokens in external_login_user table (#8554) + * Move issue label operations to issue service package (#8553) + * Adjust error reporting from merge failures and use LC_ALL=C for git (#8548) + * Mail assignee when issue/pull request is assigned (#8546) + * Allow committing / adding empty files using the web ui (#8420) (#8532) + * Move sync mirror actions to mirror service package (#8518) + * Remove arrows on numeric inputs (#8516) + * Support inline rendering of CUSTOM_URL_SCHEMES (#8496) + * Recalculate repository access only for specific user (#8481) + * Add download button for rull request diff- and patch-file (#8470) + * Add single sign-on support via SSPI on Windows (#8463) + * Move change issue title from models to issue service package (#8456) + * Add included tag on branch view (#8449) + * Make static resouces web browser cache time customized on app.ini (#8442) + * Enable Uploading/Removing Attachments When Editing an Issue/Comment (#8426) + * Add pagination to commit graph page (#8360) + * Use templates for issue e-mail subject and body (#8329) + * Move clearlabels from models to issue service (#8326) + * Move AddTestPullRequestTask to pull service package from models (#8324) + * Team permission to create repository in organization (#8312) + * Allows external rendering of other filetypes (#8300) + * Add 'Alt + click' feature to exclude labels (#8199) + * Configurable close and reopen keywords for PRs (#8120) + * Configurable URL for static resources (#7911) + * Unifies commit list in repository commit table and wiki revision page (#7907) + * Allow cross-repository dependencies on issues (#7901) + * Auto-subscribe user to repository when they commit/tag to it (#7657) + * Restore Graceful Restarting & Socket Activation (#7274) + * wiki - add 'write' 'preview' buttons to wiki edit like in issues (#7241) + * Change target branch for pull request (#6488) + * Display PR commits and diffs using base repo rather than forked (#3648) +* SECURITY + * Swagger hide search field (#9554) + * Add "search" to reserved usernames (#9063) + * Switch to fomantic-ui (#9374) + * Only serve attachments when linked to issue/release and if accessible by user (#9340) + * Hide credentials when submitting migration through API (#9102) +* TESTING + * Add debug option to serv to help debug problems (#9492) + * Fix the intermittent TestGPGGit failures (#9360) + * Testing: Update postgres sequences (#9304) + * Missed defer prepareTestEnv (#9285) + * Fix "data race" in testlogger (#9159) + * Yet another attempt to fix the intermittent failure of gpg git test (#9146) + * integrations: Fix Dropped Test Errors (#9040) + * services/mirror: fix dropped test errors (#9007) + * Fix intermittent GPG Git test failure (#8968) + * Update Github Migration Tests (#8893) (#8938) + * Update heatmap fixtures to restore tests (#8615) +* TRANSLATION + * Fix placeholders in the error message (#9060) + * Fix spelling of admin.users.max_repo_creation (#8934) + * Improve german translation of homepage (#8549) +* BUILD + * Update gitea.com/macaron to 1.4.0 (#9608) + * Upgrade lato fonts to v16. (#9498) + * Update alpine to 3.11 (#9440) + * Upgrade blevesearch (#9177) + * Remove built js/css files from git (#9114) + * Move semantic.dropdown.custom.js to webpack (#9064) + * Check compiled files during build (#9042) + * Enable lazy-loading of gitgraph.js (#9036) + * Pack web_src/js/draw.js to public/js/index.js (#8975) + * Modernize js and use babel (#8973) + * Move index.js to web_src and use webpack to pack them (#8598) + * Restrict modules/graceful to non-windows build and shim IsChild (#8537) + * Upgrade gopkg.in/editorconfig/editorconfig-core-go.v1 (#8501) +* DOCS + * Swagger info corrections (#9441) (#9558) + * Add ALLOW_ONLY_EXTERNAL_REGISTRATION to config cheat sheet (#8986) + * Rephrase comment about RuntimeDirectory option in systemd config (#8912) + * Explicitly indicate the socket unit to use the service unit "gitea.service" (#8804) + * Adjust the must-change-password help (#8755) + * Add notice to docs for migrating from more recent versions of Gogs (#8724) + * Add explicit info about customization of homepage (#8694) + * Change external asciidoctor tool to embedded mode (#8677) + * Add Docker fail2ban configuration (#8642) + * Correct some outdated statements in the contributing guidelines (#8612) + * Basic Design guidelines (describing different parts of the code) (#8601) + * Display Gitea logo in Readme (#8592) + * Fix building from source docs to ref AppWorkPath (#8567) + * Update the provided gitea.service to mention socket activation (#8531) + * Doc added how to setup email (#8520) +* MISC + * Add translatable Powered by Gitea text in footer (#9600) + * Add contrib/environment-to-ini (#9519) + * Remove unnecessary loading of settings in update hook (#9496) + * Update gitignore list (#9437) + * Update license list (#9436) + * Fix background reactions in the arc-green theme (#9421) + * Update and fix chardet import (#9351) + * Ensure LF on checkouts and in editors (#9259) + * Fixed topics margin (#9248) + * Add comment to exported function WindowsServiceName (make revive) (#9241) + * Remove empty lines on issues/pulls page (#9232) + * Fix Add Comment Button's "+" Position (#9140) + * Add first issue comment hashtag (#9052) + * Change some label colors (#9051) + * Fix double scroll in branch dropdown (#9048) + * Add comment highlight when target from url (#9047) + * Update display of reactions to issues and comments (#9038) + * Button tooltip formatting under Branches (#9034) + * Allow setting default branch via API (#9030) + * Update dashboard context for PR reviews (#8995) + * Show repository size in repo home page and settings (#8940) + * Allow to add and remove all repositories to/from team. (#8867) + * Show due date in dashboard issues list (#8860) + * Theme arc-green: reverse heatmap colors (#8840) + * Project files table style update (#8757) + * gitignore debugging file from vscode (#8740) + * Add API for Issue set Subscription (#8729) + * Make 100% width search bar (#8710) + * Update color theme for heatmap (#8709) + * Add margin to title_wip_desc (#8705) + * Improve visibility of "Pending" indicator (#8685) + * Improve accessibility of dropdown menus (#8638) + * Make /users/{username}/repos list private repos the current user has access to (#8621) + * Prevent .code-view from overriding font on icon fonts (#8614) + * Add id references on all issue events to allow internal linking (#8608) + * Upgrade xorm to v0.8.0 (#8536) + * Upgrade gopkg.in/ini.v1 (#8500) + * Update CodeMirror to version 5.49.0 (#8381) + * Wiki editor: enable side-by-side button (#7242) + +## [1.10.2](https://github.com/go-gitea/gitea/releases/tag/v1.10.2) - 2020-01-02 +* BUGFIXES + * Allow only specific Columns to be updated on Issue via API (#9539) (#9580) + * Add ErrReactionAlreadyExist error (#9550) (#9564) + * Fix bug when migrate from API (#8631) (#9563) + * Use default avatar for ghost user (#9536) (#9537) + * Fix repository issues pagination bug when there are more than one label filter (#9512) (#9528) + * Fix deleted branch not removed when push the branch again (#9516) (#9524) + * Fix missing repository status when migrating repository via API (#9511) + * Trigger webhook when deleting a branch after merging a PR (#9510) + * Fix paging on /repos/{owner}/{repo}/git/trees/{sha} API endpoint (#9482) + * Fix NewCommitStatus (#9434) (#9435) + * Use OriginalURL instead of CloneAddr in migration logging (#9418) (#9420) + * Fix Slack webhook payload title generation to work with Mattermost (#9404) + * DefaultBranch needs to be prefixed by BranchPrefix (#9356) (#9359) + * Fix issue indexer not triggered when migrating a repository (#9333) + * Fix bug that release attachment files not deleted when deleting repository (#9322) (#9329) + * Fix migration releases (#9319) (#9326) (#9328) + * Fix File Edit: Author/Committer interchanged (#9297) (#9300) + ## [1.10.1](https://github.com/go-gitea/gitea/releases/tag/v1.10.1) - 2019-12-05 * BUGFIXES * Fix max length check and limit in multiple repo forms (#9148) (#9204) @@ -39,7 +439,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Be more strict with git arguments (#7715) * Extract the username and password from the mirror url (#7651) * reserve .well-known username (#7637) -* FEATURE +* FEATURES * Org/Members: display 2FA members states + optimize sql requests (#7621) * SetDefaultBranch on pushing to empty repository (#7610) * Adds side-by-side diff for images (#6784) @@ -48,7 +448,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add option to initialize repository with labels (#6061) * Add additional password hash algorithms (#6023) * BUGFIXES - * Allow to merge if file path contains " or \ (#8629) (#8771) + * Allow to merge if file path contains " or \ (#8629) (#8771) * On windows set core.longpaths true (#8776) (#8786) * Fix 500 when edit hook (#8782) (#8789) * Fix Checkbox at RepoSettings Protected Branch (#8799) (#8801) @@ -60,7 +460,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix require external registration password (#8885) (#8890) * Fix password complexity check on registration (#8887) (#8888) * Update Github Migration Tests (#8896) (#8938) (#8945) - * Enable punctuations ending mentions (#8889) (#8894) + * Enable punctuations ending mentions (#8889) (#8894) * Add Close() method to gogitRepository (#8901) (#8956) * Hotfix for review actions and notifications (#8965) * Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) (#8618) @@ -198,7 +598,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * fix post parameter - on issue list - unset assignee (#7380) * fix/define autochecked checkboxes on issue list in firefox (#7320) * only return head: null if source branch was deleted (#6705) -* ENHANCEMENT +* ENHANCEMENTS * Add nofollow to sign in links (#8509) * vendor: update mvdan.cc/xurls/v2 to v2.1.0 (#8495) * Update milestone issues numbers when save milestone and other code improvements (#8411) @@ -392,7 +792,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix adding default Telegram webhook (#7972) (#7992) * Abort synchronization from LDAP source if there is some error (#7965) * Fix deformed emoji in commit message (#8071) -* ENHANCEMENT +* ENHANCEMENTS * Keep blame view buttons sequence consistent with normal view when viewing a file (#8007) (#8009) ## [1.9.2](https://github.com/go-gitea/gitea/releases/tag/v1.9.2) - 2019-08-22 @@ -404,7 +804,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * SECURITY * Fix No PGP signature on 1.9.1 tag (#7874) * Release built with go 1.12.9 to fix security fixes in golang std lib, ref: https://groups.google.com/forum/#!msg/golang-announce/oeMaeUnkvVE/a49yvTLqAAAJ -* ENHANCEMENT +* ENHANCEMENTS * Fix pull creation with empty changes (#7920) (#7926) * BUILD * Drone/docker: prepare multi-arch release + provide arm64 image (#7571) (#7884) @@ -445,7 +845,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Reserve .well-known username (#7638) * Do not leak secrets via timing side channel (#7364) * Ensure that decryption of cookie actually suceeds (#7363) -* FEATURE +* FEATURES * Content API for Creating, Updating, Deleting Files (#6314) * Enable tls-alpn-01: Use certmanager provided TLSConfig for LetsEncrypt (#7229) * Add command to convert mysql database from utf8 to utf8mb4 (#7144) @@ -636,7 +1036,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix bug manifest.json will not request with cookie so that session will created every request (#6372) * Disable benchmarking during tag events on DroneIO (#6365) * Comments list performance optimization (#5305) -* ENHANCEMENT +* ENHANCEMENTS * Update Drone docker generation to standard format (#7480) (#7496) (#7504) * Add API Endpoint for Repo Edit (#7006) * Add state param to milestone listing API (#7131) @@ -856,7 +1256,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Releases API paging (#5831) * Allow Macaron to be set to log through to gitea.log (#5667) * Don't close issues via commits on non-default branch (#5622) -* FEATURE +* FEATURES * Add regenerate secret feature for oauth2 (#6291) * Expose issue stopwatch toggling via API (#5970) * Add other session providers (#5963) @@ -867,7 +1267,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Discord Oauth2 support (#4476) * Allow to set organization visibility (public, internal, private) (#1763) * Added URL mapping for Release attachments like on github.com (#1707) -* ENHANCEMENT +* ENHANCEMENTS * Add support for client basic auth for exchanging access tokens (#6293) * Add ability to sort issues by due date (#6206) (#6244) * Style tweaks to issue selection (#6196) @@ -1157,7 +1557,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * BREAKING * Restrict permission check on repositories and fix some problems (#5314) * Show only opened milestones on issues page milestone filter (#5051) -* FEATURE +* FEATURES * Implement git refs API for listing references (branches, tags and other) (#5354) * Approvals at Branch Protection (#5350) * Add raw blob endpoint to get objects by SHA ID (#5334) @@ -1242,7 +1642,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * LDAP via simple auth separate bind user and search base (#5055) * Fix markdown image with link (#4675) * Fix to 3819 - Filtering issues by tags on main screen issues (#3824) -* ENHANCEMENT +* ENHANCEMENTS * Delete organization endpoint added (#5601) * Update Licenses (#5558) * Support reverse proxy providing email (#5554) @@ -1352,7 +1752,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Make cookies HttpOnly and obey COOKIE_SECURE flag (#4706) * Don't disclose emails of all users when sending out emails (#4664) * Check that repositories can only be migrated to own user or organizations (#4366) -* FEATURE +* FEATURES * Add comment replies (#5147) (#5104) * Pull request review/approval and comment on code (#3748) * Added dependencies for issues (#2196) (#2531) @@ -1365,7 +1765,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add push webhook support for mirrored repositories (#4127) * Add csv file render support defaultly (#4105) * Add Recaptcha functionality to Gitea (#4044) -* ENHANCEMENT +* ENHANCEMENTS * Fix milestones sorted wrongly (#4987) * Allow api to create tags for releases if they don't exist (#4890) * Fix #4877 to follow the OpenID Connect Audiences spec (#4878) @@ -1547,7 +1947,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix bugs when too many IN variables (#4594) (#4597) * Push whitelist now doesn't apply to branch deletion (#4601) (#4640) * Site admin could create repos even MAX_CREATION_LIMIT=0 (#4645) (#4650) -* FEATURE +* FEATURES * Add cli commands to regen hooks & keys (#3979) * Add support for FIDO U2F (#3971) * Added user language setting (#3875) @@ -1561,7 +1961,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add repository setting to enable/disable health checks (#3607) * Emoji Autocomplete (#3433) * Implements generator cli for secrets (#3531) -* ENHANCEMENT +* ENHANCEMENTS * Add more webhooks support and refactor webhook templates directory (#3929) * Add new option to allow only OAuth2/OpenID user registration (#3910) * Add option to use paged LDAP search when synchronizing users (#3895) @@ -1645,7 +2045,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix escaping changed title in comments (#3530) (#3534)  * Escape search query (#3486) (#3488) * Sanitize logs for mirror sync (#3057) -* FEATURE +* FEATURES * Serve .patch and .diff for pull requests (#3305, #3293) * Add repo-sync-releases admin command (#3254) * Support default private when creating or migrating repository (#3239) @@ -1708,7 +2108,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix go-get, src and raw urls to new scheme (#2978) * Fix error when add user has full name to team (#2973) * Fix memcache support when value is returned as string always (#2924) -* ENHANCEMENT +* ENHANCEMENTS * Use GiteaServer as the user agent for http requests (#3404) * Delete indexer DB entries when (re)creating index (#3385) * Change how merged PR commit info are prepared (#3368) @@ -1767,7 +2167,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). ## [1.3.0](https://github.com/go-gitea/gitea/releases/tag/v1.3.0) - 2017-11-29 * BREAKING * Make URL scheme unambiguous (#2408) -* FEATURE +* FEATURES * Add branch overiew page (#2108) * Code/repo search (#2582) * Add Activity page to repository (#2674) @@ -1871,7 +2271,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Fix typos in app.ini (#2732) * Fix duplicated rel attribute (#2549) * Fix tests code to prevent some runtime errors (#2381) -* ENHANCEMENT +* ENHANCEMENTS * Memory usage improvements and lower minimal git requirement to 1.7.2 (#3013) (#3028) * Set OpenID support on by default when installing new instance (#3010) (#3027) * Use api.TrackedTime in API (#2807) @@ -2017,7 +2417,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Sanitation fix from Gogs (#1461) * BREAKING * Rename /forget_password url to /forgot_password (#1219) -* FEATURE +* FEATURES * Logo: Add task to generate images from SVG and change to new logo (#2194) * Status-API (#1332) * Show commit status icon in commits table (#1688) @@ -2044,7 +2444,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Add change-password admin command (#1304) * Only use issue and wiki on repo. (#1297) * Allow push to init a wiki repo (#1279) -* ENHANCEMENT +* ENHANCEMENTS * Make time diff translatable (#2057) * Smaller watch, star, and fork buttons (#2052) * Display config file path on admin panel (#2030) @@ -2539,7 +2939,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * BREAKING * The SSH keys can potentially break, make sure to regenerate the authorized keys -* FEATURE +* FEATURES * Git LFSv2 support [#122](https://github.com/go-gitea/gitea/pull/122) * API endpoints for repo watching [#191](https://github.com/go-gitea/gitea/pull/191) * Search within private repos [#222](https://github.com/go-gitea/gitea/pull/222) @@ -2591,7 +2991,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Don't rewrite non-gitea public keys [#906](https://github.com/go-gitea/gitea/pull/906) * Use fingerprint to check instead content for public key [#911](https://github.com/go-gitea/gitea/pull/911) * Fix random avatars [#1147](https://github.com/go-gitea/gitea/pull/1147) -* ENHANCEMENT +* ENHANCEMENTS * Refactored process manager [#75](https://github.com/go-gitea/gitea/pull/75) * Restrict rights to create new orgs [#193](https://github.com/go-gitea/gitea/pull/193) * Added label and milestone sorting [#199](https://github.com/go-gitea/gitea/pull/199) @@ -2653,7 +3053,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * BREAKING * We have various changes on the API, scripting against API must be updated -* FEATURE +* FEATURES * Show last login for admins [#121](https://github.com/go-gitea/gitea/pull/121) * BUGFIXES * Fixed sender of notifications [#2](https://github.com/go-gitea/gitea/pull/2) @@ -2666,7 +3066,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Replace tabs with spaces on wiki title [#371](https://github.com/go-gitea/gitea/pull/371) * Fixed vulnerability on labels and releases [#409](https://github.com/go-gitea/gitea/pull/409) * Fixed issue comment API [#449](https://github.com/go-gitea/gitea/pull/449) -* ENHANCEMENT +* ENHANCEMENTS * Use proper import path for libravatar [#3](https://github.com/go-gitea/gitea/pull/3) * Integrated DroneCI for tests and builds [#24](https://github.com/go-gitea/gitea/issues/24) * Integrated dependency manager [#29](https://github.com/go-gitea/gitea/issues/29) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0134974ac0a..2172aeec243d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,7 @@ - [Code review](#code-review) - [Styleguide](#styleguide) - [Design guideline](#design-guideline) + - [API v1](#api-v1) - [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco) - [Release Cycle](#release-cycle) - [Maintainers](#maintainers) @@ -177,6 +178,43 @@ To maintain understandable code and avoid circular dependencies it is important - **templates:** Golang templates for generating the html output. - **vendor:** External code that Gitea depends on. +## API v1 + +The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [GitHub API v3](https://developer.github.com/v3/). +Thus, Gitea´s API should use the same endpoints and fields as GitHub´s API as far as possible, unless there are good reasons to deviate. +If Gitea provides functionality that GitHub does not, a new endpoint can be created. +If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields. + +Updating an existing API should not remove existing fields unless there is a really good reason to do so. +The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to APIv2 (which is currently not planned). + +All expected results (errors, success, fail messages) should be documented +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)). + +All JSON input types must be defined as a struct in `models/structs/` +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91)) +and referenced in +[routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go). +They can then be used like the following: +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318)). + +All JSON responses must be defined as a struct in `models/structs/` +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68)) +and referenced in its category in `routers/api/v1/swagger/` +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16)) +They can be used like the following: +([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279)) + +In general, HTTP methods are chosen as follows: + * **GET** endpoints return requested object and status **OK (200)** + * **DELETE** endpoints return status **No Content (204)** + * **POST** endpoints return status **Created (201)**, used to **create** new objects (e.g. a User) + * **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Obejcts (e.g. User) to something (e.g. Org-Team) + * **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object + + +An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required). + ## Developer Certificate of Origin (DCO) diff --git a/Makefile b/Makefile index e4dc3930081a..1815dd7579a5 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,25 @@ all: build include docker/Makefile +.PHONY: help +help: + @echo "Make Routines:" + @echo " - \"\" equivalent to \"build\"" + @echo " - build creates the entire project" + @echo " - clean delete integration files and build files but not css and js files" + @echo " - clean-all delete all generated files (integration test, build, css and js files)" + @echo " - css rebuild only css files" + @echo " - js rebuild only js files" + @echo " - generate run \"make css js\" and \"go generate\"" + @echo " - fmt format the code" + @echo " - generate-swagger generate the swagger spec from code comments" + @echo " - swagger-validate check if the swagger spec is valide" + @echo " - revive run code linter revive" + @echo " - misspell check if a word is written wrong" + @echo " - vet examines Go source code and reports suspicious constructs" + @echo " - test run unit test" + @echo " - test-sqlite run integration test for sqlite" + .PHONY: go-check go-check: $(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell go version | grep -Eo '[0-9]+\.?[0-9]+?\.?[0-9]?\s' | tr '.' ' ');)) diff --git a/cmd/admin.go b/cmd/admin.go index cd083a29e89c..0b9f6eac441b 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -155,6 +155,7 @@ var ( microcmdAuthDelete = cli.Command{ Name: "delete", Usage: "Delete specific auth source", + Flags: []cli.Flag{idFlag}, Action: runDeleteAuth, } @@ -533,9 +534,9 @@ func runListAuth(c *cli.Context) error { // loop through each source and print w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight) - fmt.Fprintf(w, "ID\tName\tType\tEnabled") + fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") for _, source := range loginSources { - fmt.Fprintf(w, "%d\t%s\t%s\t%t", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived) + fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived) } w.Flush() diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index cdad6a911eaa..d1ad21187310 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -290,7 +290,8 @@ ENABLE_GZIP = false ENABLE_PPROF = false ; PPROF_DATA_PATH, use an absolute path when you start gitea as service PPROF_DATA_PATH = data/tmp/pprof -; Landing page, can be "home", "explore", or "organizations" +; Landing page, can be "home", "explore", "organizations" or "login" +; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in. LANDING_PAGE = home ; Enables git-lfs support. true or false, default is false. LFS_START_SERVER = false @@ -381,6 +382,39 @@ REPO_INDEXER_INCLUDE = ; A comma separated list of glob patterns to exclude from the index; ; default is empty REPO_INDEXER_EXCLUDE = +[queue] +; Specific queues can be individually configured with [queue.name]. [queue] provides defaults +; +; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy +; default to persistable-channel +TYPE = persistable-channel +; data-dir for storing persistable queues and level queues, individual queues will be named by their type +DATADIR = queues/ +; Default queue length before a channel queue will block +LENGTH = 20 +; Batch size to send for batched queues +BATCH_LENGTH = 20 +; Connection string for redis queues this will store the redis connection string. +CONN_STR = "addrs=127.0.0.1:6379 db=0" +; Provide the suffix of the default redis queue name - specific queues can be overriden within in their [queue.name] sections. +QUEUE_NAME = "_queue" +; If the queue cannot be created at startup - level queues may need a timeout at startup - wrap the queue: +WRAP_IF_NECESSARY = true +; Attempt to create the wrapped queue at max +MAX_ATTEMPTS = 10 +; Timeout queue creation +TIMEOUT = 15m30s +; Create a pool with this many workers +WORKERS = 1 +; Dynamically scale the worker pool to at this many workers +MAX_WORKERS = 10 +; Add boost workers when the queue blocks for BLOCK_TIMEOUT +BLOCK_TIMEOUT = 1s +; Remove the boost workers after BOOST_TIMEOUT +BOOST_TIMEOUT = 5m +; During a boost add BOOST_WORKERS +BOOST_WORKERS = 5 + [admin] ; Disallow regular (non-admin) users from creating organizations. DISABLE_REGULAR_ORG_CREATION = false diff --git a/docs/config.yaml b/docs/config.yaml index a0a349a78062..cbfcd49e6ed9 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,7 +18,7 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io - version: 1.10.1 + version: 1.10.2 outputs: home: diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 76843a183a41..b85e70989d0e 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -186,7 +186,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `STATIC_ROOT_PATH`: **./**: Upper level of template and static files path. - `STATIC_CACHE_TIME`: **6h**: Web browser cache time for static resources on `custom/`, `public/` and all uploaded avatars. - `ENABLE_GZIP`: **false**: Enables application-level GZIP support. -- `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore\]. +- `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. - `LFS_START_SERVER`: **false**: Enables git-lfs support. - `LFS_CONTENT_PATH`: **./data/lfs**: Where to store LFS files. - `LFS_JWT_SECRET`: **\**: LFS authentication secret, change this a unique string. @@ -226,6 +226,7 @@ relation to port exhaustion. - `ISSUE_INDEXER_TYPE`: **bleve**: Issue indexer type, currently support: bleve or db, if it's db, below issue indexer item will be invalid. - `ISSUE_INDEXER_PATH`: **indexers/issues.bleve**: Index file used for issue search. +- The next 4 configuration values are deprecated and should be set in `queue.issue_indexer` however are kept for backwards compatibility: - `ISSUE_INDEXER_QUEUE_TYPE`: **levelqueue**: Issue indexer queue, currently supports:`channel`, `levelqueue`, `redis`. - `ISSUE_INDEXER_QUEUE_DIR`: **indexers/issues.queue**: When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this will be the queue will be saved path. - `ISSUE_INDEXER_QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. @@ -239,6 +240,24 @@ relation to port exhaustion. - `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of files to be indexed. - `STARTUP_TIMEOUT`: **30s**: If the indexer takes longer than this timeout to start - fail. (This timeout will be added to the hammer time above for child processes - as bleve will not start until the previous parent is shutdown.) Set to zero to never timeout. +## Queue (`queue` and `queue.*`) + +- `TYPE`: **persistable-channel**: General queue type, currently support: `persistable-channel`, `channel`, `level`, `redis`, `dummy` +- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for inidividual queues can be set in `queue.name` sections but will default to `DATADIR/`**`name`**. +- `LENGTH`: **20**: Maximal queue size before channel queues block +- `BATCH_LENGTH`: **20**: Batch data before passing to the handler +- `CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Connection string for the redis queue type. +- `QUEUE_NAME`: **_queue**: The suffix for default redis queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section. +- `WRAP_IF_NECESSARY`: **true**: Will wrap queues with a timeoutable queue if the selected queue is not ready to be created - (Only relevant for the level queue.) +- `MAX_ATTEMPTS`: **10**: Maximum number of attempts to create the wrapped queue +- `TIMEOUT`: **GRACEFUL_HAMMER_TIME + 30s**: Timeout the creation of the wrapped queue if it takes longer than this to create. +- Queues by default come with a dynamically scaling worker pool. The following settings configure this: +- `WORKERS`: **1**: Number of initial workers for the queue. +- `MAX_WORKERS`: **10**: Maximum number of worker go-routines for the queue. +- `BLOCK_TIMEOUT`: **1s**: If the queue blocks for this time, boost the number of workers - the `BLOCK_TIMEOUT` will then be doubled before boosting again whilst the boost is ongoing. +- `BOOST_TIMEOUT`: **5m**: Boost workers will timeout after this long. +- `BOOST_WORKERS`: **5**: This many workers will be added to the worker pool if there is a boost. + ## Admin (`admin`) - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled @@ -615,6 +634,7 @@ You may redefine `ELEMENT`, `ALLOW_ATTR`, and `REGEXP` multiple times; each time ## Task (`task`) +- Task queue configuration has been moved to `queue.task` however, the below configuration values are kept for backwards compatibilityx: - `QUEUE_TYPE`: **channel**: Task queue type, could be `channel` or `redis`. - `QUEUE_LENGTH`: **1000**: Task queue length, available only when `QUEUE_TYPE` is `channel`. - `QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If there redis needs a password, use `addrs=127.0.0.1:6379 password=123 db=0`. diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index 8db022772d0a..52d57f676434 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -44,12 +44,6 @@ environment variable; this can be used to override the default path to something **Note:** Gitea must perform a full restart to see configuration changes. -## Customizing /robots.txt - -To make Gitea serve a custom `/robots.txt` (default: empty 404), create a file called -`robots.txt` in the `custom` folder (or `CustomPath`) with -[expected contents](http://www.robotstxt.org/). - ## Serving custom public files To make Gitea serve custom public files (like pages and images), use the folder @@ -80,10 +74,10 @@ Dont forget to restart your gitea to apply the changes. ### Adding links and tabs -If all you want is to add extra links to the top navigation bar, or extra tabs to the repository view, you can put them in `extra_links.tmpl` and `extra_tabs.tmpl` inside your `custom/templates/custom/` directory. +If all you want is to add extra links to the top navigation bar or footer, or extra tabs to the repository view, you can put them in `extra_links.tmpl` (links added to the navbar), `extra_links_footer.tmpl` (links added to the left side of footer), and `extra_tabs.tmpl` inside your `custom/templates/custom/` directory. For instance, let's say you are in Germany and must add the famously legally-required "Impressum"/about page, listing who is responsible for the site's content: -just place it under your "custom/public/" directory (for instance `custom/public/impressum.html`) and put a link to it in `custom/templates/custom/extra_links.tmpl`. +just place it under your "custom/public/" directory (for instance `custom/public/impressum.html`) and put a link to it in either `custom/templates/custom/extra_links.tmpl` or `custom/templates/custom/extra_links_footer.tmpl`. To match the current style, the link should have the class name "item", and you can use `{{AppSubUrl}}` to get the base URL: `Impressum` diff --git a/docs/content/doc/advanced/search-engines-indexation.en-us.md b/docs/content/doc/advanced/search-engines-indexation.en-us.md new file mode 100644 index 000000000000..f6dc498e1aa1 --- /dev/null +++ b/docs/content/doc/advanced/search-engines-indexation.en-us.md @@ -0,0 +1,39 @@ +--- +date: "2019-12-31T13:55:00+05:00" +title: "Advanced: Search Engines Indexation" +slug: "search-engines-indexation" +weight: 30 +toc: true +draft: false +menu: + sidebar: + parent: "advanced" + name: "Search Engines Indexation" + weight: 60 + identifier: "search-engines-indexation" +--- + +# Search engines indexation of your Gitea installation + +By default your Gitea installation will be indexed by search engines. +If you don't want your repository to be visible for search engines read further. + +## Block search engines indexation using robots.txt + +To make Gitea serve a custom `robots.txt` (default: empty 404) for top level installations, +create a file called `robots.txt` in the [`custom` folder or `CustomPath`]({{< relref "doc/advanced/customizing-gitea.en-us.md" >}}) + +Examples on how to configure the `robots.txt` can be found at [https://moz.com/learn/seo/robotstxt](https://moz.com/learn/seo/robotstxt). + + +```txt +User-agent: * +Disallow: / +``` + +If you installed Gitea in a subdirectory, you will need to create or edit the `robots.txt` in the top level directory. + +```txt +User-agent: * +Disallow: /gitea/ +``` diff --git a/go.mod b/go.mod index 5d22b827451e..8af6890691a8 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( gitea.com/macaron/gzip v0.0.0-20191118033930-0c4c5566a0e5 gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a - gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb + gitea.com/macaron/macaron v1.4.0 gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 github.com/PuerkitoBio/goquery v1.5.0 @@ -88,7 +88,7 @@ require ( github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect github.com/tstranex/u2f v1.0.0 github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1 - github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e + github.com/unknwon/com v1.0.1 github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 github.com/urfave/cli v1.20.0 @@ -104,7 +104,7 @@ require ( gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - gopkg.in/ini.v1 v1.48.0 + gopkg.in/ini.v1 v1.51.1 gopkg.in/ldap.v3 v3.0.2 gopkg.in/src-d/go-billy.v4 v4.3.2 gopkg.in/src-d/go-git.v4 v4.13.1 diff --git a/go.sum b/go.sum index 247630d47d2e..b945bb7edbc8 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Y gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw= gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA= gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs= +gitea.com/macaron/macaron v1.4.0 h1:FY1QDGqyuUzs21K6ChkbYbRUfwL7v2aUrhNEJ0IgsAw= +gitea.com/macaron/macaron v1.4.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY= gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 h1:mvkQGAlON1Z6Y8pqa/+FpYIskk54mazuECUfZK5oTg0= gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA= gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk= @@ -532,6 +534,8 @@ github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1 h1:SpoCl3+Pta5/ubQyF+F github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU= github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM= github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +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/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE= github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:Z79lyIznnziKADUf0J7EP8Z4ZL7YJDiPuaazlfUBSy4= @@ -721,8 +725,8 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8= -gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index f412f5af085d..382fe606bf35 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -62,3 +62,61 @@ func TestAPICreateIssue(t *testing.T) { Title: title, }) } + +func TestAPIEditIssue(t *testing.T) { + defer prepareTestEnv(t)() + + issueBefore := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository) + owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + assert.NoError(t, issueBefore.LoadAttributes()) + assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session) + + // update values of issue + issueState := "closed" + removeDeadline := true + milestone := int64(4) + body := "new content!" + title := "new title from api set" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repo.Name, issueBefore.Index, token) + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{ + State: &issueState, + RemoveDeadline: &removeDeadline, + Milestone: &milestone, + Body: &body, + Title: title, + + // ToDo change more + }) + resp := session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + issueAfter := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + + // check deleted user + assert.Equal(t, int64(500), issueAfter.PosterID) + assert.NoError(t, issueAfter.LoadAttributes()) + assert.Equal(t, int64(-1), issueAfter.PosterID) + assert.Equal(t, int64(-1), issueBefore.PosterID) + assert.Equal(t, int64(-1), apiIssue.Poster.ID) + + // API response + assert.Equal(t, api.StateClosed, apiIssue.State) + assert.Equal(t, milestone, apiIssue.Milestone.ID) + assert.Equal(t, body, apiIssue.Body) + assert.True(t, apiIssue.Deadline == nil) + assert.Equal(t, title, apiIssue.Title) + + // in database + assert.Equal(t, api.StateClosed, issueAfter.State()) + assert.Equal(t, milestone, issueAfter.MilestoneID) + assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix)) + assert.Equal(t, body, issueAfter.Content) + assert.Equal(t, title, issueAfter.Title) +} diff --git a/integrations/attachement_test.go b/integrations/attachement_test.go deleted file mode 100644 index 8d709a376ec9..000000000000 --- a/integrations/attachement_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package integrations - -import ( - "bytes" - "image" - "image/png" - "io" - "mime/multipart" - "net/http" - "testing" - - "code.gitea.io/gitea/modules/test" - "github.com/stretchr/testify/assert" -) - -func generateImg() bytes.Buffer { - // Generate image - myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - return buff -} - -func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string { - body := &bytes.Buffer{} - - //Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("file", filename) - assert.NoError(t, err) - _, err = io.Copy(part, &buff) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - csrf := GetCSRF(t, session, repoURL) - - req := NewRequestWithBody(t, "POST", "/attachments", body) - req.Header.Add("X-Csrf-Token", csrf) - req.Header.Add("Content-Type", writer.FormDataContentType()) - resp := session.MakeRequest(t, req, expectedStatus) - - if expectedStatus != http.StatusOK { - return "" - } - var obj map[string]string - DecodeJSON(t, resp, &obj) - return obj["uuid"] -} - -func TestCreateAnonymousAttachment(t *testing.T) { - prepareTestEnv(t) - session := emptyTestSession(t) - createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusFound) -} - -func TestCreateIssueAttachement(t *testing.T) { - prepareTestEnv(t) - const repoURL = "user2/repo1" - session := loginUser(t, "user2") - uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK) - - req := NewRequest(t, "GET", repoURL+"/issues/new") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - - link, exists := htmlDoc.doc.Find("form").Attr("action") - assert.True(t, exists, "The template has changed") - - postData := map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "title": "New Issue With Attachement", - "content": "some content", - "files[0]": uuid, - } - - req = NewRequestWithValues(t, "POST", link, postData) - resp = session.MakeRequest(t, req, http.StatusFound) - test.RedirectURL(resp) // check that redirect URL exists - - //Validate that attachement is available - req = NewRequest(t, "GET", "/attachments/"+uuid) - session.MakeRequest(t, req, http.StatusOK) -} diff --git a/integrations/attachment_test.go b/integrations/attachment_test.go new file mode 100644 index 000000000000..746256df9554 --- /dev/null +++ b/integrations/attachment_test.go @@ -0,0 +1,137 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "bytes" + "image" + "image/png" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" + "path" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func generateImg() bytes.Buffer { + // Generate image + myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) + var buff bytes.Buffer + png.Encode(&buff, myImage) + return buff +} + +func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string { + body := &bytes.Buffer{} + + //Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", filename) + assert.NoError(t, err) + _, err = io.Copy(part, &buff) + assert.NoError(t, err) + err = writer.Close() + assert.NoError(t, err) + + csrf := GetCSRF(t, session, repoURL) + + req := NewRequestWithBody(t, "POST", "/attachments", body) + req.Header.Add("X-Csrf-Token", csrf) + req.Header.Add("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, expectedStatus) + + if expectedStatus != http.StatusOK { + return "" + } + var obj map[string]string + DecodeJSON(t, resp, &obj) + return obj["uuid"] +} + +func TestCreateAnonymousAttachment(t *testing.T) { + prepareTestEnv(t) + session := emptyTestSession(t) + createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusFound) +} + +func TestCreateIssueAttachment(t *testing.T) { + prepareTestEnv(t) + const repoURL = "user2/repo1" + session := loginUser(t, "user2") + uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK) + + req := NewRequest(t, "GET", repoURL+"/issues/new") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + link, exists := htmlDoc.doc.Find("form").Attr("action") + assert.True(t, exists, "The template has changed") + + postData := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": "New Issue With Attachment", + "content": "some content", + "files": uuid, + } + + req = NewRequestWithValues(t, "POST", link, postData) + resp = session.MakeRequest(t, req, http.StatusFound) + test.RedirectURL(resp) // check that redirect URL exists + + //Validate that attachment is available + req = NewRequest(t, "GET", "/attachments/"+uuid) + session.MakeRequest(t, req, http.StatusOK) +} + +func TestGetAttachment(t *testing.T) { + prepareTestEnv(t) + adminSession := loginUser(t, "user1") + user2Session := loginUser(t, "user2") + user8Session := loginUser(t, "user8") + emptySession := emptyTestSession(t) + testCases := []struct { + name string + uuid string + createFile bool + session *TestSession + want int + }{ + {"LinkedIssueUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, user2Session, http.StatusOK}, + {"LinkedCommentUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", true, user2Session, http.StatusOK}, + {"linked_release_uuid", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", true, user2Session, http.StatusOK}, + {"NotExistingUUID", "b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusNotFound}, + {"FileMissing", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusInternalServerError}, + {"NotLinked", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user2Session, http.StatusNotFound}, + {"NotLinkedAccessibleByUploader", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user8Session, http.StatusOK}, + {"PublicByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, emptySession, http.StatusOK}, + {"PrivateByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, emptySession, http.StatusNotFound}, + {"PrivateAccessibleByAdmin", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, adminSession, http.StatusOK}, + {"PrivateAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user2Session, http.StatusOK}, + {"RepoNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user8Session, http.StatusNotFound}, + {"OrgNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21", true, user8Session, http.StatusNotFound}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + //Write empty file to be available for response + if tc.createFile { + localPath := models.AttachmentLocalPath(tc.uuid) + err := os.MkdirAll(path.Dir(localPath), os.ModePerm) + assert.NoError(t, err) + err = ioutil.WriteFile(localPath, []byte("hello world"), 0644) + assert.NoError(t, err) + } + //Actual test + req := NewRequest(t, "GET", "/attachments/"+tc.uuid) + tc.session.MakeRequest(t, req, tc.want) + }) + } +} diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba b/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba new file mode 100644 index 000000000000..d3c45d51ea5d Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/00/750edc07d6415dcc07ae0351e9397b0222b7ba differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 b/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 new file mode 100644 index 000000000000..bf97d00fd85d Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/4a/357436d925b5c974181ff12a994538ddc5a269 differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b b/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b new file mode 100644 index 000000000000..7678d6754dca Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/repo1.git/objects/dc/7a8ba127fee870dd683310ce660dfe59333a1b differ diff --git a/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head new file mode 100644 index 000000000000..98593d653703 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/repo1.git/refs/pull/3/head @@ -0,0 +1 @@ +4a357436d925b5c974181ff12a994538ddc5a269 diff --git a/integrations/issue_test.go b/integrations/issue_test.go index fe66a005047f..1454d7588501 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -11,8 +11,10 @@ import ( "strconv" "strings" "testing" + "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -87,7 +89,12 @@ func TestViewIssuesKeyword(t *testing.T) { defer prepareTestEnv(t)() repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - + issue := models.AssertExistsAndLoadBean(t, &models.Issue{ + RepoID: repo.ID, + Index: 1, + }).(*models.Issue) + issues.UpdateIssueIndexer(issue) + time.Sleep(time.Second * 1) const keyword = "first" req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.RelLink(), keyword) resp := MakeRequest(t, req, http.StatusOK) diff --git a/integrations/setting_test.go b/integrations/setting_test.go index 518a3b7c6f8f..eb495acb2445 100644 --- a/integrations/setting_test.go +++ b/integrations/setting_test.go @@ -99,5 +99,10 @@ func TestSettingLandingPage(t *testing.T) { resp = MakeRequest(t, req, http.StatusFound) assert.Equal(t, "/explore/organizations", resp.Header().Get("Location")) + setting.LandingPageURL = setting.LandingPageLogin + req = NewRequest(t, "GET", "/") + resp = MakeRequest(t, req, http.StatusFound) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + setting.LandingPageURL = landingPage } diff --git a/models/attachment.go b/models/attachment.go index 487ddd4ad5b4..6cfa6cb64ec3 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -71,6 +71,26 @@ func (a *Attachment) DownloadURL() string { return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID) } +// LinkedRepository returns the linked repo if any +func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) { + if a.IssueID != 0 { + iss, err := GetIssueByID(a.IssueID) + if err != nil { + return nil, UnitTypeIssues, err + } + repo, err := GetRepositoryByID(iss.RepoID) + return repo, UnitTypeIssues, err + } else if a.ReleaseID != 0 { + rel, err := GetReleaseByID(a.ReleaseID) + if err != nil { + return nil, UnitTypeReleases, err + } + repo, err := GetRepositoryByID(rel.RepoID) + return repo, UnitTypeReleases, err + } + return nil, -1, nil +} + // NewAttachment creates a new attachment object. func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) { attach.UUID = gouuid.NewV4().String() diff --git a/models/attachment_test.go b/models/attachment_test.go index f38a5beeee13..ddb6abad3212 100644 --- a/models/attachment_test.go +++ b/models/attachment_test.go @@ -61,7 +61,7 @@ func TestGetByCommentOrIssueID(t *testing.T) { // count of attachments from issue ID attachments, err := GetAttachmentsByIssueID(1) assert.NoError(t, err) - assert.Equal(t, 2, len(attachments)) + assert.Equal(t, 1, len(attachments)) attachments, err = GetAttachmentsByCommentID(1) assert.NoError(t, err) @@ -73,7 +73,7 @@ func TestDeleteAttachments(t *testing.T) { count, err := DeleteAttachmentsByIssue(4, false) assert.NoError(t, err) - assert.Equal(t, 1, count) + assert.Equal(t, 2, count) count, err = DeleteAttachmentsByComment(2, false) assert.NoError(t, err) @@ -128,3 +128,31 @@ func TestGetAttachmentsByUUIDs(t *testing.T) { assert.Equal(t, int64(1), attachList[0].IssueID) assert.Equal(t, int64(5), attachList[1].IssueID) } + +func TestLinkedRepository(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + testCases := []struct { + name string + attachID int64 + expectedRepo *Repository + expectedUnitType UnitType + }{ + {"LinkedIssue", 1, &Repository{ID: 1}, UnitTypeIssues}, + {"LinkedComment", 3, &Repository{ID: 1}, UnitTypeIssues}, + {"LinkedRelease", 9, &Repository{ID: 1}, UnitTypeReleases}, + {"Notlinked", 10, nil, -1}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + attach, err := GetAttachmentByID(tc.attachID) + assert.NoError(t, err) + repo, unitType, err := attach.LinkedRepository() + assert.NoError(t, err) + if tc.expectedRepo != nil { + assert.Equal(t, tc.expectedRepo.ID, repo.ID) + } + assert.Equal(t, tc.expectedUnitType, unitType) + + }) + } +} diff --git a/models/branches.go b/models/branches.go index 21b23c75d92d..385817e4f982 100644 --- a/models/branches.go +++ b/models/branches.go @@ -44,6 +44,7 @@ type ProtectedBranch struct { ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` + BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } @@ -166,6 +167,23 @@ func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) return approvals } +// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews +func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool { + if !protectBranch.BlockOnRejectedReviews { + return false + } + rejectExist, err := x.Where("issue_id = ?", pr.IssueID). + And("type = ?", ReviewTypeReject). + And("official = ?", true). + Exist(new(Review)) + if err != nil { + log.Error("MergeBlockedByRejectedReview: %v", err) + return true + } + + return rejectExist +} + // GetProtectedBranchByRepoID getting protected branch by repo ID func GetProtectedBranchByRepoID(repoID int64) ([]*ProtectedBranch, error) { protectedBranches := make([]*ProtectedBranch, 0) @@ -340,7 +358,7 @@ func (repo *Repository) IsProtectedBranchForMerging(pr *PullRequest, branchName if err != nil { return true, err } else if has { - return !protectedBranch.CanUserMerge(doer.ID) || !protectedBranch.HasEnoughApprovals(pr), nil + return !protectedBranch.CanUserMerge(doer.ID) || !protectedBranch.HasEnoughApprovals(pr) || protectedBranch.MergeBlockedByRejectedReview(pr), nil } return false, nil diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml index 289d4d0efd6c..2606d52b4716 100644 --- a/models/fixtures/attachment.yml +++ b/models/fixtures/attachment.yml @@ -10,7 +10,7 @@ - id: 2 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12 - issue_id: 1 + issue_id: 4 comment_id: 0 name: attach2 download_count: 1 @@ -81,6 +81,15 @@ - id: 10 uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20 + uploader_id: 8 + name: attach1 + download_count: 0 + created_unix: 946684800 + +- + id: 11 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21 + release_id: 2 name: attach1 download_count: 0 - created_unix: 946684800 \ No newline at end of file + created_unix: 946684800 diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 6b57268a7a02..ecee7499f6f0 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -108,4 +108,17 @@ is_closed: false is_pull: true created_unix: 946684820 - updated_unix: 978307180 \ No newline at end of file + updated_unix: 978307180 + +- + id: 10 + repo_id: 42 + index: 1 + poster_id: 500 + name: issue from deleted account + content: content from deleted account + is_closed: false + is_pull: false + created_unix: 946684830 + updated_unix: 999307200 + deadline_unix: 1019307200 diff --git a/models/fixtures/milestone.yml b/models/fixtures/milestone.yml index 15f422fc3b5c..a9ecb4ee6a6e 100644 --- a/models/fixtures/milestone.yml +++ b/models/fixtures/milestone.yml @@ -21,3 +21,11 @@ content: content3 is_closed: true num_issues: 0 + +- + id: 4 + repo_id: 42 + name: milestone of repo42 + content: content random + is_closed: false + num_issues: 0 diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index db9a6b503d45..f95eb048be38 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -11,4 +11,19 @@ is_draft: false is_prerelease: false is_tag: false - created_unix: 946684800 \ No newline at end of file + created_unix: 946684800 + +- + id: 2 + repo_id: 40 + publisher_id: 2 + tag_name: "v1.1" + lower_tag_name: "v1.1" + target: "master" + title: "testing-release" + sha1: "65f1bf27bc3bf70f64657658635e66094edbcb4d" + num_commits: 10 + is_draft: false + is_prerelease: false + is_tag: false + created_unix: 946684800 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index f7522d303116..5ced38b003fe 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -472,4 +472,10 @@ repo_id: 48 type: 7 config: "{\"ExternalTrackerURL\":\"https://tracker.com\",\"ExternalTrackerFormat\":\"https://tracker.com/{user}/{repo}/issues/{index}\",\"ExternalTrackerStyle\":\"alphanumeric\"}" + created_unix: 946684810 +- + id: 69 + repo_id: 2 + type: 2 + config: "{}" created_unix: 946684810 \ No newline at end of file diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index feec0b5fafb9..c7f4d4d1096a 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -547,7 +547,8 @@ is_private: false num_stars: 0 num_forks: 0 - num_issues: 0 + num_issues: 1 + num_milestones: 1 is_mirror: false - @@ -588,7 +589,7 @@ is_mirror: false status: 0 -- +- id: 46 owner_id: 26 lower_name: repo_external_tracker @@ -600,7 +601,7 @@ is_mirror: false status: 0 -- +- id: 47 owner_id: 26 lower_name: repo_external_tracker_numeric @@ -612,7 +613,7 @@ is_mirror: false status: 0 -- +- id: 48 owner_id: 26 lower_name: repo_external_tracker_alpha diff --git a/models/issue.go b/models/issue.go index 75f7bd818aa6..485be4baef7b 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -239,6 +240,16 @@ func (issue *Issue) loadReactions(e Engine) (err error) { return nil } +func (issue *Issue) loadMilestone(e Engine) (err error) { + if issue.Milestone == nil && issue.MilestoneID > 0 { + issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) + if err != nil && !IsErrMilestoneNotExist(err) { + return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) + } + } + return nil +} + func (issue *Issue) loadAttributes(e Engine) (err error) { if err = issue.loadRepo(e); err != nil { return @@ -252,11 +263,8 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { return } - if issue.Milestone == nil && issue.MilestoneID > 0 { - issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil && !IsErrMilestoneNotExist(err) { - return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) - } + if err = issue.loadMilestone(e); err != nil { + return } if err = issue.loadAssignees(e); err != nil { @@ -296,6 +304,11 @@ func (issue *Issue) LoadAttributes() error { return issue.loadAttributes(x) } +// LoadMilestone load milestone of this issue. +func (issue *Issue) LoadMilestone() error { + return issue.loadMilestone(x) +} + // GetIsRead load the `IsRead` field of the issue func (issue *Issue) GetIsRead(userID int64) error { issueUser := &IssueUser{IssueID: issue.ID, UID: userID} @@ -1568,27 +1581,25 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6 return total, ids, nil } -func updateIssue(e Engine, issue *Issue) error { - _, err := e.ID(issue.ID).AllCols().Update(issue) - if err != nil { - return err - } - return nil -} - -// UpdateIssue updates all fields of given issue. -func UpdateIssue(issue *Issue) error { +// UpdateIssueByAPI updates all allowed fields of given issue. +func UpdateIssueByAPI(issue *Issue) error { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - if err := updateIssue(sess, issue); err != nil { + + if _, err := sess.ID(issue.ID).Cols( + "name", "is_closed", "content", "milestone_id", "priority", + "deadline_unix", "updated_unix", "closed_unix", "is_locked"). + Update(issue); err != nil { return err } + if err := issue.loadPoster(sess); err != nil { return err } + if err := issue.addCrossReferences(sess, issue.Poster, true); err != nil { return err } diff --git a/models/issue_watch.go b/models/issue_watch.go index e42e371a1fae..6144d6cafe1f 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -4,7 +4,9 @@ package models -import "code.gitea.io/gitea/modules/timeutil" +import ( + "code.gitea.io/gitea/modules/timeutil" +) // IssueWatch is connection request for receiving issue notification. type IssueWatch struct { @@ -46,17 +48,18 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { return nil } -// GetIssueWatch returns an issue watch by user and issue +// GetIssueWatch returns all IssueWatch objects from db by user and issue +// the current Web-UI need iw object for watchers AND explicit non-watchers func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) { return getIssueWatch(x, userID, issueID) } +// Return watcher AND explicit non-watcher if entry in db exist func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) { iw = new(IssueWatch) exists, err = e. Where("user_id = ?", userID). And("issue_id = ?", issueID). - And("is_watching = ?", true). Get(iw) return } diff --git a/models/issue_watch_test.go b/models/issue_watch_test.go index 1d0473426e8d..90140591bfac 100644 --- a/models/issue_watch_test.go +++ b/models/issue_watch_test.go @@ -29,9 +29,10 @@ func TestGetIssueWatch(t *testing.T) { assert.True(t, exists) assert.NoError(t, err) - _, exists, err = GetIssueWatch(2, 2) - assert.False(t, exists) + iw, exists, err := GetIssueWatch(2, 2) + assert.True(t, exists) assert.NoError(t, err) + assert.EqualValues(t, false, iw.IsWatching) _, exists, err = GetIssueWatch(3, 1) assert.False(t, exists) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e8bb3f16d460..73c9bc113828 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -288,6 +288,8 @@ var migrations = []Migration{ NewMigration("add user_id prefix to existing user avatar name", renameExistingUserAvatarName), // v116 -> v117 NewMigration("Extend TrackedTimes", extendTrackedTimes), + // v117 -> v118 + NewMigration("Add block on rejected reviews branch protection", addBlockOnRejectedReviews), } // Migrate database to current version diff --git a/models/migrations/v117.go b/models/migrations/v117.go new file mode 100644 index 000000000000..662d6c7b4679 --- /dev/null +++ b/models/migrations/v117.go @@ -0,0 +1,17 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/xorm" +) + +func addBlockOnRejectedReviews(x *xorm.Engine) error { + type ProtectedBranch struct { + BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync2(new(ProtectedBranch)) +} diff --git a/models/pull.go b/models/pull.go index 9a8777aca303..f86892cbfc92 100644 --- a/models/pull.go +++ b/models/pull.go @@ -122,7 +122,7 @@ func (pr *PullRequest) LoadHeadRepo() error { if has, err := x.ID(pr.HeadRepoID).Get(&repo); err != nil { return err } else if !has { - return ErrRepoNotExist{ID: pr.BaseRepoID} + return ErrRepoNotExist{ID: pr.HeadRepoID} } pr.HeadRepo = &repo } diff --git a/models/repo_list.go b/models/repo_list.go index 34fac8b05531..7b48834dba9e 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -121,7 +121,8 @@ type SearchRepoOptions struct { StarredByID int64 Page int IsProfile bool - AllPublic bool // Include also all public repositories + AllPublic bool // Include also all public repositories of users and public organisations + AllLimited bool // Include also all public repositories of limited organisations PageSize int // Can be smaller than or equal to setting.ExplorePagingNum // None -> include collaborative AND non-collaborative // True -> include just collaborative @@ -228,7 +229,11 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { } if opts.AllPublic { - accessCond = accessCond.Or(builder.Eq{"is_private": false}) + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) + } + + if opts.AllLimited { + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) } cond = cond.And(accessCond) diff --git a/models/repo_list_test.go b/models/repo_list_test.go index a1eed18b83ab..07f84207e200 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -177,8 +177,8 @@ func TestSearchRepository(t *testing.T) { opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, count: 25}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, Template: util.OptionalBoolFalse}, - count: 31}, + opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, + count: 30}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, count: 15}, diff --git a/models/repo_permission.go b/models/repo_permission.go index 782b195629c9..79d7dd012b66 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -369,3 +369,23 @@ func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) { func HasAccess(userID int64, repo *Repository) (bool, error) { return hasAccess(x, userID, repo) } + +// FilterOutRepoIdsWithoutUnitAccess filter out repos where user has no access to repositories +func FilterOutRepoIdsWithoutUnitAccess(u *User, repoIDs []int64, units ...UnitType) ([]int64, error) { + i := 0 + for _, rID := range repoIDs { + repo, err := GetRepositoryByID(rID) + if err != nil { + return nil, err + } + perm, err := GetUserRepoPermission(repo, u) + if err != nil { + return nil, err + } + if perm.CanReadAny(units...) { + repoIDs[i] = rID + i++ + } + } + return repoIDs[:i], nil +} diff --git a/models/user.go b/models/user.go index e832c2ed519b..f2c0a1861e5a 100644 --- a/models/user.go +++ b/models/user.go @@ -638,19 +638,20 @@ func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { var ids []int64 - sess := x.Table("repository"). + if err := x.Table("repository"). Cols("repository.id"). Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). - Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true) + Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true). + Where("team_user.uid = ?", u.ID). + GroupBy("repository.id").Find(&ids); err != nil { + return nil, err + } if len(units) > 0 { - sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id") - sess = sess.In("team_unit.type", units) + return FilterOutRepoIdsWithoutUnitAccess(u, ids, units...) } - return ids, sess. - Where("team_user.uid = ?", u.ID). - GroupBy("repository.id").Find(&ids) + return ids, nil } // GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations @@ -791,6 +792,14 @@ func NewGhostUser() *User { } } +// IsGhost check if user is fake user for a deleted account +func (u *User) IsGhost() bool { + if u == nil { + return false + } + return u.ID == -1 && u.Name == "Ghost" +} + var ( reservedUsernames = []string{ "attachments", diff --git a/models/wiki.go b/models/wiki.go index 8b63716afa1f..32a0cc1627a6 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -5,53 +5,12 @@ package models import ( - "fmt" - "net/url" - "os" "path/filepath" "strings" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/sync" - "github.com/unknwon/com" ) -var ( - reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"} - wikiWorkingPool = sync.NewExclusivePool() -) - -// NormalizeWikiName normalizes a wiki name -func NormalizeWikiName(name string) string { - return strings.Replace(name, "-", " ", -1) -} - -// WikiNameToSubURL converts a wiki name to its corresponding sub-URL. -func WikiNameToSubURL(name string) string { - return url.QueryEscape(strings.Replace(name, " ", "-", -1)) -} - -// WikiNameToFilename converts a wiki name to its corresponding filename. -func WikiNameToFilename(name string) string { - name = strings.Replace(name, " ", "-", -1) - return url.QueryEscape(name) + ".md" -} - -// WikiFilenameToName converts a wiki filename to its corresponding page name. -func WikiFilenameToName(filename string) (string, error) { - if !strings.HasSuffix(filename, ".md") { - return "", ErrWikiInvalidFileName{filename} - } - basename := filename[:len(filename)-3] - unescaped, err := url.QueryUnescape(basename) - if err != nil { - return "", err - } - return NormalizeWikiName(unescaped), nil -} - // WikiCloneLink returns clone URLs of repository wiki. func (repo *Repository) WikiCloneLink() *CloneLink { return repo.cloneLink(x, true) @@ -71,275 +30,3 @@ func (repo *Repository) WikiPath() string { func (repo *Repository) HasWiki() bool { return com.IsDir(repo.WikiPath()) } - -// InitWiki initializes a wiki for repository, -// it does nothing when repository already has wiki. -func (repo *Repository) InitWiki() error { - if repo.HasWiki() { - return nil - } - - if err := git.InitRepository(repo.WikiPath(), true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repo.WikiPath()); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - return nil -} - -// nameAllowed checks if a wiki name is allowed -func nameAllowed(name string) error { - for _, reservedName := range reservedWikiNames { - if name == reservedName { - return ErrWikiReservedName{name} - } - } - return nil -} - -// updateWikiPage adds a new page to the repository wiki. -func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, content, message string, isNew bool) (err error) { - if err = nameAllowed(newWikiName); err != nil { - return err - } - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.InitWiki(); err != nil { - return fmt.Errorf("InitWiki: %v", err) - } - - hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") - - basePath, err := CreateTemporaryPath("update-wiki") - if err != nil { - return err - } - defer func() { - if err := RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() - - cloneOpts := git.CloneRepoOptions{ - Bare: true, - Shared: true, - } - - if hasMasterBranch { - cloneOpts.Branch = "master" - } - - if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil { - log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) - return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) - } - - gitRepo, err := git.OpenRepository(basePath) - if err != nil { - log.Error("Unable to open temporary repository: %s (%v)", basePath, err) - return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) - } - defer gitRepo.Close() - - if hasMasterBranch { - if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { - log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) - return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) - } - } - - newWikiPath := WikiNameToFilename(newWikiName) - if isNew { - filesInIndex, err := gitRepo.LsFiles(newWikiPath) - if err != nil { - log.Error("%v", err) - return err - } - for _, file := range filesInIndex { - if file == newWikiPath { - return ErrWikiAlreadyExist{newWikiPath} - } - } - } else { - oldWikiPath := WikiNameToFilename(oldWikiName) - filesInIndex, err := gitRepo.LsFiles(oldWikiPath) - if err != nil { - log.Error("%v", err) - return err - } - found := false - for _, file := range filesInIndex { - if file == oldWikiPath { - found = true - break - } - } - if found { - err := gitRepo.RemoveFilesFromIndex(oldWikiPath) - if err != nil { - log.Error("%v", err) - return err - } - } - } - - // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here - - objectHash, err := gitRepo.HashObject(strings.NewReader(content)) - if err != nil { - log.Error("%v", err) - return err - } - - if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { - log.Error("%v", err) - return err - } - - tree, err := gitRepo.WriteTree() - if err != nil { - log.Error("%v", err) - return err - } - - commitTreeOpts := git.CommitTreeOpts{ - Message: message, - } - - sign, signingKey := repo.SignWikiCommit(doer) - if sign { - commitTreeOpts.KeyID = signingKey - } else { - commitTreeOpts.NoGPGSign = true - } - if hasMasterBranch { - commitTreeOpts.Parents = []string{"HEAD"} - } - commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) - if err != nil { - log.Error("%v", err) - return err - } - - if err := git.Push(basePath, git.PushOptions{ - Remote: "origin", - Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), - Env: FullPushingEnvironment( - doer, - doer, - repo, - repo.Name+".wiki", - 0, - ), - }); err != nil { - log.Error("%v", err) - return fmt.Errorf("Push: %v", err) - } - - return nil -} - -// AddWikiPage adds a new wiki page with a given wikiPath. -func (repo *Repository) AddWikiPage(doer *User, wikiName, content, message string) error { - return repo.updateWikiPage(doer, "", wikiName, content, message, true) -} - -// EditWikiPage updates a wiki page identified by its wikiPath, -// optionally also changing wikiPath. -func (repo *Repository) EditWikiPage(doer *User, oldWikiName, newWikiName, content, message string) error { - return repo.updateWikiPage(doer, oldWikiName, newWikiName, content, message, false) -} - -// DeleteWikiPage deletes a wiki page identified by its path. -func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) { - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - - if err = repo.InitWiki(); err != nil { - return fmt.Errorf("InitWiki: %v", err) - } - - basePath, err := CreateTemporaryPath("update-wiki") - if err != nil { - return err - } - defer func() { - if err := RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() - - if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{ - Bare: true, - Shared: true, - Branch: "master", - }); err != nil { - log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) - return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) - } - - gitRepo, err := git.OpenRepository(basePath) - if err != nil { - log.Error("Unable to open temporary repository: %s (%v)", basePath, err) - return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) - } - defer gitRepo.Close() - - if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { - log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) - return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) - } - - wikiPath := WikiNameToFilename(wikiName) - filesInIndex, err := gitRepo.LsFiles(wikiPath) - found := false - for _, file := range filesInIndex { - if file == wikiPath { - found = true - break - } - } - if found { - err := gitRepo.RemoveFilesFromIndex(wikiPath) - if err != nil { - return err - } - } else { - return os.ErrNotExist - } - - // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here - - tree, err := gitRepo.WriteTree() - if err != nil { - return err - } - message := "Delete page '" + wikiName + "'" - commitTreeOpts := git.CommitTreeOpts{ - Message: message, - Parents: []string{"HEAD"}, - } - - sign, signingKey := repo.SignWikiCommit(doer) - if sign { - commitTreeOpts.KeyID = signingKey - } else { - commitTreeOpts.NoGPGSign = true - } - - commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) - if err != nil { - return err - } - - if err := git.Push(basePath, git.PushOptions{ - Remote: "origin", - Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), - Env: PushingEnvironment(doer, repo), - }); err != nil { - return fmt.Errorf("Push: %v", err) - } - - return nil -} diff --git a/models/wiki_test.go b/models/wiki_test.go index 37c0a86635ea..244c4f2553b2 100644 --- a/models/wiki_test.go +++ b/models/wiki_test.go @@ -8,100 +8,11 @@ import ( "path/filepath" "testing" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) -func TestNormalizeWikiName(t *testing.T) { - type test struct { - Expected string - WikiName string - } - for _, test := range []test{ - {"wiki name", "wiki name"}, - {"wiki name", "wiki-name"}, - {"name with/slash", "name with/slash"}, - {"name with%percent", "name-with%percent"}, - {"%2F", "%2F"}, - } { - assert.Equal(t, test.Expected, NormalizeWikiName(test.WikiName)) - } -} - -func TestWikiNameToFilename(t *testing.T) { - type test struct { - Expected string - WikiName string - } - for _, test := range []test{ - {"wiki-name.md", "wiki name"}, - {"wiki-name.md", "wiki-name"}, - {"name-with%2Fslash.md", "name with/slash"}, - {"name-with%25percent.md", "name with%percent"}, - } { - assert.Equal(t, test.Expected, WikiNameToFilename(test.WikiName)) - } -} - -func TestWikiNameToSubURL(t *testing.T) { - type test struct { - Expected string - WikiName string - } - for _, test := range []test{ - {"wiki-name", "wiki name"}, - {"wiki-name", "wiki-name"}, - {"name-with%2Fslash", "name with/slash"}, - {"name-with%25percent", "name with%percent"}, - } { - assert.Equal(t, test.Expected, WikiNameToSubURL(test.WikiName)) - } -} - -func TestWikiFilenameToName(t *testing.T) { - type test struct { - Expected string - Filename string - } - for _, test := range []test{ - {"hello world", "hello-world.md"}, - {"symbols/?*", "symbols%2F%3F%2A.md"}, - } { - name, err := WikiFilenameToName(test.Filename) - assert.NoError(t, err) - assert.Equal(t, test.Expected, name) - } - for _, badFilename := range []string{ - "nofileextension", - "wrongfileextension.txt", - } { - _, err := WikiFilenameToName(badFilename) - assert.Error(t, err) - assert.True(t, IsErrWikiInvalidFileName(err)) - } - _, err := WikiFilenameToName("badescaping%%.md") - assert.Error(t, err) - assert.False(t, IsErrWikiInvalidFileName(err)) -} - -func TestWikiNameToFilenameToName(t *testing.T) { - // converting from wiki name to filename, then back to wiki name should - // return the original (normalized) name - for _, name := range []string{ - "wiki-name", - "wiki name", - "wiki name with/slash", - "$$$%%%^^&&!@#$(),.<>", - } { - filename := WikiNameToFilename(name) - resultName, err := WikiFilenameToName(filename) - assert.NoError(t, err) - assert.Equal(t, NormalizeWikiName(name), resultName) - } -} - func TestRepository_WikiCloneLink(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) @@ -131,107 +42,3 @@ func TestRepository_HasWiki(t *testing.T) { repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) assert.False(t, repo2.HasWiki()) } - -func TestRepository_InitWiki(t *testing.T) { - PrepareTestEnv(t) - // repo1 already has a wiki - repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - assert.NoError(t, repo1.InitWiki()) - - // repo2 does not already have a wiki - repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) - assert.NoError(t, repo2.InitWiki()) - assert.True(t, repo2.HasWiki()) -} - -func TestRepository_AddWikiPage(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - const wikiContent = "This is the wiki content" - const commitMsg = "Commit message" - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - for _, wikiName := range []string{ - "Another page", - "Here's a and a/slash", - } { - wikiName := wikiName - t.Run("test wiki exist: "+wikiName, func(t *testing.T) { - t.Parallel() - assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg)) - // Now need to show that the page has been added: - gitRepo, err := git.OpenRepository(repo.WikiPath()) - assert.NoError(t, err) - defer gitRepo.Close() - masterTree, err := gitRepo.GetTree("master") - assert.NoError(t, err) - wikiPath := WikiNameToFilename(wikiName) - entry, err := masterTree.GetTreeEntryByPath(wikiPath) - assert.NoError(t, err) - assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName) - }) - } - - t.Run("check wiki already exist", func(t *testing.T) { - t.Parallel() - // test for already-existing wiki name - err := repo.AddWikiPage(doer, "Home", wikiContent, commitMsg) - assert.Error(t, err) - assert.True(t, IsErrWikiAlreadyExist(err)) - }) - - t.Run("check wiki reserved name", func(t *testing.T) { - t.Parallel() - // test for reserved wiki name - err := repo.AddWikiPage(doer, "_edit", wikiContent, commitMsg) - assert.Error(t, err) - assert.True(t, IsErrWikiReservedName(err)) - }) -} - -func TestRepository_EditWikiPage(t *testing.T) { - const newWikiContent = "This is the new content" - const commitMsg = "Commit message" - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - for _, newWikiName := range []string{ - "Home", // same name as before - "New home", - "New/name/with/slashes", - } { - PrepareTestEnv(t) - assert.NoError(t, repo.EditWikiPage(doer, "Home", newWikiName, newWikiContent, commitMsg)) - - // Now need to show that the page has been added: - gitRepo, err := git.OpenRepository(repo.WikiPath()) - assert.NoError(t, err) - masterTree, err := gitRepo.GetTree("master") - assert.NoError(t, err) - wikiPath := WikiNameToFilename(newWikiName) - entry, err := masterTree.GetTreeEntryByPath(wikiPath) - assert.NoError(t, err) - assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName) - - if newWikiName != "Home" { - _, err := masterTree.GetTreeEntryByPath("Home.md") - assert.Error(t, err) - } - gitRepo.Close() - } -} - -func TestRepository_DeleteWikiPage(t *testing.T) { - PrepareTestEnv(t) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - assert.NoError(t, repo.DeleteWikiPage(doer, "Home")) - - // Now need to show that the page has been added: - gitRepo, err := git.OpenRepository(repo.WikiPath()) - assert.NoError(t, err) - defer gitRepo.Close() - masterTree, err := gitRepo.GetTree("master") - assert.NoError(t, err) - wikiPath := WikiNameToFilename("Home") - _, err = masterTree.GetTreeEntryByPath(wikiPath) - assert.Error(t, err) -} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 26ed2bb69292..c87549af92a2 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -171,6 +171,7 @@ type ProtectBranchForm struct { EnableApprovalsWhitelist bool ApprovalsWhitelistUsers string ApprovalsWhitelistTeams string + BlockOnRejectedReviews bool } // Validate validates the fields diff --git a/modules/indexer/issues/db.go b/modules/indexer/issues/db.go index a758cfeaeebd..d0cca4fd1808 100644 --- a/modules/indexer/issues/db.go +++ b/modules/indexer/issues/db.go @@ -25,6 +25,10 @@ func (db *DBIndexer) Delete(ids ...int64) error { return nil } +// Close dummy function +func (db *DBIndexer) Close() { +} + // Search dummy function func (db *DBIndexer) Search(kw string, repoIDs []int64, limit, start int) (*SearchResult, error) { total, ids, err := models.SearchIssueIDsByKeyword(kw, repoIDs, limit, start) diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index ebcd3f68dd51..894f37a96315 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -5,12 +5,16 @@ package issues import ( + "context" + "fmt" + "os" "sync" "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -44,12 +48,14 @@ type Indexer interface { Index(issue []*IndexerData) error Delete(ids ...int64) error Search(kw string, repoIDs []int64, limit, start int) (*SearchResult, error) + Close() } type indexerHolder struct { - indexer Indexer - mutex sync.RWMutex - cond *sync.Cond + indexer Indexer + mutex sync.RWMutex + cond *sync.Cond + cancelled bool } func newIndexerHolder() *indexerHolder { @@ -58,6 +64,13 @@ func newIndexerHolder() *indexerHolder { return h } +func (h *indexerHolder) cancel() { + h.mutex.Lock() + defer h.mutex.Unlock() + h.cancelled = true + h.cond.Broadcast() +} + func (h *indexerHolder) set(indexer Indexer) { h.mutex.Lock() defer h.mutex.Unlock() @@ -68,16 +81,15 @@ func (h *indexerHolder) set(indexer Indexer) { func (h *indexerHolder) get() Indexer { h.mutex.RLock() defer h.mutex.RUnlock() - if h.indexer == nil { + if h.indexer == nil && !h.cancelled { h.cond.Wait() } return h.indexer } var ( - issueIndexerChannel = make(chan *IndexerData, setting.Indexer.UpdateQueueLength) // issueIndexerQueue queue of issue ids to be updated - issueIndexerQueue Queue + issueIndexerQueue queue.Queue holder = newIndexerHolder() ) @@ -85,90 +97,99 @@ var ( // all issue index done. func InitIssueIndexer(syncReindex bool) { waitChannel := make(chan time.Duration) + + // Create the Queue + switch setting.Indexer.IssueType { + case "bleve": + handler := func(data ...queue.Data) { + indexer := holder.get() + if indexer == nil { + log.Error("Issue indexer handler: unable to get indexer!") + return + } + + iData := make([]*IndexerData, 0, setting.Indexer.IssueQueueBatchNumber) + for _, datum := range data { + indexerData, ok := datum.(*IndexerData) + if !ok { + log.Error("Unable to process provided datum: %v - not possible to cast to IndexerData", datum) + continue + } + log.Trace("IndexerData Process: %d %v %t", indexerData.ID, indexerData.IDs, indexerData.IsDelete) + if indexerData.IsDelete { + _ = indexer.Delete(indexerData.IDs...) + continue + } + iData = append(iData, indexerData) + } + if err := indexer.Index(iData); err != nil { + log.Error("Error whilst indexing: %v Error: %v", iData, err) + } + } + + issueIndexerQueue = queue.CreateQueue("issue_indexer", handler, &IndexerData{}) + + if issueIndexerQueue == nil { + log.Fatal("Unable to create issue indexer queue") + } + default: + issueIndexerQueue = &queue.DummyQueue{} + } + + // Create the Indexer go func() { start := time.Now() - log.Info("Initializing Issue Indexer") + log.Info("PID %d: Initializing Issue Indexer: %s", os.Getpid(), setting.Indexer.IssueType) var populate bool - var dummyQueue bool switch setting.Indexer.IssueType { case "bleve": - issueIndexer := NewBleveIndexer(setting.Indexer.IssuePath) - exist, err := issueIndexer.Init() - if err != nil { - log.Fatal("Unable to initialize Bleve Issue Indexer: %v", err) - } - populate = !exist - holder.set(issueIndexer) + graceful.GetManager().RunWithShutdownFns(func(_, atTerminate func(context.Context, func())) { + issueIndexer := NewBleveIndexer(setting.Indexer.IssuePath) + exist, err := issueIndexer.Init() + if err != nil { + holder.cancel() + log.Fatal("Unable to initialize Bleve Issue Indexer: %v", err) + } + populate = !exist + holder.set(issueIndexer) + atTerminate(context.Background(), func() { + log.Debug("Closing issue indexer") + issueIndexer := holder.get() + if issueIndexer != nil { + issueIndexer.Close() + } + log.Info("PID: %d Issue Indexer closed", os.Getpid()) + }) + log.Debug("Created Bleve Indexer") + }) case "db": issueIndexer := &DBIndexer{} holder.set(issueIndexer) - dummyQueue = true default: + holder.cancel() log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType) } - if dummyQueue { - issueIndexerQueue = &DummyQueue{} - } else { - var err error - switch setting.Indexer.IssueQueueType { - case setting.LevelQueueType: - issueIndexerQueue, err = NewLevelQueue( - holder.get(), - setting.Indexer.IssueQueueDir, - setting.Indexer.IssueQueueBatchNumber) - if err != nil { - log.Fatal( - "Unable create level queue for issue queue dir: %s batch number: %d : %v", - setting.Indexer.IssueQueueDir, - setting.Indexer.IssueQueueBatchNumber, - err) - } - case setting.ChannelQueueType: - issueIndexerQueue = NewChannelQueue(holder.get(), setting.Indexer.IssueQueueBatchNumber) - case setting.RedisQueueType: - addrs, pass, idx, err := parseConnStr(setting.Indexer.IssueQueueConnStr) - if err != nil { - log.Fatal("Unable to parse connection string for RedisQueueType: %s : %v", - setting.Indexer.IssueQueueConnStr, - err) - } - issueIndexerQueue, err = NewRedisQueue(addrs, pass, idx, holder.get(), setting.Indexer.IssueQueueBatchNumber) - if err != nil { - log.Fatal("Unable to create RedisQueue: %s : %v", - setting.Indexer.IssueQueueConnStr, - err) - } - default: - log.Fatal("Unsupported indexer queue type: %v", - setting.Indexer.IssueQueueType) - } - - go func() { - err = issueIndexerQueue.Run() - if err != nil { - log.Error("issueIndexerQueue.Run: %v", err) - } - }() - } - - go func() { - for data := range issueIndexerChannel { - _ = issueIndexerQueue.Push(data) - } - }() + // Start processing the queue + go graceful.GetManager().RunWithShutdownFns(issueIndexerQueue.Run) + // Populate the index if populate { if syncReindex { - populateIssueIndexer() + graceful.GetManager().RunWithShutdownContext(populateIssueIndexer) } else { - go populateIssueIndexer() + go graceful.GetManager().RunWithShutdownContext(populateIssueIndexer) } } waitChannel <- time.Since(start) + close(waitChannel) }() + if syncReindex { - <-waitChannel + select { + case <-waitChannel: + case <-graceful.GetManager().IsShutdown(): + } } else if setting.Indexer.StartupTimeout > 0 { go func() { timeout := setting.Indexer.StartupTimeout @@ -178,7 +199,12 @@ func InitIssueIndexer(syncReindex bool) { select { case duration := <-waitChannel: log.Info("Issue Indexer Initialization took %v", duration) + case <-graceful.GetManager().IsShutdown(): + log.Warn("Shutdown occurred before issue index initialisation was complete") case <-time.After(timeout): + if shutdownable, ok := issueIndexerQueue.(queue.Shutdownable); ok { + shutdownable.Terminate() + } log.Fatal("Issue Indexer Initialization timed-out after: %v", timeout) } }() @@ -186,8 +212,14 @@ func InitIssueIndexer(syncReindex bool) { } // populateIssueIndexer populate the issue indexer with issue data -func populateIssueIndexer() { +func populateIssueIndexer(ctx context.Context) { for page := 1; ; page++ { + select { + case <-ctx.Done(): + log.Warn("Issue Indexer population shutdown before completion") + return + default: + } repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ Page: page, PageSize: models.RepositoryListDefaultPageSize, @@ -200,10 +232,17 @@ func populateIssueIndexer() { continue } if len(repos) == 0 { + log.Debug("Issue Indexer population complete") return } for _, repo := range repos { + select { + case <-ctx.Done(): + log.Info("Issue Indexer population shutdown before completion") + return + default: + } UpdateRepoIndexer(repo) } } @@ -237,13 +276,17 @@ func UpdateIssueIndexer(issue *models.Issue) { comments = append(comments, comment.Content) } } - issueIndexerChannel <- &IndexerData{ + indexerData := &IndexerData{ ID: issue.ID, RepoID: issue.RepoID, Title: issue.Title, Content: issue.Content, Comments: comments, } + log.Debug("Adding to channel: %v", indexerData) + if err := issueIndexerQueue.Push(indexerData); err != nil { + log.Error("Unable to push to issue indexer: %v: Error: %v", indexerData, err) + } } // DeleteRepoIssueIndexer deletes repo's all issues indexes @@ -258,17 +301,25 @@ func DeleteRepoIssueIndexer(repo *models.Repository) { if len(ids) == 0 { return } - - issueIndexerChannel <- &IndexerData{ + indexerData := &IndexerData{ IDs: ids, IsDelete: true, } + if err := issueIndexerQueue.Push(indexerData); err != nil { + log.Error("Unable to push to issue indexer: %v: Error: %v", indexerData, err) + } } // SearchIssuesByKeyword search issue ids by keywords and repo id func SearchIssuesByKeyword(repoIDs []int64, keyword string) ([]int64, error) { var issueIDs []int64 - res, err := holder.get().Search(keyword, repoIDs, 1000, 0) + indexer := holder.get() + + if indexer == nil { + log.Error("SearchIssuesByKeyword(): unable to get indexer!") + return nil, fmt.Errorf("unable to get issue indexer") + } + res, err := indexer.Search(keyword, repoIDs, 1000, 0) if err != nil { return nil, err } diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index ca7ba29703fe..4028a6c8b518 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -15,6 +15,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" + "gopkg.in/ini.v1" + "github.com/stretchr/testify/assert" ) @@ -24,6 +26,7 @@ func TestMain(m *testing.M) { func TestBleveSearchIssues(t *testing.T) { assert.NoError(t, models.PrepareTestDatabase()) + setting.Cfg = ini.Empty() tmpIndexerDir, err := ioutil.TempDir("", "issues-indexer") if err != nil { @@ -41,6 +44,7 @@ func TestBleveSearchIssues(t *testing.T) { }() setting.Indexer.IssueType = "bleve" + setting.NewQueueService() InitIssueIndexer(true) defer func() { indexer := holder.get() diff --git a/modules/indexer/issues/queue.go b/modules/indexer/issues/queue.go deleted file mode 100644 index f93e5c47a40a..000000000000 --- a/modules/indexer/issues/queue.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package issues - -// Queue defines an interface to save an issue indexer queue -type Queue interface { - Run() error - Push(*IndexerData) error -} - -// DummyQueue represents an empty queue -type DummyQueue struct { -} - -// Run starts to run the queue -func (b *DummyQueue) Run() error { - return nil -} - -// Push pushes data to indexer -func (b *DummyQueue) Push(*IndexerData) error { - return nil -} diff --git a/modules/indexer/issues/queue_channel.go b/modules/indexer/issues/queue_channel.go deleted file mode 100644 index b6458d3eb53d..000000000000 --- a/modules/indexer/issues/queue_channel.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package issues - -import ( - "time" - - "code.gitea.io/gitea/modules/setting" -) - -// ChannelQueue implements -type ChannelQueue struct { - queue chan *IndexerData - indexer Indexer - batchNumber int -} - -// NewChannelQueue create a memory channel queue -func NewChannelQueue(indexer Indexer, batchNumber int) *ChannelQueue { - return &ChannelQueue{ - queue: make(chan *IndexerData, setting.Indexer.UpdateQueueLength), - indexer: indexer, - batchNumber: batchNumber, - } -} - -// Run starts to run the queue -func (c *ChannelQueue) Run() error { - var i int - var datas = make([]*IndexerData, 0, c.batchNumber) - for { - select { - case data := <-c.queue: - if data.IsDelete { - _ = c.indexer.Delete(data.IDs...) - continue - } - - datas = append(datas, data) - if len(datas) >= c.batchNumber { - _ = c.indexer.Index(datas) - // TODO: save the point - datas = make([]*IndexerData, 0, c.batchNumber) - } - case <-time.After(time.Millisecond * 100): - i++ - if i >= 3 && len(datas) > 0 { - _ = c.indexer.Index(datas) - // TODO: save the point - datas = make([]*IndexerData, 0, c.batchNumber) - } - } - } -} - -// Push will push the indexer data to queue -func (c *ChannelQueue) Push(data *IndexerData) error { - c.queue <- data - return nil -} diff --git a/modules/indexer/issues/queue_disk.go b/modules/indexer/issues/queue_disk.go deleted file mode 100644 index d6187f2acbd0..000000000000 --- a/modules/indexer/issues/queue_disk.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package issues - -import ( - "encoding/json" - "time" - - "code.gitea.io/gitea/modules/log" - - "gitea.com/lunny/levelqueue" -) - -var ( - _ Queue = &LevelQueue{} -) - -// LevelQueue implements a disk library queue -type LevelQueue struct { - indexer Indexer - queue *levelqueue.Queue - batchNumber int -} - -// NewLevelQueue creates a ledis local queue -func NewLevelQueue(indexer Indexer, dataDir string, batchNumber int) (*LevelQueue, error) { - queue, err := levelqueue.Open(dataDir) - if err != nil { - return nil, err - } - - return &LevelQueue{ - indexer: indexer, - queue: queue, - batchNumber: batchNumber, - }, nil -} - -// Run starts to run the queue -func (l *LevelQueue) Run() error { - var i int - var datas = make([]*IndexerData, 0, l.batchNumber) - for { - i++ - if len(datas) > l.batchNumber || (len(datas) > 0 && i > 3) { - _ = l.indexer.Index(datas) - datas = make([]*IndexerData, 0, l.batchNumber) - i = 0 - continue - } - - bs, err := l.queue.RPop() - if err != nil { - if err != levelqueue.ErrNotFound { - log.Error("RPop: %v", err) - } - time.Sleep(time.Millisecond * 100) - continue - } - - if len(bs) == 0 { - time.Sleep(time.Millisecond * 100) - continue - } - - var data IndexerData - err = json.Unmarshal(bs, &data) - if err != nil { - log.Error("Unmarshal: %v", err) - time.Sleep(time.Millisecond * 100) - continue - } - - log.Trace("LevelQueue: task found: %#v", data) - - if data.IsDelete { - if data.ID > 0 { - if err = l.indexer.Delete(data.ID); err != nil { - log.Error("indexer.Delete: %v", err) - } - } else if len(data.IDs) > 0 { - if err = l.indexer.Delete(data.IDs...); err != nil { - log.Error("indexer.Delete: %v", err) - } - } - time.Sleep(time.Millisecond * 10) - continue - } - - datas = append(datas, &data) - time.Sleep(time.Millisecond * 10) - } -} - -// Push will push the indexer data to queue -func (l *LevelQueue) Push(data *IndexerData) error { - bs, err := json.Marshal(data) - if err != nil { - return err - } - return l.queue.LPush(bs) -} diff --git a/modules/indexer/issues/queue_redis.go b/modules/indexer/issues/queue_redis.go deleted file mode 100644 index 0344d3c87a0f..000000000000 --- a/modules/indexer/issues/queue_redis.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package issues - -import ( - "encoding/json" - "errors" - "strconv" - "strings" - "time" - - "code.gitea.io/gitea/modules/log" - - "github.com/go-redis/redis" -) - -var ( - _ Queue = &RedisQueue{} -) - -type redisClient interface { - RPush(key string, args ...interface{}) *redis.IntCmd - LPop(key string) *redis.StringCmd - Ping() *redis.StatusCmd -} - -// RedisQueue redis queue -type RedisQueue struct { - client redisClient - queueName string - indexer Indexer - batchNumber int -} - -func parseConnStr(connStr string) (addrs, password string, dbIdx int, err error) { - fields := strings.Fields(connStr) - for _, f := range fields { - items := strings.SplitN(f, "=", 2) - if len(items) < 2 { - continue - } - switch strings.ToLower(items[0]) { - case "addrs": - addrs = items[1] - case "password": - password = items[1] - case "db": - dbIdx, err = strconv.Atoi(items[1]) - if err != nil { - return - } - } - } - return -} - -// NewRedisQueue creates single redis or cluster redis queue -func NewRedisQueue(addrs string, password string, dbIdx int, indexer Indexer, batchNumber int) (*RedisQueue, error) { - dbs := strings.Split(addrs, ",") - var queue = RedisQueue{ - queueName: "issue_indexer_queue", - indexer: indexer, - batchNumber: batchNumber, - } - if len(dbs) == 0 { - return nil, errors.New("no redis host found") - } else if len(dbs) == 1 { - queue.client = redis.NewClient(&redis.Options{ - Addr: strings.TrimSpace(dbs[0]), // use default Addr - Password: password, // no password set - DB: dbIdx, // use default DB - }) - } else { - queue.client = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: dbs, - }) - } - if err := queue.client.Ping().Err(); err != nil { - return nil, err - } - return &queue, nil -} - -// Run runs the redis queue -func (r *RedisQueue) Run() error { - var i int - var datas = make([]*IndexerData, 0, r.batchNumber) - for { - bs, err := r.client.LPop(r.queueName).Bytes() - if err != nil && err != redis.Nil { - log.Error("LPop faile: %v", err) - time.Sleep(time.Millisecond * 100) - continue - } - - i++ - if len(datas) > r.batchNumber || (len(datas) > 0 && i > 3) { - _ = r.indexer.Index(datas) - datas = make([]*IndexerData, 0, r.batchNumber) - i = 0 - } - - if len(bs) == 0 { - time.Sleep(time.Millisecond * 100) - continue - } - - var data IndexerData - err = json.Unmarshal(bs, &data) - if err != nil { - log.Error("Unmarshal: %v", err) - time.Sleep(time.Millisecond * 100) - continue - } - - log.Trace("RedisQueue: task found: %#v", data) - - if data.IsDelete { - if data.ID > 0 { - if err = r.indexer.Delete(data.ID); err != nil { - log.Error("indexer.Delete: %v", err) - } - } else if len(data.IDs) > 0 { - if err = r.indexer.Delete(data.IDs...); err != nil { - log.Error("indexer.Delete: %v", err) - } - } - time.Sleep(time.Millisecond * 100) - continue - } - - datas = append(datas, &data) - time.Sleep(time.Millisecond * 100) - } -} - -// Push implements Queue -func (r *RedisQueue) Push(data *IndexerData) error { - bs, err := json.Marshal(data) - if err != nil { - return err - } - return r.client.RPush(r.queueName, bs).Err() -} diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index 5148434dca23..6cc6fda14bbb 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -53,6 +53,7 @@ func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) { func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) { var actionType models.ActionType + issue.Content = "" if issue.IsPull { if isClosed { actionType = models.ActionClosePullRequest @@ -105,7 +106,7 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode log.Error("pr.LoadIssue: %v", err) return } - + pr.Issue.Content = "" if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest); err != nil { log.Error("MailParticipants: %v", err) } diff --git a/modules/queue/manager.go b/modules/queue/manager.go new file mode 100644 index 000000000000..88b264484867 --- /dev/null +++ b/modules/queue/manager.go @@ -0,0 +1,270 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "sort" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" +) + +var manager *Manager + +// Manager is a queue manager +type Manager struct { + mutex sync.Mutex + + counter int64 + Queues map[int64]*ManagedQueue +} + +// ManagedQueue represents a working queue inheriting from Gitea. +type ManagedQueue struct { + mutex sync.Mutex + QID int64 + Queue Queue + Type Type + Name string + Configuration interface{} + ExemplarType string + Pool ManagedPool + counter int64 + PoolWorkers map[int64]*PoolWorkers +} + +// ManagedPool is a simple interface to get certain details from a worker pool +type ManagedPool interface { + AddWorkers(number int, timeout time.Duration) context.CancelFunc + NumberOfWorkers() int + MaxNumberOfWorkers() int + SetMaxNumberOfWorkers(int) + BoostTimeout() time.Duration + BlockTimeout() time.Duration + BoostWorkers() int + SetSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) +} + +// ManagedQueueList implements the sort.Interface +type ManagedQueueList []*ManagedQueue + +// PoolWorkers represents a working queue inheriting from Gitea. +type PoolWorkers struct { + PID int64 + Workers int + Start time.Time + Timeout time.Time + HasTimeout bool + Cancel context.CancelFunc +} + +// PoolWorkersList implements the sort.Interface +type PoolWorkersList []*PoolWorkers + +func init() { + _ = GetManager() +} + +// GetManager returns a Manager and initializes one as singleton if there's none yet +func GetManager() *Manager { + if manager == nil { + manager = &Manager{ + Queues: make(map[int64]*ManagedQueue), + } + } + return manager +} + +// Add adds a queue to this manager +func (m *Manager) Add(queue Queue, + t Type, + configuration, + exemplar interface{}, + pool ManagedPool) int64 { + + cfg, _ := json.Marshal(configuration) + mq := &ManagedQueue{ + Queue: queue, + Type: t, + Configuration: string(cfg), + ExemplarType: reflect.TypeOf(exemplar).String(), + PoolWorkers: make(map[int64]*PoolWorkers), + Pool: pool, + } + m.mutex.Lock() + m.counter++ + mq.QID = m.counter + mq.Name = fmt.Sprintf("queue-%d", mq.QID) + if named, ok := queue.(Named); ok { + mq.Name = named.Name() + } + m.Queues[mq.QID] = mq + m.mutex.Unlock() + log.Trace("Queue Manager registered: %s (QID: %d)", mq.Name, mq.QID) + return mq.QID +} + +// Remove a queue from the Manager +func (m *Manager) Remove(qid int64) { + m.mutex.Lock() + delete(m.Queues, qid) + m.mutex.Unlock() + log.Trace("Queue Manager removed: QID: %d", qid) + +} + +// GetManagedQueue by qid +func (m *Manager) GetManagedQueue(qid int64) *ManagedQueue { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.Queues[qid] +} + +// ManagedQueues returns the managed queues +func (m *Manager) ManagedQueues() []*ManagedQueue { + m.mutex.Lock() + mqs := make([]*ManagedQueue, 0, len(m.Queues)) + for _, mq := range m.Queues { + mqs = append(mqs, mq) + } + m.mutex.Unlock() + sort.Sort(ManagedQueueList(mqs)) + return mqs +} + +// Workers returns the poolworkers +func (q *ManagedQueue) Workers() []*PoolWorkers { + q.mutex.Lock() + workers := make([]*PoolWorkers, 0, len(q.PoolWorkers)) + for _, worker := range q.PoolWorkers { + workers = append(workers, worker) + } + q.mutex.Unlock() + sort.Sort(PoolWorkersList(workers)) + return workers +} + +// RegisterWorkers registers workers to this queue +func (q *ManagedQueue) RegisterWorkers(number int, start time.Time, hasTimeout bool, timeout time.Time, cancel context.CancelFunc) int64 { + q.mutex.Lock() + defer q.mutex.Unlock() + q.counter++ + q.PoolWorkers[q.counter] = &PoolWorkers{ + PID: q.counter, + Workers: number, + Start: start, + Timeout: timeout, + HasTimeout: hasTimeout, + Cancel: cancel, + } + return q.counter +} + +// CancelWorkers cancels pooled workers with pid +func (q *ManagedQueue) CancelWorkers(pid int64) { + q.mutex.Lock() + pw, ok := q.PoolWorkers[pid] + q.mutex.Unlock() + if !ok { + return + } + pw.Cancel() +} + +// RemoveWorkers deletes pooled workers with pid +func (q *ManagedQueue) RemoveWorkers(pid int64) { + q.mutex.Lock() + pw, ok := q.PoolWorkers[pid] + delete(q.PoolWorkers, pid) + q.mutex.Unlock() + if ok && pw.Cancel != nil { + pw.Cancel() + } +} + +// AddWorkers adds workers to the queue if it has registered an add worker function +func (q *ManagedQueue) AddWorkers(number int, timeout time.Duration) context.CancelFunc { + if q.Pool != nil { + // the cancel will be added to the pool workers description above + return q.Pool.AddWorkers(number, timeout) + } + return nil +} + +// NumberOfWorkers returns the number of workers in the queue +func (q *ManagedQueue) NumberOfWorkers() int { + if q.Pool != nil { + return q.Pool.NumberOfWorkers() + } + return -1 +} + +// MaxNumberOfWorkers returns the maximum number of workers for the pool +func (q *ManagedQueue) MaxNumberOfWorkers() int { + if q.Pool != nil { + return q.Pool.MaxNumberOfWorkers() + } + return 0 +} + +// BoostWorkers returns the number of workers for a boost +func (q *ManagedQueue) BoostWorkers() int { + if q.Pool != nil { + return q.Pool.BoostWorkers() + } + return -1 +} + +// BoostTimeout returns the timeout of the next boost +func (q *ManagedQueue) BoostTimeout() time.Duration { + if q.Pool != nil { + return q.Pool.BoostTimeout() + } + return 0 +} + +// BlockTimeout returns the timeout til the next boost +func (q *ManagedQueue) BlockTimeout() time.Duration { + if q.Pool != nil { + return q.Pool.BlockTimeout() + } + return 0 +} + +// SetSettings sets the setable boost values +func (q *ManagedQueue) SetSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) { + if q.Pool != nil { + q.Pool.SetSettings(maxNumberOfWorkers, boostWorkers, timeout) + } +} + +func (l ManagedQueueList) Len() int { + return len(l) +} + +func (l ManagedQueueList) Less(i, j int) bool { + return l[i].Name < l[j].Name +} + +func (l ManagedQueueList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func (l PoolWorkersList) Len() int { + return len(l) +} + +func (l PoolWorkersList) Less(i, j int) bool { + return l[i].Start.Before(l[j].Start) +} + +func (l PoolWorkersList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} diff --git a/modules/queue/queue.go b/modules/queue/queue.go new file mode 100644 index 000000000000..d458a7d50627 --- /dev/null +++ b/modules/queue/queue.go @@ -0,0 +1,133 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "encoding/json" + "fmt" + "reflect" +) + +// ErrInvalidConfiguration is called when there is invalid configuration for a queue +type ErrInvalidConfiguration struct { + cfg interface{} + err error +} + +func (err ErrInvalidConfiguration) Error() string { + if err.err != nil { + return fmt.Sprintf("Invalid Configuration Argument: %v: Error: %v", err.cfg, err.err) + } + return fmt.Sprintf("Invalid Configuration Argument: %v", err.cfg) +} + +// IsErrInvalidConfiguration checks if an error is an ErrInvalidConfiguration +func IsErrInvalidConfiguration(err error) bool { + _, ok := err.(ErrInvalidConfiguration) + return ok +} + +// Type is a type of Queue +type Type string + +// Data defines an type of queuable data +type Data interface{} + +// HandlerFunc is a function that takes a variable amount of data and processes it +type HandlerFunc func(...Data) + +// NewQueueFunc is a function that creates a queue +type NewQueueFunc func(handler HandlerFunc, config interface{}, exemplar interface{}) (Queue, error) + +// Shutdownable represents a queue that can be shutdown +type Shutdownable interface { + Shutdown() + Terminate() +} + +// Named represents a queue with a name +type Named interface { + Name() string +} + +// Queue defines an interface to save an issue indexer queue +type Queue interface { + Run(atShutdown, atTerminate func(context.Context, func())) + Push(Data) error +} + +// DummyQueueType is the type for the dummy queue +const DummyQueueType Type = "dummy" + +// NewDummyQueue creates a new DummyQueue +func NewDummyQueue(handler HandlerFunc, opts, exemplar interface{}) (Queue, error) { + return &DummyQueue{}, nil +} + +// DummyQueue represents an empty queue +type DummyQueue struct { +} + +// Run starts to run the queue +func (b *DummyQueue) Run(_, _ func(context.Context, func())) {} + +// Push pushes data to the queue +func (b *DummyQueue) Push(Data) error { + return nil +} + +func toConfig(exemplar, cfg interface{}) (interface{}, error) { + if reflect.TypeOf(cfg).AssignableTo(reflect.TypeOf(exemplar)) { + return cfg, nil + } + + configBytes, ok := cfg.([]byte) + if !ok { + configStr, ok := cfg.(string) + if !ok { + return nil, ErrInvalidConfiguration{cfg: cfg} + } + configBytes = []byte(configStr) + } + newVal := reflect.New(reflect.TypeOf(exemplar)) + if err := json.Unmarshal(configBytes, newVal.Interface()); err != nil { + return nil, ErrInvalidConfiguration{cfg: cfg, err: err} + } + return newVal.Elem().Interface(), nil +} + +var queuesMap = map[Type]NewQueueFunc{DummyQueueType: NewDummyQueue} + +// RegisteredTypes provides the list of requested types of queues +func RegisteredTypes() []Type { + types := make([]Type, len(queuesMap)) + i := 0 + for key := range queuesMap { + types[i] = key + i++ + } + return types +} + +// RegisteredTypesAsString provides the list of requested types of queues +func RegisteredTypesAsString() []string { + types := make([]string, len(queuesMap)) + i := 0 + for key := range queuesMap { + types[i] = string(key) + i++ + } + return types +} + +// NewQueue takes a queue Type and HandlerFunc some options and possibly an exemplar and returns a Queue or an error +func NewQueue(queueType Type, handlerFunc HandlerFunc, opts, exemplar interface{}) (Queue, error) { + newFn, ok := queuesMap[queueType] + if !ok { + return nil, fmt.Errorf("Unsupported queue type: %v", queueType) + } + return newFn(handlerFunc, opts, exemplar) +} diff --git a/modules/queue/queue_channel.go b/modules/queue/queue_channel.go new file mode 100644 index 000000000000..c8f8a53804e7 --- /dev/null +++ b/modules/queue/queue_channel.go @@ -0,0 +1,106 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "fmt" + "reflect" + "time" + + "code.gitea.io/gitea/modules/log" +) + +// ChannelQueueType is the type for channel queue +const ChannelQueueType Type = "channel" + +// ChannelQueueConfiguration is the configuration for a ChannelQueue +type ChannelQueueConfiguration struct { + QueueLength int + BatchLength int + Workers int + MaxWorkers int + BlockTimeout time.Duration + BoostTimeout time.Duration + BoostWorkers int + Name string +} + +// ChannelQueue implements +type ChannelQueue struct { + pool *WorkerPool + exemplar interface{} + workers int + name string +} + +// NewChannelQueue create a memory channel queue +func NewChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { + configInterface, err := toConfig(ChannelQueueConfiguration{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(ChannelQueueConfiguration) + if config.BatchLength == 0 { + config.BatchLength = 1 + } + dataChan := make(chan Data, config.QueueLength) + + ctx, cancel := context.WithCancel(context.Background()) + queue := &ChannelQueue{ + pool: &WorkerPool{ + baseCtx: ctx, + cancel: cancel, + batchLength: config.BatchLength, + handle: handle, + dataChan: dataChan, + blockTimeout: config.BlockTimeout, + boostTimeout: config.BoostTimeout, + boostWorkers: config.BoostWorkers, + maxNumberOfWorkers: config.MaxWorkers, + }, + exemplar: exemplar, + workers: config.Workers, + name: config.Name, + } + queue.pool.qid = GetManager().Add(queue, ChannelQueueType, config, exemplar, queue.pool) + return queue, nil +} + +// Run starts to run the queue +func (c *ChannelQueue) Run(atShutdown, atTerminate func(context.Context, func())) { + atShutdown(context.Background(), func() { + log.Warn("ChannelQueue: %s is not shutdownable!", c.name) + }) + atTerminate(context.Background(), func() { + log.Warn("ChannelQueue: %s is not terminatable!", c.name) + }) + go func() { + _ = c.pool.AddWorkers(c.workers, 0) + }() +} + +// Push will push data into the queue +func (c *ChannelQueue) Push(data Data) error { + if c.exemplar != nil { + // Assert data is of same type as r.exemplar + t := reflect.TypeOf(data) + exemplarType := reflect.TypeOf(c.exemplar) + if !t.AssignableTo(exemplarType) || data == nil { + return fmt.Errorf("Unable to assign data: %v to same type as exemplar: %v in queue: %s", data, c.exemplar, c.name) + } + } + c.pool.Push(data) + return nil +} + +// Name returns the name of this queue +func (c *ChannelQueue) Name() string { + return c.name +} + +func init() { + queuesMap[ChannelQueueType] = NewChannelQueue +} diff --git a/modules/queue/queue_channel_test.go b/modules/queue/queue_channel_test.go new file mode 100644 index 000000000000..fafc1e3303eb --- /dev/null +++ b/modules/queue/queue_channel_test.go @@ -0,0 +1,91 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestChannelQueue(t *testing.T) { + handleChan := make(chan *testData) + handle := func(data ...Data) { + for _, datum := range data { + testDatum := datum.(*testData) + handleChan <- testDatum + } + } + + nilFn := func(_ context.Context, _ func()) {} + + queue, err := NewChannelQueue(handle, + ChannelQueueConfiguration{ + QueueLength: 20, + Workers: 1, + MaxWorkers: 10, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 5, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(nilFn, nilFn) + + test1 := testData{"A", 1} + go queue.Push(&test1) + result1 := <-handleChan + assert.Equal(t, test1.TestString, result1.TestString) + assert.Equal(t, test1.TestInt, result1.TestInt) + + err = queue.Push(test1) + assert.Error(t, err) +} + +func TestChannelQueue_Batch(t *testing.T) { + handleChan := make(chan *testData) + handle := func(data ...Data) { + assert.True(t, len(data) == 2) + for _, datum := range data { + testDatum := datum.(*testData) + handleChan <- testDatum + } + } + + nilFn := func(_ context.Context, _ func()) {} + + queue, err := NewChannelQueue(handle, + ChannelQueueConfiguration{ + QueueLength: 20, + BatchLength: 2, + Workers: 1, + MaxWorkers: 10, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 5, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(nilFn, nilFn) + + test1 := testData{"A", 1} + test2 := testData{"B", 2} + + queue.Push(&test1) + go queue.Push(&test2) + + result1 := <-handleChan + assert.Equal(t, test1.TestString, result1.TestString) + assert.Equal(t, test1.TestInt, result1.TestInt) + + result2 := <-handleChan + assert.Equal(t, test2.TestString, result2.TestString) + assert.Equal(t, test2.TestInt, result2.TestInt) + + err = queue.Push(test1) + assert.Error(t, err) +} diff --git a/modules/queue/queue_disk.go b/modules/queue/queue_disk.go new file mode 100644 index 000000000000..98e7b24e42fd --- /dev/null +++ b/modules/queue/queue_disk.go @@ -0,0 +1,213 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" + + "gitea.com/lunny/levelqueue" +) + +// LevelQueueType is the type for level queue +const LevelQueueType Type = "level" + +// LevelQueueConfiguration is the configuration for a LevelQueue +type LevelQueueConfiguration struct { + DataDir string + QueueLength int + BatchLength int + Workers int + MaxWorkers int + BlockTimeout time.Duration + BoostTimeout time.Duration + BoostWorkers int + Name string +} + +// LevelQueue implements a disk library queue +type LevelQueue struct { + pool *WorkerPool + queue *levelqueue.Queue + closed chan struct{} + terminated chan struct{} + lock sync.Mutex + exemplar interface{} + workers int + name string +} + +// NewLevelQueue creates a ledis local queue +func NewLevelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { + configInterface, err := toConfig(LevelQueueConfiguration{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(LevelQueueConfiguration) + + internal, err := levelqueue.Open(config.DataDir) + if err != nil { + return nil, err + } + + dataChan := make(chan Data, config.QueueLength) + ctx, cancel := context.WithCancel(context.Background()) + + queue := &LevelQueue{ + pool: &WorkerPool{ + baseCtx: ctx, + cancel: cancel, + batchLength: config.BatchLength, + handle: handle, + dataChan: dataChan, + blockTimeout: config.BlockTimeout, + boostTimeout: config.BoostTimeout, + boostWorkers: config.BoostWorkers, + maxNumberOfWorkers: config.MaxWorkers, + }, + queue: internal, + exemplar: exemplar, + closed: make(chan struct{}), + terminated: make(chan struct{}), + workers: config.Workers, + name: config.Name, + } + queue.pool.qid = GetManager().Add(queue, LevelQueueType, config, exemplar, queue.pool) + return queue, nil +} + +// Run starts to run the queue +func (l *LevelQueue) Run(atShutdown, atTerminate func(context.Context, func())) { + atShutdown(context.Background(), l.Shutdown) + atTerminate(context.Background(), l.Terminate) + + go func() { + _ = l.pool.AddWorkers(l.workers, 0) + }() + + go l.readToChan() + + log.Trace("LevelQueue: %s Waiting til closed", l.name) + <-l.closed + + log.Trace("LevelQueue: %s Waiting til done", l.name) + l.pool.Wait() + + log.Trace("LevelQueue: %s Waiting til cleaned", l.name) + ctx, cancel := context.WithCancel(context.Background()) + atTerminate(ctx, cancel) + l.pool.CleanUp(ctx) + cancel() + log.Trace("LevelQueue: %s Cleaned", l.name) + +} + +func (l *LevelQueue) readToChan() { + for { + select { + case <-l.closed: + // tell the pool to shutdown. + l.pool.cancel() + return + default: + bs, err := l.queue.RPop() + if err != nil { + if err != levelqueue.ErrNotFound { + log.Error("LevelQueue: %s Error on RPop: %v", l.name, err) + } + time.Sleep(time.Millisecond * 100) + continue + } + + if len(bs) == 0 { + time.Sleep(time.Millisecond * 100) + continue + } + + var data Data + if l.exemplar != nil { + t := reflect.TypeOf(l.exemplar) + n := reflect.New(t) + ne := n.Elem() + err = json.Unmarshal(bs, ne.Addr().Interface()) + data = ne.Interface().(Data) + } else { + err = json.Unmarshal(bs, &data) + } + if err != nil { + log.Error("LevelQueue: %s Failed to unmarshal with error: %v", l.name, err) + time.Sleep(time.Millisecond * 100) + continue + } + + log.Trace("LevelQueue %s: Task found: %#v", l.name, data) + l.pool.Push(data) + + } + } +} + +// Push will push the indexer data to queue +func (l *LevelQueue) Push(data Data) error { + if l.exemplar != nil { + // Assert data is of same type as r.exemplar + value := reflect.ValueOf(data) + t := value.Type() + exemplarType := reflect.ValueOf(l.exemplar).Type() + if !t.AssignableTo(exemplarType) || data == nil { + return fmt.Errorf("Unable to assign data: %v to same type as exemplar: %v in %s", data, l.exemplar, l.name) + } + } + bs, err := json.Marshal(data) + if err != nil { + return err + } + return l.queue.LPush(bs) +} + +// Shutdown this queue and stop processing +func (l *LevelQueue) Shutdown() { + l.lock.Lock() + defer l.lock.Unlock() + log.Trace("LevelQueue: %s Shutdown", l.name) + select { + case <-l.closed: + default: + close(l.closed) + } +} + +// Terminate this queue and close the queue +func (l *LevelQueue) Terminate() { + log.Trace("LevelQueue: %s Terminating", l.name) + l.Shutdown() + l.lock.Lock() + select { + case <-l.terminated: + l.lock.Unlock() + default: + close(l.terminated) + l.lock.Unlock() + if err := l.queue.Close(); err != nil && err.Error() != "leveldb: closed" { + log.Error("Error whilst closing internal queue in %s: %v", l.name, err) + } + + } +} + +// Name returns the name of this queue +func (l *LevelQueue) Name() string { + return l.name +} + +func init() { + queuesMap[LevelQueueType] = NewLevelQueue +} diff --git a/modules/queue/queue_disk_channel.go b/modules/queue/queue_disk_channel.go new file mode 100644 index 000000000000..895c8ce918f3 --- /dev/null +++ b/modules/queue/queue_disk_channel.go @@ -0,0 +1,193 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "time" + + "code.gitea.io/gitea/modules/log" +) + +// PersistableChannelQueueType is the type for persistable queue +const PersistableChannelQueueType Type = "persistable-channel" + +// PersistableChannelQueueConfiguration is the configuration for a PersistableChannelQueue +type PersistableChannelQueueConfiguration struct { + Name string + DataDir string + BatchLength int + QueueLength int + Timeout time.Duration + MaxAttempts int + Workers int + MaxWorkers int + BlockTimeout time.Duration + BoostTimeout time.Duration + BoostWorkers int +} + +// PersistableChannelQueue wraps a channel queue and level queue together +type PersistableChannelQueue struct { + *ChannelQueue + delayedStarter + closed chan struct{} +} + +// NewPersistableChannelQueue creates a wrapped batched channel queue with persistable level queue backend when shutting down +// This differs from a wrapped queue in that the persistent queue is only used to persist at shutdown/terminate +func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { + configInterface, err := toConfig(PersistableChannelQueueConfiguration{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(PersistableChannelQueueConfiguration) + + channelQueue, err := NewChannelQueue(handle, ChannelQueueConfiguration{ + QueueLength: config.QueueLength, + BatchLength: config.BatchLength, + Workers: config.Workers, + MaxWorkers: config.MaxWorkers, + BlockTimeout: config.BlockTimeout, + BoostTimeout: config.BoostTimeout, + BoostWorkers: config.BoostWorkers, + Name: config.Name + "-channel", + }, exemplar) + if err != nil { + return nil, err + } + + // the level backend only needs temporary workers to catch up with the previously dropped work + levelCfg := LevelQueueConfiguration{ + DataDir: config.DataDir, + QueueLength: config.QueueLength, + BatchLength: config.BatchLength, + Workers: 1, + MaxWorkers: 6, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 5, + Name: config.Name + "-level", + } + + levelQueue, err := NewLevelQueue(handle, levelCfg, exemplar) + if err == nil { + queue := &PersistableChannelQueue{ + ChannelQueue: channelQueue.(*ChannelQueue), + delayedStarter: delayedStarter{ + internal: levelQueue.(*LevelQueue), + name: config.Name, + }, + closed: make(chan struct{}), + } + _ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar, nil) + return queue, nil + } + if IsErrInvalidConfiguration(err) { + // Retrying ain't gonna make this any better... + return nil, ErrInvalidConfiguration{cfg: cfg} + } + + queue := &PersistableChannelQueue{ + ChannelQueue: channelQueue.(*ChannelQueue), + delayedStarter: delayedStarter{ + cfg: levelCfg, + underlying: LevelQueueType, + timeout: config.Timeout, + maxAttempts: config.MaxAttempts, + name: config.Name, + }, + closed: make(chan struct{}), + } + _ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar, nil) + return queue, nil +} + +// Name returns the name of this queue +func (p *PersistableChannelQueue) Name() string { + return p.delayedStarter.name +} + +// Push will push the indexer data to queue +func (p *PersistableChannelQueue) Push(data Data) error { + select { + case <-p.closed: + return p.internal.Push(data) + default: + return p.ChannelQueue.Push(data) + } +} + +// Run starts to run the queue +func (p *PersistableChannelQueue) Run(atShutdown, atTerminate func(context.Context, func())) { + p.lock.Lock() + if p.internal == nil { + err := p.setInternal(atShutdown, p.ChannelQueue.pool.handle, p.exemplar) + p.lock.Unlock() + if err != nil { + log.Fatal("Unable to create internal queue for %s Error: %v", p.Name(), err) + return + } + } else { + p.lock.Unlock() + } + atShutdown(context.Background(), p.Shutdown) + atTerminate(context.Background(), p.Terminate) + + // Just run the level queue - we shut it down later + go p.internal.Run(func(_ context.Context, _ func()) {}, func(_ context.Context, _ func()) {}) + + go func() { + _ = p.ChannelQueue.pool.AddWorkers(p.workers, 0) + }() + + log.Trace("PersistableChannelQueue: %s Waiting til closed", p.delayedStarter.name) + <-p.closed + log.Trace("PersistableChannelQueue: %s Cancelling pools", p.delayedStarter.name) + p.ChannelQueue.pool.cancel() + p.internal.(*LevelQueue).pool.cancel() + log.Trace("PersistableChannelQueue: %s Waiting til done", p.delayedStarter.name) + p.ChannelQueue.pool.Wait() + p.internal.(*LevelQueue).pool.Wait() + // Redirect all remaining data in the chan to the internal channel + go func() { + log.Trace("PersistableChannelQueue: %s Redirecting remaining data", p.delayedStarter.name) + for data := range p.ChannelQueue.pool.dataChan { + _ = p.internal.Push(data) + } + log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", p.delayedStarter.name) + }() + log.Trace("PersistableChannelQueue: %s Done main loop", p.delayedStarter.name) +} + +// Shutdown processing this queue +func (p *PersistableChannelQueue) Shutdown() { + log.Trace("PersistableChannelQueue: %s Shutdown", p.delayedStarter.name) + select { + case <-p.closed: + default: + p.lock.Lock() + defer p.lock.Unlock() + if p.internal != nil { + p.internal.(*LevelQueue).Shutdown() + } + close(p.closed) + } +} + +// Terminate this queue and close the queue +func (p *PersistableChannelQueue) Terminate() { + log.Trace("PersistableChannelQueue: %s Terminating", p.delayedStarter.name) + p.Shutdown() + p.lock.Lock() + defer p.lock.Unlock() + if p.internal != nil { + p.internal.(*LevelQueue).Terminate() + } +} + +func init() { + queuesMap[PersistableChannelQueueType] = NewPersistableChannelQueue +} diff --git a/modules/queue/queue_disk_channel_test.go b/modules/queue/queue_disk_channel_test.go new file mode 100644 index 000000000000..4ef68961c6fe --- /dev/null +++ b/modules/queue/queue_disk_channel_test.go @@ -0,0 +1,117 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPersistableChannelQueue(t *testing.T) { + handleChan := make(chan *testData) + handle := func(data ...Data) { + assert.True(t, len(data) == 2) + for _, datum := range data { + testDatum := datum.(*testData) + handleChan <- testDatum + } + } + + queueShutdown := []func(){} + queueTerminate := []func(){} + + tmpDir, err := ioutil.TempDir("", "persistable-channel-queue-test-data") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + queue, err := NewPersistableChannelQueue(handle, PersistableChannelQueueConfiguration{ + DataDir: tmpDir, + BatchLength: 2, + QueueLength: 20, + Workers: 1, + MaxWorkers: 10, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(func(_ context.Context, shutdown func()) { + queueShutdown = append(queueShutdown, shutdown) + }, func(_ context.Context, terminate func()) { + queueTerminate = append(queueTerminate, terminate) + }) + + test1 := testData{"A", 1} + test2 := testData{"B", 2} + + err = queue.Push(&test1) + assert.NoError(t, err) + go func() { + err = queue.Push(&test2) + assert.NoError(t, err) + }() + + result1 := <-handleChan + assert.Equal(t, test1.TestString, result1.TestString) + assert.Equal(t, test1.TestInt, result1.TestInt) + + result2 := <-handleChan + assert.Equal(t, test2.TestString, result2.TestString) + assert.Equal(t, test2.TestInt, result2.TestInt) + + err = queue.Push(test1) + assert.Error(t, err) + + for _, callback := range queueShutdown { + callback() + } + time.Sleep(200 * time.Millisecond) + err = queue.Push(&test1) + assert.NoError(t, err) + err = queue.Push(&test2) + assert.NoError(t, err) + select { + case <-handleChan: + assert.Fail(t, "Handler processing should have stopped") + default: + } + for _, callback := range queueTerminate { + callback() + } + + // Reopen queue + queue, err = NewPersistableChannelQueue(handle, PersistableChannelQueueConfiguration{ + DataDir: tmpDir, + BatchLength: 2, + QueueLength: 20, + Workers: 1, + MaxWorkers: 10, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(func(_ context.Context, shutdown func()) { + queueShutdown = append(queueShutdown, shutdown) + }, func(_ context.Context, terminate func()) { + queueTerminate = append(queueTerminate, terminate) + }) + + result3 := <-handleChan + assert.Equal(t, test1.TestString, result3.TestString) + assert.Equal(t, test1.TestInt, result3.TestInt) + + result4 := <-handleChan + assert.Equal(t, test2.TestString, result4.TestString) + assert.Equal(t, test2.TestInt, result4.TestInt) + for _, callback := range queueShutdown { + callback() + } + for _, callback := range queueTerminate { + callback() + } + +} diff --git a/modules/queue/queue_disk_test.go b/modules/queue/queue_disk_test.go new file mode 100644 index 000000000000..c5959d606fdd --- /dev/null +++ b/modules/queue/queue_disk_test.go @@ -0,0 +1,126 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestLevelQueue(t *testing.T) { + handleChan := make(chan *testData) + handle := func(data ...Data) { + assert.True(t, len(data) == 2) + for _, datum := range data { + testDatum := datum.(*testData) + handleChan <- testDatum + } + } + + queueShutdown := []func(){} + queueTerminate := []func(){} + + tmpDir, err := ioutil.TempDir("", "level-queue-test-data") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + queue, err := NewLevelQueue(handle, LevelQueueConfiguration{ + DataDir: tmpDir, + BatchLength: 2, + Workers: 1, + MaxWorkers: 10, + QueueLength: 20, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 5, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(func(_ context.Context, shutdown func()) { + queueShutdown = append(queueShutdown, shutdown) + }, func(_ context.Context, terminate func()) { + queueTerminate = append(queueTerminate, terminate) + }) + + test1 := testData{"A", 1} + test2 := testData{"B", 2} + + err = queue.Push(&test1) + assert.NoError(t, err) + go func() { + err = queue.Push(&test2) + assert.NoError(t, err) + }() + + result1 := <-handleChan + assert.Equal(t, test1.TestString, result1.TestString) + assert.Equal(t, test1.TestInt, result1.TestInt) + + result2 := <-handleChan + assert.Equal(t, test2.TestString, result2.TestString) + assert.Equal(t, test2.TestInt, result2.TestInt) + + err = queue.Push(test1) + assert.Error(t, err) + + for _, callback := range queueShutdown { + callback() + } + time.Sleep(200 * time.Millisecond) + err = queue.Push(&test1) + assert.NoError(t, err) + err = queue.Push(&test2) + assert.NoError(t, err) + select { + case <-handleChan: + assert.Fail(t, "Handler processing should have stopped") + default: + } + for _, callback := range queueTerminate { + callback() + } + + // Reopen queue + queue, err = NewWrappedQueue(handle, + WrappedQueueConfiguration{ + Underlying: LevelQueueType, + Config: LevelQueueConfiguration{ + DataDir: tmpDir, + BatchLength: 2, + Workers: 1, + MaxWorkers: 10, + QueueLength: 20, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 5, + }, + }, &testData{}) + assert.NoError(t, err) + + go queue.Run(func(_ context.Context, shutdown func()) { + queueShutdown = append(queueShutdown, shutdown) + }, func(_ context.Context, terminate func()) { + queueTerminate = append(queueTerminate, terminate) + }) + + result3 := <-handleChan + assert.Equal(t, test1.TestString, result3.TestString) + assert.Equal(t, test1.TestInt, result3.TestInt) + + result4 := <-handleChan + assert.Equal(t, test2.TestString, result4.TestString) + assert.Equal(t, test2.TestInt, result4.TestInt) + for _, callback := range queueShutdown { + callback() + } + for _, callback := range queueTerminate { + callback() + } +} diff --git a/modules/queue/queue_redis.go b/modules/queue/queue_redis.go new file mode 100644 index 000000000000..14e68937a5b8 --- /dev/null +++ b/modules/queue/queue_redis.go @@ -0,0 +1,234 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" + + "github.com/go-redis/redis" +) + +// RedisQueueType is the type for redis queue +const RedisQueueType Type = "redis" + +type redisClient interface { + RPush(key string, args ...interface{}) *redis.IntCmd + LPop(key string) *redis.StringCmd + Ping() *redis.StatusCmd + Close() error +} + +// RedisQueue redis queue +type RedisQueue struct { + pool *WorkerPool + client redisClient + queueName string + closed chan struct{} + terminated chan struct{} + exemplar interface{} + workers int + name string + lock sync.Mutex +} + +// RedisQueueConfiguration is the configuration for the redis queue +type RedisQueueConfiguration struct { + Network string + Addresses string + Password string + DBIndex int + BatchLength int + QueueLength int + QueueName string + Workers int + MaxWorkers int + BlockTimeout time.Duration + BoostTimeout time.Duration + BoostWorkers int + Name string +} + +// NewRedisQueue creates single redis or cluster redis queue +func NewRedisQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { + configInterface, err := toConfig(RedisQueueConfiguration{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(RedisQueueConfiguration) + + dbs := strings.Split(config.Addresses, ",") + + dataChan := make(chan Data, config.QueueLength) + ctx, cancel := context.WithCancel(context.Background()) + + var queue = &RedisQueue{ + pool: &WorkerPool{ + baseCtx: ctx, + cancel: cancel, + batchLength: config.BatchLength, + handle: handle, + dataChan: dataChan, + blockTimeout: config.BlockTimeout, + boostTimeout: config.BoostTimeout, + boostWorkers: config.BoostWorkers, + maxNumberOfWorkers: config.MaxWorkers, + }, + queueName: config.QueueName, + exemplar: exemplar, + closed: make(chan struct{}), + workers: config.Workers, + name: config.Name, + } + if len(dbs) == 0 { + return nil, errors.New("no redis host specified") + } else if len(dbs) == 1 { + queue.client = redis.NewClient(&redis.Options{ + Network: config.Network, + Addr: strings.TrimSpace(dbs[0]), // use default Addr + Password: config.Password, // no password set + DB: config.DBIndex, // use default DB + }) + } else { + queue.client = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: dbs, + }) + } + if err := queue.client.Ping().Err(); err != nil { + return nil, err + } + queue.pool.qid = GetManager().Add(queue, RedisQueueType, config, exemplar, queue.pool) + + return queue, nil +} + +// Run runs the redis queue +func (r *RedisQueue) Run(atShutdown, atTerminate func(context.Context, func())) { + atShutdown(context.Background(), r.Shutdown) + atTerminate(context.Background(), r.Terminate) + + go func() { + _ = r.pool.AddWorkers(r.workers, 0) + }() + + go r.readToChan() + + log.Trace("RedisQueue: %s Waiting til closed", r.name) + <-r.closed + log.Trace("RedisQueue: %s Waiting til done", r.name) + r.pool.Wait() + + log.Trace("RedisQueue: %s Waiting til cleaned", r.name) + ctx, cancel := context.WithCancel(context.Background()) + atTerminate(ctx, cancel) + r.pool.CleanUp(ctx) + cancel() +} + +func (r *RedisQueue) readToChan() { + for { + select { + case <-r.closed: + // tell the pool to shutdown + r.pool.cancel() + return + default: + bs, err := r.client.LPop(r.queueName).Bytes() + if err != nil && err != redis.Nil { + log.Error("RedisQueue: %s Error on LPop: %v", r.name, err) + time.Sleep(time.Millisecond * 100) + continue + } + + if len(bs) == 0 { + time.Sleep(time.Millisecond * 100) + continue + } + + var data Data + if r.exemplar != nil { + t := reflect.TypeOf(r.exemplar) + n := reflect.New(t) + ne := n.Elem() + err = json.Unmarshal(bs, ne.Addr().Interface()) + data = ne.Interface().(Data) + } else { + err = json.Unmarshal(bs, &data) + } + if err != nil { + log.Error("RedisQueue: %s Error on Unmarshal: %v", r.name, err) + time.Sleep(time.Millisecond * 100) + continue + } + + log.Trace("RedisQueue: %s Task found: %#v", r.name, data) + r.pool.Push(data) + } + } +} + +// Push implements Queue +func (r *RedisQueue) Push(data Data) error { + if r.exemplar != nil { + // Assert data is of same type as r.exemplar + value := reflect.ValueOf(data) + t := value.Type() + exemplarType := reflect.ValueOf(r.exemplar).Type() + if !t.AssignableTo(exemplarType) || data == nil { + return fmt.Errorf("Unable to assign data: %v to same type as exemplar: %v in %s", data, r.exemplar, r.name) + } + } + bs, err := json.Marshal(data) + if err != nil { + return err + } + return r.client.RPush(r.queueName, bs).Err() +} + +// Shutdown processing from this queue +func (r *RedisQueue) Shutdown() { + log.Trace("Shutdown: %s", r.name) + r.lock.Lock() + select { + case <-r.closed: + default: + close(r.closed) + } + r.lock.Unlock() +} + +// Terminate this queue and close the queue +func (r *RedisQueue) Terminate() { + log.Trace("Terminating: %s", r.name) + r.Shutdown() + r.lock.Lock() + select { + case <-r.terminated: + r.lock.Unlock() + default: + close(r.terminated) + r.lock.Unlock() + if err := r.client.Close(); err != nil { + log.Error("Error whilst closing internal redis client in %s: %v", r.name, err) + } + } +} + +// Name returns the name of this queue +func (r *RedisQueue) Name() string { + return r.name +} + +func init() { + queuesMap[RedisQueueType] = NewRedisQueue +} diff --git a/modules/queue/queue_test.go b/modules/queue/queue_test.go new file mode 100644 index 000000000000..3608f68d3d42 --- /dev/null +++ b/modules/queue/queue_test.go @@ -0,0 +1,43 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +type testData struct { + TestString string + TestInt int +} + +func TestToConfig(t *testing.T) { + cfg := testData{ + TestString: "Config", + TestInt: 10, + } + exemplar := testData{} + + cfg2I, err := toConfig(exemplar, cfg) + assert.NoError(t, err) + cfg2, ok := (cfg2I).(testData) + assert.True(t, ok) + assert.NotEqual(t, cfg2, exemplar) + assert.Equal(t, &cfg, &cfg2) + + cfgString, err := json.Marshal(cfg) + assert.NoError(t, err) + + cfg3I, err := toConfig(exemplar, cfgString) + assert.NoError(t, err) + cfg3, ok := (cfg3I).(testData) + assert.True(t, ok) + assert.Equal(t, cfg.TestString, cfg3.TestString) + assert.Equal(t, cfg.TestInt, cfg3.TestInt) + assert.NotEqual(t, cfg3, exemplar) +} diff --git a/modules/queue/queue_wrapped.go b/modules/queue/queue_wrapped.go new file mode 100644 index 000000000000..d0b93b54d0c3 --- /dev/null +++ b/modules/queue/queue_wrapped.go @@ -0,0 +1,206 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "fmt" + "reflect" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" +) + +// WrappedQueueType is the type for a wrapped delayed starting queue +const WrappedQueueType Type = "wrapped" + +// WrappedQueueConfiguration is the configuration for a WrappedQueue +type WrappedQueueConfiguration struct { + Underlying Type + Timeout time.Duration + MaxAttempts int + Config interface{} + QueueLength int + Name string +} + +type delayedStarter struct { + lock sync.Mutex + internal Queue + underlying Type + cfg interface{} + timeout time.Duration + maxAttempts int + name string +} + +// setInternal must be called with the lock locked. +func (q *delayedStarter) setInternal(atShutdown func(context.Context, func()), handle HandlerFunc, exemplar interface{}) error { + var ctx context.Context + var cancel context.CancelFunc + if q.timeout > 0 { + ctx, cancel = context.WithTimeout(context.Background(), q.timeout) + } else { + ctx, cancel = context.WithCancel(context.Background()) + } + + defer cancel() + // Ensure we also stop at shutdown + atShutdown(ctx, func() { + cancel() + }) + + i := 1 + for q.internal == nil { + select { + case <-ctx.Done(): + return fmt.Errorf("Timedout creating queue %v with cfg %v in %s", q.underlying, q.cfg, q.name) + default: + queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar) + if err == nil { + q.internal = queue + q.lock.Unlock() + break + } + if err.Error() != "resource temporarily unavailable" { + log.Warn("[Attempt: %d] Failed to create queue: %v for %s cfg: %v error: %v", i, q.underlying, q.name, q.cfg, err) + } + i++ + if q.maxAttempts > 0 && i > q.maxAttempts { + return fmt.Errorf("Unable to create queue %v for %s with cfg %v by max attempts: error: %v", q.underlying, q.name, q.cfg, err) + } + sleepTime := 100 * time.Millisecond + if q.timeout > 0 && q.maxAttempts > 0 { + sleepTime = (q.timeout - 200*time.Millisecond) / time.Duration(q.maxAttempts) + } + t := time.NewTimer(sleepTime) + select { + case <-ctx.Done(): + t.Stop() + case <-t.C: + } + } + } + return nil +} + +// WrappedQueue wraps a delayed starting queue +type WrappedQueue struct { + delayedStarter + handle HandlerFunc + exemplar interface{} + channel chan Data +} + +// NewWrappedQueue will attempt to create a queue of the provided type, +// but if there is a problem creating this queue it will instead create +// a WrappedQueue with delayed startup of the queue instead and a +// channel which will be redirected to the queue +func NewWrappedQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { + configInterface, err := toConfig(WrappedQueueConfiguration{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(WrappedQueueConfiguration) + + queue, err := NewQueue(config.Underlying, handle, config.Config, exemplar) + if err == nil { + // Just return the queue there is no need to wrap + return queue, nil + } + if IsErrInvalidConfiguration(err) { + // Retrying ain't gonna make this any better... + return nil, ErrInvalidConfiguration{cfg: cfg} + } + + queue = &WrappedQueue{ + handle: handle, + channel: make(chan Data, config.QueueLength), + exemplar: exemplar, + delayedStarter: delayedStarter{ + cfg: config.Config, + underlying: config.Underlying, + timeout: config.Timeout, + maxAttempts: config.MaxAttempts, + name: config.Name, + }, + } + _ = GetManager().Add(queue, WrappedQueueType, config, exemplar, nil) + return queue, nil +} + +// Name returns the name of the queue +func (q *WrappedQueue) Name() string { + return q.name + "-wrapper" +} + +// Push will push the data to the internal channel checking it against the exemplar +func (q *WrappedQueue) Push(data Data) error { + if q.exemplar != nil { + // Assert data is of same type as r.exemplar + value := reflect.ValueOf(data) + t := value.Type() + exemplarType := reflect.ValueOf(q.exemplar).Type() + if !t.AssignableTo(exemplarType) || data == nil { + return fmt.Errorf("Unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name) + } + } + q.channel <- data + return nil +} + +// Run starts to run the queue and attempts to create the internal queue +func (q *WrappedQueue) Run(atShutdown, atTerminate func(context.Context, func())) { + q.lock.Lock() + if q.internal == nil { + err := q.setInternal(atShutdown, q.handle, q.exemplar) + q.lock.Unlock() + if err != nil { + log.Fatal("Unable to set the internal queue for %s Error: %v", q.Name(), err) + return + } + go func() { + for data := range q.channel { + _ = q.internal.Push(data) + } + }() + } else { + q.lock.Unlock() + } + + q.internal.Run(atShutdown, atTerminate) + log.Trace("WrappedQueue: %s Done", q.name) +} + +// Shutdown this queue and stop processing +func (q *WrappedQueue) Shutdown() { + log.Trace("WrappedQueue: %s Shutdown", q.name) + q.lock.Lock() + defer q.lock.Unlock() + if q.internal == nil { + return + } + if shutdownable, ok := q.internal.(Shutdownable); ok { + shutdownable.Shutdown() + } +} + +// Terminate this queue and close the queue +func (q *WrappedQueue) Terminate() { + log.Trace("WrappedQueue: %s Terminating", q.name) + q.lock.Lock() + defer q.lock.Unlock() + if q.internal == nil { + return + } + if shutdownable, ok := q.internal.(Shutdownable); ok { + shutdownable.Terminate() + } +} + +func init() { + queuesMap[WrappedQueueType] = NewWrappedQueue +} diff --git a/modules/queue/setting.go b/modules/queue/setting.go new file mode 100644 index 000000000000..d5a6b41882a0 --- /dev/null +++ b/modules/queue/setting.go @@ -0,0 +1,75 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "encoding/json" + "fmt" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +func validType(t string) (Type, error) { + if len(t) == 0 { + return PersistableChannelQueueType, nil + } + for _, typ := range RegisteredTypes() { + if t == string(typ) { + return typ, nil + } + } + return PersistableChannelQueueType, fmt.Errorf("Unknown queue type: %s defaulting to %s", t, string(PersistableChannelQueueType)) +} + +// CreateQueue for name with provided handler and exemplar +func CreateQueue(name string, handle HandlerFunc, exemplar interface{}) Queue { + q := setting.GetQueueSettings(name) + opts := make(map[string]interface{}) + opts["Name"] = name + opts["QueueLength"] = q.Length + opts["BatchLength"] = q.BatchLength + opts["DataDir"] = q.DataDir + opts["Addresses"] = q.Addresses + opts["Network"] = q.Network + opts["Password"] = q.Password + opts["DBIndex"] = q.DBIndex + opts["QueueName"] = q.QueueName + opts["Workers"] = q.Workers + opts["MaxWorkers"] = q.MaxWorkers + opts["BlockTimeout"] = q.BlockTimeout + opts["BoostTimeout"] = q.BoostTimeout + opts["BoostWorkers"] = q.BoostWorkers + + typ, err := validType(q.Type) + if err != nil { + log.Error("Invalid type %s provided for queue named %s defaulting to %s", q.Type, name, string(typ)) + } + + cfg, err := json.Marshal(opts) + if err != nil { + log.Error("Unable to marshall generic options: %v Error: %v", opts, err) + log.Error("Unable to create queue for %s", name, err) + return nil + } + + returnable, err := NewQueue(typ, handle, cfg, exemplar) + if q.WrapIfNecessary && err != nil { + log.Warn("Unable to create queue for %s: %v", name, err) + log.Warn("Attempting to create wrapped queue") + returnable, err = NewQueue(WrappedQueueType, handle, WrappedQueueConfiguration{ + Underlying: Type(q.Type), + Timeout: q.Timeout, + MaxAttempts: q.MaxAttempts, + Config: cfg, + QueueLength: q.Length, + }, exemplar) + } + if err != nil { + log.Error("Unable to create queue for %s: %v", name, err) + return nil + } + return returnable +} diff --git a/modules/queue/workerpool.go b/modules/queue/workerpool.go new file mode 100644 index 000000000000..25fc7dd64425 --- /dev/null +++ b/modules/queue/workerpool.go @@ -0,0 +1,325 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package queue + +import ( + "context" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" +) + +// WorkerPool takes +type WorkerPool struct { + lock sync.Mutex + baseCtx context.Context + cancel context.CancelFunc + cond *sync.Cond + qid int64 + maxNumberOfWorkers int + numberOfWorkers int + batchLength int + handle HandlerFunc + dataChan chan Data + blockTimeout time.Duration + boostTimeout time.Duration + boostWorkers int +} + +// Push pushes the data to the internal channel +func (p *WorkerPool) Push(data Data) { + p.lock.Lock() + if p.blockTimeout > 0 && p.boostTimeout > 0 && (p.numberOfWorkers <= p.maxNumberOfWorkers || p.maxNumberOfWorkers < 0) { + p.lock.Unlock() + p.pushBoost(data) + } else { + p.lock.Unlock() + p.dataChan <- data + } +} + +func (p *WorkerPool) pushBoost(data Data) { + select { + case p.dataChan <- data: + default: + p.lock.Lock() + if p.blockTimeout <= 0 { + p.lock.Unlock() + p.dataChan <- data + return + } + ourTimeout := p.blockTimeout + timer := time.NewTimer(p.blockTimeout) + p.lock.Unlock() + select { + case p.dataChan <- data: + if timer.Stop() { + select { + case <-timer.C: + default: + } + } + case <-timer.C: + p.lock.Lock() + if p.blockTimeout > ourTimeout || (p.numberOfWorkers > p.maxNumberOfWorkers && p.maxNumberOfWorkers >= 0) { + p.lock.Unlock() + p.dataChan <- data + return + } + p.blockTimeout *= 2 + ctx, cancel := context.WithCancel(p.baseCtx) + mq := GetManager().GetManagedQueue(p.qid) + boost := p.boostWorkers + if (boost+p.numberOfWorkers) > p.maxNumberOfWorkers && p.maxNumberOfWorkers >= 0 { + boost = p.maxNumberOfWorkers - p.numberOfWorkers + } + if mq != nil { + log.Warn("WorkerPool: %d (for %s) Channel blocked for %v - adding %d temporary workers for %s, block timeout now %v", p.qid, mq.Name, ourTimeout, boost, p.boostTimeout, p.blockTimeout) + + start := time.Now() + pid := mq.RegisterWorkers(boost, start, false, start, cancel) + go func() { + <-ctx.Done() + mq.RemoveWorkers(pid) + cancel() + }() + } else { + log.Warn("WorkerPool: %d Channel blocked for %v - adding %d temporary workers for %s, block timeout now %v", p.qid, ourTimeout, p.boostWorkers, p.boostTimeout, p.blockTimeout) + } + go func() { + <-time.After(p.boostTimeout) + cancel() + p.lock.Lock() + p.blockTimeout /= 2 + p.lock.Unlock() + }() + p.addWorkers(ctx, boost) + p.lock.Unlock() + p.dataChan <- data + } + } +} + +// NumberOfWorkers returns the number of current workers in the pool +func (p *WorkerPool) NumberOfWorkers() int { + p.lock.Lock() + defer p.lock.Unlock() + return p.numberOfWorkers +} + +// MaxNumberOfWorkers returns the maximum number of workers automatically added to the pool +func (p *WorkerPool) MaxNumberOfWorkers() int { + p.lock.Lock() + defer p.lock.Unlock() + return p.maxNumberOfWorkers +} + +// BoostWorkers returns the number of workers for a boost +func (p *WorkerPool) BoostWorkers() int { + p.lock.Lock() + defer p.lock.Unlock() + return p.boostWorkers +} + +// BoostTimeout returns the timeout of the next boost +func (p *WorkerPool) BoostTimeout() time.Duration { + p.lock.Lock() + defer p.lock.Unlock() + return p.boostTimeout +} + +// BlockTimeout returns the timeout til the next boost +func (p *WorkerPool) BlockTimeout() time.Duration { + p.lock.Lock() + defer p.lock.Unlock() + return p.blockTimeout +} + +// SetSettings sets the setable boost values +func (p *WorkerPool) SetSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) { + p.lock.Lock() + defer p.lock.Unlock() + p.maxNumberOfWorkers = maxNumberOfWorkers + p.boostWorkers = boostWorkers + p.boostTimeout = timeout +} + +// SetMaxNumberOfWorkers sets the maximum number of workers automatically added to the pool +// Changing this number will not change the number of current workers but will change the limit +// for future additions +func (p *WorkerPool) SetMaxNumberOfWorkers(newMax int) { + p.lock.Lock() + defer p.lock.Unlock() + p.maxNumberOfWorkers = newMax +} + +// AddWorkers adds workers to the pool - this allows the number of workers to go above the limit +func (p *WorkerPool) AddWorkers(number int, timeout time.Duration) context.CancelFunc { + var ctx context.Context + var cancel context.CancelFunc + start := time.Now() + end := start + hasTimeout := false + if timeout > 0 { + ctx, cancel = context.WithTimeout(p.baseCtx, timeout) + end = start.Add(timeout) + hasTimeout = true + } else { + ctx, cancel = context.WithCancel(p.baseCtx) + } + + mq := GetManager().GetManagedQueue(p.qid) + if mq != nil { + pid := mq.RegisterWorkers(number, start, hasTimeout, end, cancel) + go func() { + <-ctx.Done() + mq.RemoveWorkers(pid) + cancel() + }() + log.Trace("WorkerPool: %d (for %s) adding %d workers with group id: %d", p.qid, mq.Name, number, pid) + } else { + log.Trace("WorkerPool: %d adding %d workers (no group id)", p.qid, number) + + } + p.addWorkers(ctx, number) + return cancel +} + +// addWorkers adds workers to the pool +func (p *WorkerPool) addWorkers(ctx context.Context, number int) { + for i := 0; i < number; i++ { + p.lock.Lock() + if p.cond == nil { + p.cond = sync.NewCond(&p.lock) + } + p.numberOfWorkers++ + p.lock.Unlock() + go func() { + p.doWork(ctx) + + p.lock.Lock() + p.numberOfWorkers-- + if p.numberOfWorkers == 0 { + p.cond.Broadcast() + } else if p.numberOfWorkers < 0 { + // numberOfWorkers can't go negative but... + log.Warn("Number of Workers < 0 for QID %d - this shouldn't happen", p.qid) + p.numberOfWorkers = 0 + p.cond.Broadcast() + } + p.lock.Unlock() + }() + } +} + +// Wait for WorkerPool to finish +func (p *WorkerPool) Wait() { + p.lock.Lock() + defer p.lock.Unlock() + if p.cond == nil { + p.cond = sync.NewCond(&p.lock) + } + if p.numberOfWorkers <= 0 { + return + } + p.cond.Wait() +} + +// CleanUp will drain the remaining contents of the channel +// This should be called after AddWorkers context is closed +func (p *WorkerPool) CleanUp(ctx context.Context) { + log.Trace("WorkerPool: %d CleanUp", p.qid) + close(p.dataChan) + for data := range p.dataChan { + p.handle(data) + select { + case <-ctx.Done(): + log.Warn("WorkerPool: %d Cleanup context closed before finishing clean-up", p.qid) + return + default: + } + } + log.Trace("WorkerPool: %d CleanUp Done", p.qid) +} + +func (p *WorkerPool) doWork(ctx context.Context) { + delay := time.Millisecond * 300 + var data = make([]Data, 0, p.batchLength) + for { + select { + case <-ctx.Done(): + if len(data) > 0 { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + } + log.Trace("Worker shutting down") + return + case datum, ok := <-p.dataChan: + if !ok { + // the dataChan has been closed - we should finish up: + if len(data) > 0 { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + } + log.Trace("Worker shutting down") + return + } + data = append(data, datum) + if len(data) >= p.batchLength { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + data = make([]Data, 0, p.batchLength) + } + default: + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + if timer.Stop() { + select { + case <-timer.C: + default: + } + } + if len(data) > 0 { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + } + log.Trace("Worker shutting down") + return + case datum, ok := <-p.dataChan: + if timer.Stop() { + select { + case <-timer.C: + default: + } + } + if !ok { + // the dataChan has been closed - we should finish up: + if len(data) > 0 { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + } + log.Trace("Worker shutting down") + return + } + data = append(data, datum) + if len(data) >= p.batchLength { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + data = make([]Data, 0, p.batchLength) + } + case <-timer.C: + delay = time.Millisecond * 100 + if len(data) > 0 { + log.Trace("Handling: %d data, %v", len(data), data) + p.handle(data...) + data = make([]Data, 0, p.batchLength) + } + + } + } + } +} diff --git a/modules/setting/queue.go b/modules/setting/queue.go new file mode 100644 index 000000000000..546802715f14 --- /dev/null +++ b/modules/setting/queue.go @@ -0,0 +1,159 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package setting + +import ( + "fmt" + "path" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/modules/log" +) + +// QueueSettings represent the settings for a queue from the ini +type QueueSettings struct { + DataDir string + Length int + BatchLength int + ConnectionString string + Type string + Network string + Addresses string + Password string + QueueName string + DBIndex int + WrapIfNecessary bool + MaxAttempts int + Timeout time.Duration + Workers int + MaxWorkers int + BlockTimeout time.Duration + BoostTimeout time.Duration + BoostWorkers int +} + +// Queue settings +var Queue = QueueSettings{} + +// GetQueueSettings returns the queue settings for the appropriately named queue +func GetQueueSettings(name string) QueueSettings { + q := QueueSettings{} + sec := Cfg.Section("queue." + name) + // DataDir is not directly inheritable + q.DataDir = path.Join(Queue.DataDir, name) + // QueueName is not directly inheritable either + q.QueueName = name + Queue.QueueName + for _, key := range sec.Keys() { + switch key.Name() { + case "DATADIR": + q.DataDir = key.MustString(q.DataDir) + case "QUEUE_NAME": + q.QueueName = key.MustString(q.QueueName) + } + } + if !path.IsAbs(q.DataDir) { + q.DataDir = path.Join(AppDataPath, q.DataDir) + } + sec.Key("DATADIR").SetValue(q.DataDir) + // The rest are... + q.Length = sec.Key("LENGTH").MustInt(Queue.Length) + q.BatchLength = sec.Key("BATCH_LENGTH").MustInt(Queue.BatchLength) + q.ConnectionString = sec.Key("CONN_STR").MustString(Queue.ConnectionString) + q.Type = sec.Key("TYPE").MustString(Queue.Type) + q.WrapIfNecessary = sec.Key("WRAP_IF_NECESSARY").MustBool(Queue.WrapIfNecessary) + q.MaxAttempts = sec.Key("MAX_ATTEMPTS").MustInt(Queue.MaxAttempts) + q.Timeout = sec.Key("TIMEOUT").MustDuration(Queue.Timeout) + q.Workers = sec.Key("WORKERS").MustInt(Queue.Workers) + q.MaxWorkers = sec.Key("MAX_WORKERS").MustInt(Queue.MaxWorkers) + q.BlockTimeout = sec.Key("BLOCK_TIMEOUT").MustDuration(Queue.BlockTimeout) + q.BoostTimeout = sec.Key("BOOST_TIMEOUT").MustDuration(Queue.BoostTimeout) + q.BoostWorkers = sec.Key("BOOST_WORKERS").MustInt(Queue.BoostWorkers) + + q.Network, q.Addresses, q.Password, q.DBIndex, _ = ParseQueueConnStr(q.ConnectionString) + return q +} + +// NewQueueService sets up the default settings for Queues +// This is exported for tests to be able to use the queue +func NewQueueService() { + sec := Cfg.Section("queue") + Queue.DataDir = sec.Key("DATADIR").MustString("queues/") + if !path.IsAbs(Queue.DataDir) { + Queue.DataDir = path.Join(AppDataPath, Queue.DataDir) + } + Queue.Length = sec.Key("LENGTH").MustInt(20) + Queue.BatchLength = sec.Key("BATCH_LENGTH").MustInt(20) + Queue.ConnectionString = sec.Key("CONN_STR").MustString(path.Join(AppDataPath, "")) + Queue.Type = sec.Key("TYPE").MustString("") + Queue.Network, Queue.Addresses, Queue.Password, Queue.DBIndex, _ = ParseQueueConnStr(Queue.ConnectionString) + Queue.WrapIfNecessary = sec.Key("WRAP_IF_NECESSARY").MustBool(true) + Queue.MaxAttempts = sec.Key("MAX_ATTEMPTS").MustInt(10) + Queue.Timeout = sec.Key("TIMEOUT").MustDuration(GracefulHammerTime + 30*time.Second) + Queue.Workers = sec.Key("WORKERS").MustInt(1) + Queue.MaxWorkers = sec.Key("MAX_WORKERS").MustInt(10) + Queue.BlockTimeout = sec.Key("BLOCK_TIMEOUT").MustDuration(1 * time.Second) + Queue.BoostTimeout = sec.Key("BOOST_TIMEOUT").MustDuration(5 * time.Minute) + Queue.BoostWorkers = sec.Key("BOOST_WORKERS").MustInt(5) + Queue.QueueName = sec.Key("QUEUE_NAME").MustString("_queue") + + // Now handle the old issue_indexer configuration + section := Cfg.Section("queue.issue_indexer") + issueIndexerSectionMap := map[string]string{} + for _, key := range section.Keys() { + issueIndexerSectionMap[key.Name()] = key.Value() + } + if _, ok := issueIndexerSectionMap["TYPE"]; !ok { + switch Indexer.IssueQueueType { + case LevelQueueType: + section.Key("TYPE").SetValue("level") + case ChannelQueueType: + section.Key("TYPE").SetValue("persistable-channel") + case RedisQueueType: + section.Key("TYPE").SetValue("redis") + default: + log.Fatal("Unsupported indexer queue type: %v", + Indexer.IssueQueueType) + } + } + if _, ok := issueIndexerSectionMap["LENGTH"]; !ok { + section.Key("LENGTH").SetValue(fmt.Sprintf("%d", Indexer.UpdateQueueLength)) + } + if _, ok := issueIndexerSectionMap["BATCH_LENGTH"]; !ok { + section.Key("BATCH_LENGTH").SetValue(fmt.Sprintf("%d", Indexer.IssueQueueBatchNumber)) + } + if _, ok := issueIndexerSectionMap["DATADIR"]; !ok { + section.Key("DATADIR").SetValue(Indexer.IssueQueueDir) + } + if _, ok := issueIndexerSectionMap["CONN_STR"]; !ok { + section.Key("CONN_STR").SetValue(Indexer.IssueQueueConnStr) + } +} + +// ParseQueueConnStr parses a queue connection string +func ParseQueueConnStr(connStr string) (network, addrs, password string, dbIdx int, err error) { + fields := strings.Fields(connStr) + for _, f := range fields { + items := strings.SplitN(f, "=", 2) + if len(items) < 2 { + continue + } + switch strings.ToLower(items[0]) { + case "network": + network = items[1] + case "addrs": + addrs = items[1] + case "password": + password = items[1] + case "db": + dbIdx, err = strconv.Atoi(items[1]) + if err != nil { + return + } + } + } + return +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index dbf43f31ee25..17c84d3d313f 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -54,6 +54,7 @@ const ( LandingPageHome LandingPage = "/" LandingPageExplore LandingPage = "/explore" LandingPageOrganizations LandingPage = "/explore/organizations" + LandingPageLogin LandingPage = "/user/login" ) // enumerates all the types of captchas @@ -648,6 +649,8 @@ func NewContext() { LandingPageURL = LandingPageExplore case "organizations": LandingPageURL = LandingPageOrganizations + case "login": + LandingPageURL = LandingPageLogin default: LandingPageURL = LandingPageHome } @@ -1090,4 +1093,5 @@ func NewServices() { newMigrationsService() newIndexerService() newTaskService() + NewQueueService() } diff --git a/modules/setting/task.go b/modules/setting/task.go index 97704d4a4da6..81ed39a9fb90 100644 --- a/modules/setting/task.go +++ b/modules/setting/task.go @@ -4,22 +4,15 @@ package setting -var ( - // Task settings - Task = struct { - QueueType string - QueueLength int - QueueConnStr string - }{ - QueueType: ChannelQueueType, - QueueLength: 1000, - QueueConnStr: "addrs=127.0.0.1:6379 db=0", - } -) - func newTaskService() { - sec := Cfg.Section("task") - Task.QueueType = sec.Key("QUEUE_TYPE").MustString(ChannelQueueType) - Task.QueueLength = sec.Key("QUEUE_LENGTH").MustInt(1000) - Task.QueueConnStr = sec.Key("QUEUE_CONN_STR").MustString("addrs=127.0.0.1:6379 db=0") + taskSec := Cfg.Section("task") + queueTaskSec := Cfg.Section("queue.task") + switch taskSec.Key("QUEUE_TYPE").MustString(ChannelQueueType) { + case ChannelQueueType: + queueTaskSec.Key("TYPE").MustString("persistable-channel") + case RedisQueueType: + queueTaskSec.Key("TYPE").MustString("redis") + } + queueTaskSec.Key("LENGTH").MustInt(taskSec.Key("QUEUE_LENGTH").MustInt(1000)) + queueTaskSec.Key("CONN_STR").MustString(taskSec.Key("QUEUE_CONN_STR").MustString("addrs=127.0.0.1:6379 db=0")) } diff --git a/modules/task/queue.go b/modules/task/queue.go deleted file mode 100644 index ddee0b3d4627..000000000000 --- a/modules/task/queue.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2019 Gitea. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package task - -import "code.gitea.io/gitea/models" - -// Queue defines an interface to run task queue -type Queue interface { - Run() error - Push(*models.Task) error - Stop() -} diff --git a/modules/task/queue_channel.go b/modules/task/queue_channel.go deleted file mode 100644 index da541f47551f..000000000000 --- a/modules/task/queue_channel.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package task - -import ( - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" -) - -var ( - _ Queue = &ChannelQueue{} -) - -// ChannelQueue implements -type ChannelQueue struct { - queue chan *models.Task -} - -// NewChannelQueue create a memory channel queue -func NewChannelQueue(queueLen int) *ChannelQueue { - return &ChannelQueue{ - queue: make(chan *models.Task, queueLen), - } -} - -// Run starts to run the queue -func (c *ChannelQueue) Run() error { - for task := range c.queue { - err := Run(task) - if err != nil { - log.Error("Run task failed: %s", err.Error()) - } - } - return nil -} - -// Push will push the task ID to queue -func (c *ChannelQueue) Push(task *models.Task) error { - c.queue <- task - return nil -} - -// Stop stop the queue -func (c *ChannelQueue) Stop() { - close(c.queue) -} diff --git a/modules/task/queue_redis.go b/modules/task/queue_redis.go deleted file mode 100644 index 127de0cdbf1d..000000000000 --- a/modules/task/queue_redis.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package task - -import ( - "encoding/json" - "errors" - "strconv" - "strings" - "time" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" - - "github.com/go-redis/redis" -) - -var ( - _ Queue = &RedisQueue{} -) - -type redisClient interface { - RPush(key string, args ...interface{}) *redis.IntCmd - LPop(key string) *redis.StringCmd - Ping() *redis.StatusCmd -} - -// RedisQueue redis queue -type RedisQueue struct { - client redisClient - queueName string - closeChan chan bool -} - -func parseConnStr(connStr string) (addrs, password string, dbIdx int, err error) { - fields := strings.Fields(connStr) - for _, f := range fields { - items := strings.SplitN(f, "=", 2) - if len(items) < 2 { - continue - } - switch strings.ToLower(items[0]) { - case "addrs": - addrs = items[1] - case "password": - password = items[1] - case "db": - dbIdx, err = strconv.Atoi(items[1]) - if err != nil { - return - } - } - } - return -} - -// NewRedisQueue creates single redis or cluster redis queue -func NewRedisQueue(addrs string, password string, dbIdx int) (*RedisQueue, error) { - dbs := strings.Split(addrs, ",") - var queue = RedisQueue{ - queueName: "task_queue", - closeChan: make(chan bool), - } - if len(dbs) == 0 { - return nil, errors.New("no redis host found") - } else if len(dbs) == 1 { - queue.client = redis.NewClient(&redis.Options{ - Addr: strings.TrimSpace(dbs[0]), // use default Addr - Password: password, // no password set - DB: dbIdx, // use default DB - }) - } else { - // cluster will ignore db - queue.client = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: dbs, - Password: password, - }) - } - if err := queue.client.Ping().Err(); err != nil { - return nil, err - } - return &queue, nil -} - -// Run starts to run the queue -func (r *RedisQueue) Run() error { - for { - select { - case <-r.closeChan: - return nil - case <-time.After(time.Millisecond * 100): - } - - bs, err := r.client.LPop(r.queueName).Bytes() - if err != nil { - if err != redis.Nil { - log.Error("LPop failed: %v", err) - } - time.Sleep(time.Millisecond * 100) - continue - } - - var task models.Task - err = json.Unmarshal(bs, &task) - if err != nil { - log.Error("Unmarshal task failed: %s", err.Error()) - } else { - err = Run(&task) - if err != nil { - log.Error("Run task failed: %s", err.Error()) - } - } - } -} - -// Push implements Queue -func (r *RedisQueue) Push(task *models.Task) error { - bs, err := json.Marshal(task) - if err != nil { - return err - } - return r.client.RPush(r.queueName, bs).Err() -} - -// Stop stop the queue -func (r *RedisQueue) Stop() { - r.closeChan <- true -} diff --git a/modules/task/task.go b/modules/task/task.go index 64744afe7a4c..416f0c696a99 100644 --- a/modules/task/task.go +++ b/modules/task/task.go @@ -8,14 +8,15 @@ import ( "fmt" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" - "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/structs" ) // taskQueue is a global queue of tasks -var taskQueue Queue +var taskQueue queue.Queue // Run a task func Run(t *models.Task) error { @@ -23,38 +24,32 @@ func Run(t *models.Task) error { case structs.TaskTypeMigrateRepo: return runMigrateTask(t) default: - return fmt.Errorf("Unknow task type: %d", t.Type) + return fmt.Errorf("Unknown task type: %d", t.Type) } } // Init will start the service to get all unfinished tasks and run them func Init() error { - switch setting.Task.QueueType { - case setting.ChannelQueueType: - taskQueue = NewChannelQueue(setting.Task.QueueLength) - case setting.RedisQueueType: - var err error - addrs, pass, idx, err := parseConnStr(setting.Task.QueueConnStr) - if err != nil { - return err - } - taskQueue, err = NewRedisQueue(addrs, pass, idx) - if err != nil { - return err - } - default: - return fmt.Errorf("Unsupported task queue type: %v", setting.Task.QueueType) + taskQueue = queue.CreateQueue("task", handle, &models.Task{}) + + if taskQueue == nil { + return fmt.Errorf("Unable to create Task Queue") } - go func() { - if err := taskQueue.Run(); err != nil { - log.Error("taskQueue.Run end failed: %v", err) - } - }() + go graceful.GetManager().RunWithShutdownFns(taskQueue.Run) return nil } +func handle(data ...queue.Data) { + for _, datum := range data { + task := datum.(*models.Task) + if err := Run(task); err != nil { + log.Error("Run task failed: %v", err) + } + } +} + // MigrateRepository add migration repository to task func MigrateRepository(doer, u *models.User, opts base.MigrateOptions) error { task, err := models.CreateMigrateTask(doer, u, opts) diff --git a/modules/webhook/dingtalk.go b/modules/webhook/dingtalk.go index 4869c1a37c3c..0d569f412076 100644 --- a/modules/webhook/dingtalk.go +++ b/modules/webhook/dingtalk.go @@ -132,7 +132,7 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { } func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) { - text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter) + text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ MsgType: "actionCard", @@ -148,7 +148,7 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) { } func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) { - text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter) + text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ MsgType: "actionCard", @@ -163,7 +163,7 @@ func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayloa } func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { - text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter) + text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ MsgType: "actionCard", @@ -236,7 +236,7 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e } func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) { - text, _ := getReleasePayloadInfo(p, noneLinkFormatter) + text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ MsgType: "actionCard", diff --git a/modules/webhook/discord.go b/modules/webhook/discord.go index ea69f36fe791..c1e8421228ef 100644 --- a/modules/webhook/discord.go +++ b/modules/webhook/discord.go @@ -227,7 +227,7 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo } func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) { - text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter) + text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ Username: meta.Username, @@ -249,7 +249,7 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa } func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) { - text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter) + text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ Username: discord.Username, @@ -271,7 +271,7 @@ func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordM } func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) { - text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter) + text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ Username: meta.Username, @@ -368,7 +368,7 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (* } func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) { - text, color := getReleasePayloadInfo(p, noneLinkFormatter) + text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ Username: meta.Username, diff --git a/modules/webhook/general.go b/modules/webhook/general.go index 28c3b2730d75..bc9a10b5291b 100644 --- a/modules/webhook/general.go +++ b/modules/webhook/general.go @@ -25,8 +25,7 @@ func htmlLinkFormatter(url string, text string) string { return fmt.Sprintf(`%s`, url, html.EscapeString(text)) } -func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (string, string, string, int) { - senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) +func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle) @@ -35,34 +34,36 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (str switch p.Action { case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Issue opened: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue opened: %s", repoLink, titleLink) color = orangeColor case api.HookIssueClosed: - text = fmt.Sprintf("[%s] Issue closed: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue closed: %s", repoLink, titleLink) color = redColor case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue re-opened: %s", repoLink, titleLink) case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Issue edited: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue edited: %s", repoLink, titleLink) case api.HookIssueAssigned: - text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", repoLink, - linkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), - titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink, + linkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), titleLink) color = greenColor case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue unassigned: %s", repoLink, titleLink) case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue labels updated: %s", repoLink, titleLink) case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue labels cleared: %s", repoLink, titleLink) case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue synchronized: %s", repoLink, titleLink) case api.HookIssueMilestoned: mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) - text = fmt.Sprintf("[%s] Issue milestoned to %s: %s by %s", repoLink, - linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue milestoned to %s: %s", repoLink, + linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink) case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Issue milestone cleared: %s by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) + } + if withSender { + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) } var attachmentText string @@ -73,8 +74,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter) (str return text, issueTitle, attachmentText, color } -func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter) (string, string, string, int) { - senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) +func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) issueTitle := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := linkFormatter(p.PullRequest.URL, issueTitle) @@ -83,43 +83,45 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm switch p.Action { case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Pull request %s opened by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink) color = greenColor case api.HookIssueClosed: if p.PullRequest.HasMerged { - text = fmt.Sprintf("[%s] Pull request %s merged by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request merged: %s", repoLink, titleLink) color = purpleColor } else { - text = fmt.Sprintf("[%s] Pull request %s closed by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request closed: %s", repoLink, titleLink) color = redColor } case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Pull request %s re-opened by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink) case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Pull request %s edited by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink) case api.HookIssueAssigned: list := make([]string, len(p.PullRequest.Assignees)) for i, user := range p.PullRequest.Assignees { list[i] = linkFormatter(setting.AppURL+user.UserName, user.UserName) } - text = fmt.Sprintf("[%s] Pull request %s assigned to %s by %s", repoLink, - strings.Join(list, ", "), - titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request assigned: %s to %s", repoLink, + strings.Join(list, ", "), titleLink) color = greenColor case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Pull request %s unassigned by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request unassigned: %s", repoLink, titleLink) case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Pull request %s labels updated by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request labels updated: %s", repoLink, titleLink) case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Pull request %s labels cleared by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request labels cleared: %s", repoLink, titleLink) case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Pull request %s synchronized by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request synchronized: %s", repoLink, titleLink) case api.HookIssueMilestoned: mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) - text = fmt.Sprintf("[%s] Pull request %s milestoned to %s by %s", repoLink, - linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request milestoned: %s to %s", repoLink, + linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink) case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Pull request %s milestone cleared by %s", repoLink, titleLink, senderLink) + text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink) + } + if withSender { + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) } var attachmentText string @@ -130,28 +132,29 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm return text, issueTitle, attachmentText, color } -func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter) (text string, color int) { - senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) +func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) refLink := linkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) switch p.Action { case api.HookReleasePublished: - text = fmt.Sprintf("[%s] Release %s created by %s", repoLink, refLink, senderLink) + text = fmt.Sprintf("[%s] Release created: %s", repoLink, refLink) color = greenColor case api.HookReleaseUpdated: - text = fmt.Sprintf("[%s] Release %s updated by %s", repoLink, refLink, senderLink) + text = fmt.Sprintf("[%s] Release updated: %s", repoLink, refLink) color = yellowColor case api.HookReleaseDeleted: - text = fmt.Sprintf("[%s] Release %s deleted by %s", repoLink, refLink, senderLink) + text = fmt.Sprintf("[%s] Release deleted: %s", repoLink, refLink) color = redColor } + if withSender { + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + } return text, color } -func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter) (string, string, int) { - senderLink := linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) +func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter, withSender bool) (string, string, int) { repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) issueTitle := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) @@ -168,18 +171,21 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo switch p.Action { case api.HookIssueCommentCreated: - text = fmt.Sprintf("[%s] New comment on %s %s by %s", repoLink, typ, titleLink, senderLink) + text = fmt.Sprintf("[%s] New comment on %s %s", repoLink, typ, titleLink) if p.IsPull { color = greenColorLight } else { color = orangeColorLight } case api.HookIssueCommentEdited: - text = fmt.Sprintf("[%s] Comment on %s %s edited by %s", repoLink, typ, titleLink, senderLink) + text = fmt.Sprintf("[%s] Comment edited on %s %s", repoLink, typ, titleLink) case api.HookIssueCommentDeleted: - text = fmt.Sprintf("[%s] Comment on %s %s deleted by %s", repoLink, typ, titleLink, senderLink) + text = fmt.Sprintf("[%s] Comment deleted on %s %s", repoLink, typ, titleLink) color = redColor } + if withSender { + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + } return text, issueTitle, color } diff --git a/modules/webhook/msteams.go b/modules/webhook/msteams.go index 4c148421fe15..b47725209d95 100644 --- a/modules/webhook/msteams.go +++ b/modules/webhook/msteams.go @@ -266,7 +266,7 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { } func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) { - text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter) + text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ Type: "MessageCard", @@ -308,7 +308,7 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) { } func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) { - text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter) + text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ Type: "MessageCard", @@ -350,7 +350,7 @@ func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, } func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, error) { - text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter) + text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ Type: "MessageCard", @@ -503,7 +503,7 @@ func getMSTeamsRepositoryPayload(p *api.RepositoryPayload) (*MSTeamsPayload, err } func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) { - text, color := getReleasePayloadInfo(p, noneLinkFormatter) + text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ Type: "MessageCard", diff --git a/modules/webhook/slack.go b/modules/webhook/slack.go index 508cb13b8fb7..11ad4c1b8bdf 100644 --- a/modules/webhook/slack.go +++ b/modules/webhook/slack.go @@ -144,7 +144,7 @@ func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, e } func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) { - text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter) + text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true) pl := &SlackPayload{ Channel: slack.Channel, @@ -167,7 +167,7 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload } func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { - text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter) + text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true) return &SlackPayload{ Channel: slack.Channel, @@ -184,7 +184,7 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) ( } func getSlackReleasePayload(p *api.ReleasePayload, slack *SlackMeta) (*SlackPayload, error) { - text, _ := getReleasePayloadInfo(p, SlackLinkFormatter) + text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true) return &SlackPayload{ Channel: slack.Channel, @@ -239,7 +239,7 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e } func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) { - text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter) + text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true) pl := &SlackPayload{ Channel: slack.Channel, diff --git a/modules/webhook/slack_test.go b/modules/webhook/slack_test.go index fe4f1384fcdd..a418c8e2e284 100644 --- a/modules/webhook/slack_test.go +++ b/modules/webhook/slack_test.go @@ -70,7 +70,7 @@ func TestSlackReleasePayload(t *testing.T) { require.Nil(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Release created by ", pl.Text) + assert.Equal(t, "[] Release created: by ", pl.Text) } func TestSlackPullRequestPayload(t *testing.T) { @@ -84,5 +84,5 @@ func TestSlackPullRequestPayload(t *testing.T) { require.Nil(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Pull request opened by ", pl.Text) + assert.Equal(t, "[] Pull request opened: by ", pl.Text) } diff --git a/modules/webhook/telegram.go b/modules/webhook/telegram.go index a98d47d55c73..42adb40be28b 100644 --- a/modules/webhook/telegram.go +++ b/modules/webhook/telegram.go @@ -125,7 +125,7 @@ func getTelegramPushPayload(p *api.PushPayload) (*TelegramPayload, error) { } func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) { - text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter) + text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ Message: text + "\n\n" + attachmentText, @@ -133,7 +133,7 @@ func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) { } func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) { - text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter) + text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ Message: text + "\n" + p.Comment.Body, @@ -141,7 +141,7 @@ func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayloa } func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload, error) { - text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter) + text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ Message: text + "\n" + attachmentText, @@ -166,7 +166,7 @@ func getTelegramRepositoryPayload(p *api.RepositoryPayload) (*TelegramPayload, e } func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error) { - text, _ := getReleasePayloadInfo(p, htmlLinkFormatter) + text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ Message: text + "\n", diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index 8bd81d0de183..c4d9009f9295 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -733,6 +733,8 @@ monitor.desc=Описание monitor.start=Начален час monitor.execute_time=Време за изпълнение + + notices.system_notice_list=Системни известия notices.actions=Действия notices.select_all=Избери всички diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index ef2c0178e8e8..b55c98557021 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1944,6 +1944,8 @@ monitor.desc=Popis monitor.start=Čas zahájení monitor.execute_time=Doba provádění + + notices.system_notice_list=Systémová oznámení notices.view_detail_header=Zobrazit detaily oznámení notices.actions=Akce diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 69a5322e1bb7..881e6561fe7d 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -10,6 +10,7 @@ link_account=Account verbinden register=Registrieren website=Webseite version=Version +powered_by=Powered by %s page=Seite template=Template language=Sprache @@ -1053,6 +1054,7 @@ pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich. pulls.required_status_check_administrator=Als Administrator kannst du diesen Pull-Request weiterhin zusammenführen. pulls.blocked_by_approvals=Dieser Pull-Request hat noch nicht genügend Zustimmungen. %d von %d Zustimmungen erteilt. +pulls.blocked_by_rejection=Dieser Pull-Request hat Änderungen, die von einem offiziellen Reviewer angefragt wurden. pulls.can_auto_merge_desc=Dieser Pull-Request kann automatisch zusammengeführt werden. pulls.cannot_auto_merge_desc=Dieser Pull-Request kann nicht automatisch zusammengeführt werden, da es Konflikte gibt. pulls.cannot_auto_merge_helper=Bitte manuell zusammenführen, um die Konflikte zu lösen. @@ -1416,6 +1418,8 @@ settings.update_protect_branch_success=Branch-Schutz für den Branch „%s“ wu settings.remove_protected_branch_success=Branch-Schutz für den Branch „%s“ wurde deaktiviert. settings.protected_branch_deletion=Branch-Schutz deaktivieren settings.protected_branch_deletion_desc=Wenn du den Branch-Schutz deaktivierst, können alle Nutzer mit Schreibrechten auf den Branch pushen. Fortfahren? +settings.block_rejected_reviews=Merge bei abgelehnten Reviews blockieren +settings.block_rejected_reviews_desc=Mergen ist nicht möglich, wenn Änderungen durch offizielle Reviewer angefragt werden, auch wenn es genügend Zustimmungen gibt. settings.default_branch_desc=Wähle einen Standardbranch für Pull-Requests und Code-Commits: settings.choose_branch=Wähle einen Branch … settings.no_protected_branch=Es gibt keine geschützten Branches. @@ -2022,6 +2026,8 @@ monitor.process.cancel=Prozess abbrechen monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen monitor.process.cancel_notices=Abbrechen: %s? + + notices.system_notice_list=Systemmitteilungen notices.view_detail_header=Meldungsdetails ansehen notices.actions=Aktionen diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f010c0db675b..7cb46a9a1160 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -10,6 +10,7 @@ link_account = Link Account register = Register website = Website version = Version +powered_by = Powered by %s page = Page template = Template language = Language @@ -1054,6 +1055,7 @@ pulls.is_checking = "Merge conflict checking is in progress. Try again in few mo pulls.required_status_check_failed = Some required checks were not successful. pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." +pulls.blocked_by_rejection = "This Pull Request has changes requested by an official reviewer." pulls.can_auto_merge_desc = This pull request can be merged automatically. pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. @@ -1408,7 +1410,7 @@ settings.protect_check_status_contexts_list = Status checks found in the last we settings.protect_required_approvals = Required approvals: settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews. settings.protect_approvals_whitelist_enabled = Restrict approvals to whitelisted users or teams -settings.protect_approvals_whitelist_enabled_desc = Only reviews from whitelisted users or teams will count to the required approvals. Without approval whitelist, reviews from anyone with write access count to the required approvals. +settings.protect_approvals_whitelist_enabled_desc = Only reviews from whitelisted users or teams will count to the required approvals. Without approval whitelist, reviews from anyone with write access count to the required approvals. settings.protect_approvals_whitelist_users = Whitelisted reviewers: settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: settings.add_protected_branch = Enable protection @@ -1417,6 +1419,8 @@ settings.update_protect_branch_success = Branch protection for branch '%s' has b settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. settings.protected_branch_deletion = Disable Branch Protection settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? +settings.block_rejected_reviews = Block merge on rejected reviews +settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals. settings.default_branch_desc = Select a default repository branch for pull requests and code commits: settings.choose_branch = Choose a branch… settings.no_protected_branch = There are no protected branches. @@ -2022,6 +2026,54 @@ monitor.execute_time = Execution Time monitor.process.cancel = Cancel process monitor.process.cancel_desc = Cancelling a process may cause data loss monitor.process.cancel_notices = Cancel: %s? +monitor.queues = Queues +monitor.queue = Queue: %s +monitor.queue.name = Name +monitor.queue.type = Type +monitor.queue.exemplar = Exemplar Type +monitor.queue.numberworkers = Number of Workers +monitor.queue.maxnumberworkers = Max Number of Workers +monitor.queue.review = Review Config +monitor.queue.review_add = Review/Add Workers +monitor.queue.configuration = Initial Configuration +monitor.queue.nopool.title = No Worker Pool +monitor.queue.nopool.desc = This queue wraps other queues and does not itself have a worker pool. +monitor.queue.wrapped.desc = A wrapped queue wraps a slow starting queue, buffering queued requests in a channel. It does not have a worker pool itself. +monitor.queue.persistable-channel.desc = A persistable-channel wraps two queues, a channel queue that has its own worker pool and a level queue for persisted requests from previous shutdowns. It does not have a worker pool itself. +monitor.queue.pool.timeout = Timeout +monitor.queue.pool.addworkers.title = Add Workers +monitor.queue.pool.addworkers.submit = Add Workers +monitor.queue.pool.addworkers.desc = Add Workers to this pool with or without a timeout. If you set a timeout these workers will be removed from the pool after the timeout has lapsed. +monitor.queue.pool.addworkers.numberworkers.placeholder = Number of Workers +monitor.queue.pool.addworkers.timeout.placeholder = Set to 0 for no timeout +monitor.queue.pool.addworkers.mustnumbergreaterzero = Number of Workers to add must be greater than zero +monitor.queue.pool.addworkers.musttimeoutduration = Timeout must be a golang duration eg. 5m or be 0 + +monitor.queue.settings.title = Pool Settings +monitor.queue.settings.desc = Pools dynamically grow with a boost in response to their worker queue blocking. These changes will not affect current worker groups. +monitor.queue.settings.timeout = Boost Timeout +monitor.queue.settings.timeout.placeholder = Currently %[1]v +monitor.queue.settings.timeout.error = Timeout must be a golang duration eg. 5m or be 0 +monitor.queue.settings.numberworkers = Boost Number of Workers +monitor.queue.settings.numberworkers.placeholder = Currently %[1]d +monitor.queue.settings.numberworkers.error = Number of Workers to add must be greater than or equal to zero +monitor.queue.settings.maxnumberworkers = Max Number of workers +monitor.queue.settings.maxnumberworkers.placeholder = Currently %[1]d +monitor.queue.settings.maxnumberworkers.error = Max number of workers must be a number +monitor.queue.settings.submit = Update Settings +monitor.queue.settings.changed = Settings Updated +monitor.queue.settings.blocktimeout = Current Block Timeout +monitor.queue.settings.blocktimeout.value = %[1]v + +monitor.queue.pool.none = This queue does not have a Pool +monitor.queue.pool.added = Worker Group Added +monitor.queue.pool.max_changed = Maximum number of workers changed +monitor.queue.pool.workers.title = Active Worker Groups +monitor.queue.pool.workers.none = No worker groups. +monitor.queue.pool.cancel = Shutdown Worker Group +monitor.queue.pool.cancelling = Worker Group shutting down +monitor.queue.pool.cancel_notices = Shutdown this group of %s workers? +monitor.queue.pool.cancel_desc = Leaving a queue without any worker groups may cause requests to block indefinitely. notices.system_notice_list = System Notices notices.view_detail_header = View Notice Details diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index e1ae174fd980..f504c80f0411 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -66,6 +66,7 @@ forks=Forks activities=Actividad pull_requests=Pull Requests issues=Incidencias +milestones=Hitos cancel=Cancelar add=Añadir @@ -269,6 +270,7 @@ authorize_application_description=Si concede el acceso, podrá acceder y escribi authorize_title=¿Autorizar a "%s" a acceder a su cuenta? authorization_failed=Autorización fallida authorization_failed_desc=La autorización ha fallado porque hemos detectado una solicitud no válida. Por favor, póngase en contacto con el mantenedor de la aplicación que ha intentado autorizar. +sspi_auth_failed=Fallo en la autenticación SSPI [mail] activate_account=Por favor, active su cuenta @@ -302,6 +304,8 @@ CommitChoice=Hacer commit de la elección TreeName=Ruta del archivo Content=Contenido +SSPISeparatorReplacement=Separador +SSPIDefaultLanguage=Idioma predeterminado require_error=` no puede estar vacío.` alpha_dash_error=` solo debe contener caracteres alfanuméricos, guiones medios ('-') y guiones bajos ('_').` @@ -317,6 +321,7 @@ glob_pattern_error=` el patrón globo no es válido: %s.` unknown_error=Error desconocido: captcha_incorrect=El código CAPTCHA no es correcto. password_not_match=Las contraseñas no coinciden. +lang_select_error=Seleccione un idioma de la lista. username_been_taken=El nombre de usuario ya está en uso. repo_name_been_taken=El nombre del repositorio ya está usado. @@ -328,6 +333,11 @@ team_no_units_error=Permitir el acceso a por lo menos una sección del repositor email_been_used=La dirección de correo electrónico ya está usada. openid_been_used=La dirección OpenID '%s' ya está usada. username_password_incorrect=El nombre de usuario o la contraseña son incorrectos. +password_complexity=La contraseña no cumple los requisitos de complejidad: +password_lowercase_one=Al menos una letra minúscula +password_uppercase_one=Al menos una letra mayúscula +password_digit_one=Al menos un dígito +password_special_one=Al menos un carácter especial (puntuación, corchetes, comillas, etc.) enterred_invalid_repo_name=El nombre de repositorio que ha entrado es incorrecto. enterred_invalid_owner_name=El nuevo nombre de usuario no es válido. enterred_invalid_password=La contraseña que ha introducido es incorrecta. @@ -581,6 +591,7 @@ email_notifications.submit=Establecer preferencias de correo electrónico owner=Propietario repo_name=Nombre del repositorio repo_name_helper=Un buen nombre de repositorio está compuesto por palabras clave cortas, memorables y únicas. +repo_size=Tamaño del repositorio template=Plantilla template_select=Seleccionar una plantilla. template_helper=Hacer del repositorio una plantilla @@ -626,7 +637,11 @@ reactions_more=y %d más template.items=Elementos de plantilla template.git_content=Contenido Git (rama predeterminada) +template.git_hooks=Git Hooks +template.git_hooks_tooltip=Actualmente no puede modificar o eliminar git hooks una vez añadidos. Seleccione esta opción sólo si confía en el repositorio de plantillas. +template.webhooks=Webhooks template.topics=Temas +template.avatar=Avatar template.one_item=Debe seleccionar al menos un elemento de plantilla template.invalid=Debe seleccionar una plantilla de repositorio @@ -865,6 +880,10 @@ issues.closed_title=Cerrada issues.num_comments=%d comentarios issues.commented_at=`comentado %s` issues.delete_comment_confirm=¿Seguro que deseas eliminar este comentario? +issues.context.copy_link=Copiar enlace +issues.context.quote_reply=Citar respuesta +issues.context.edit=Editar +issues.context.delete=Eliminar issues.no_content=Aún no existe contenido. issues.close_issue=Cerrar issues.close_comment_issue=Comentar y cerrar @@ -874,6 +893,12 @@ issues.create_comment=Comentar issues.closed_at=`cerró %[2]s` issues.reopened_at=`reabrió %[2]s` issues.commit_ref_at=`mencionada esta incidencia en un commit %[2]s` +issues.ref_issue_from=`referenció esta incidencia %[4]s %[2]s` +issues.ref_pull_from=`referenció este pull request %[4]s %[2]s` +issues.ref_closing_from=`referenció un pull request %[4]s que cerrará esta incidencia %[2]s` +issues.ref_reopening_from=`referenció un pull request %[4]s que reabrirá esta incidencia %[2]s` +issues.ref_closed_from=`cerró esta incidencia %[4]s %[2]s` +issues.ref_reopened_from=`reabrió esta incidencia %[4]s %[2]s` issues.ref_from=`de %[1]s` issues.poster=Autor issues.collaborator=Colaborador @@ -933,6 +958,7 @@ issues.add_time=Añadir tiempo gastado manualmente issues.add_time_short=Añadir tiempo gastado issues.add_time_cancel=Cancelar issues.add_time_history=`añadió tiempo gastado %s` +issues.del_time_history=`eliminado el tiempo gastado %s` issues.add_time_hours=Horas issues.add_time_minutes=Minutos issues.add_time_sum_to_small=No se ha entrado tiempo. @@ -1006,6 +1032,7 @@ pulls.no_results=Sin resultados. pulls.nothing_to_compare=Estas ramas son iguales. No hay necesidad para crear un pull request. pulls.has_pull_request=`Ya existe un pull request entre estas ramas: %[2]s#%[3]d` pulls.create=Crear Pull Request +pulls.title_desc=desea fusionar %[1]d commits de %[2]s en %[3]s pulls.merged_title_desc=fusionados %[1]d commits de %[2]s en %[3]s %[4]s pulls.tab_conversation=Conversación pulls.tab_commits=Commits @@ -1014,6 +1041,7 @@ pulls.reopen_to_merge=Vuelva a abrir este Pull Request para realizar una fusión pulls.cant_reopen_deleted_branch=Este pull request no se puede reabrir porque la rama fue eliminada. pulls.merged=Fusionado pulls.merged_as=El Pull Request se ha fusionado como %[2]s. +pulls.is_closed=El pull request ha sido cerrado. pulls.has_merged=El pull request ha sido fusionado. pulls.title_wip_desc=`Comience el título con %s para prevenir que el pull request se fusione accidentalmente.` pulls.cannot_merge_work_in_progress=Este pull request está marcado como un trabajo en progreso. Elimine el prefijo %s del título cuando esté listo @@ -1036,6 +1064,9 @@ pulls.rebase_merge_commit_pull_request=Hacer Rebase y Fusionar (--no-ff) pulls.squash_merge_pull_request=Hacer Squash y Fusionar pulls.invalid_merge_option=No puede utilizar esta opción de combinación para esta solicitud de extracción. pulls.merge_conflict=Fusión fallida: Hubo un conflicto mientras se fusionaba: %[1]s
%[2]s
Pista: Pruebe una estrategia diferente +pulls.rebase_conflict=Fusión fallida: Hubo un conflicto mientras se rebasaba el commit: %[1]s
%[2]s
%[3]s
Sugerencia:Prueba una estrategia diferente +pulls.unrelated_histories=Fusionar Fallidos: El jefe de fusión y la base no comparten un historial común. Pista: Prueba una estrategia diferente +pulls.merge_out_of_date=Fusión fallida: Mientras se generaba la fusión, la base fue actualizada. Pista: Inténtelo de nuevo. pulls.open_unmerged_pull_exists=`No puede realizar la reapertura porque hay un pull request pendiente (#%d) con propiedades idénticas.` pulls.status_checking=Algunas comprobaciones están pendientes pulls.status_checks_success=Todas las comprobaciones han sido exitosas @@ -1352,6 +1383,10 @@ settings.protected_branch_can_push_yes=Puede hacer push settings.protected_branch_can_push_no=No puede hacer push settings.branch_protection=Proteccion de la rama '%s' settings.protect_this_branch=Activar protección de rama +settings.protect_disable_push=Deshabilitar Push +settings.protect_disable_push_desc=No se permitirá hacer push a esta rama. +settings.protect_enable_push=Habilitar Push +settings.protect_enable_push_desc=Cualquier usuario con permiso de escritura podrá hacer push a esta rama (pero no push --force). settings.protect_whitelist_deploy_keys=Poner en lista blanca las claves de implementación con permisos de hacer push settings.protect_whitelist_users=Usuarios en la lista blanca para hacer push: settings.protect_whitelist_search_users=Buscar usuarios… @@ -1365,6 +1400,7 @@ settings.protect_check_status_contexts=Habilitar comprobación de estado settings.protect_check_status_contexts_desc=Requiere comprobaciones de estado para pasar antes de fusionar. Elija qué los controles de estado deben pasar antes de que las ramas puedan ser fusionadas en una rama que coincida con esta regla. Cuando está habilitada, los commits deben ser primero empujados a otra rama y luego fusionados, o empujados directamente a una rama que coincida con esta regla después de que hayan pasado los comprobaciones de estado. Si no se selecciona ningún contexto, la última confirmación debe tener éxito independientemente del contexto. settings.protect_check_status_contexts_list=Comprobaciones de estado para este repositorio encontradas durante la semana pasada settings.protect_required_approvals=Aprobaciones requeridas: +settings.protect_required_approvals_desc=Permite fusionar sólo los pull request con suficientes comentarios positivos. settings.protect_approvals_whitelist_users=Lista blanca de usuarios revisores: settings.protect_approvals_whitelist_teams=Lista blanca de equipos revisores: settings.add_protected_branch=Activar protección @@ -1398,7 +1434,20 @@ settings.lfs_filelist=No hay archivos LFS almacenados en este repositorio settings.lfs_no_lfs_files=No hay archivos LFS almacenados en este repositorio settings.lfs_findcommits=Encontrar consignas settings.lfs_lfs_file_no_commits=No se encontraron commits para este archivo LFS +settings.lfs_noattribute=Esta ruta no tiene el atributo bloqueable en la rama por defecto settings.lfs_delete=Eliminar archivo LFS con el OID %s +settings.lfs_delete_warning=Eliminar un archivo LFS puede causar errores de 'objeto no existe' en la compra. ¿Está seguro? +settings.lfs_findpointerfiles=Buscar de punteros LFS +settings.lfs_locks=Bloqueos +settings.lfs_invalid_locking_path=Ruta no válida: %s +settings.lfs_invalid_lock_directory=No se puede bloquear el directorio: %s +settings.lfs_lock_already_exists=El bloqueo ya existe: %s +settings.lfs_lock=Bloquear +settings.lfs_lock_path=Ruta del archivo a bloquear... +settings.lfs_locks_no_locks=Sin bloqueos +settings.lfs_lock_file_no_exist=El archivo bloqueado no existe en la rama por defecto +settings.lfs_force_unlock=Forzar desbloqueo +settings.lfs_pointers.found=Encontrados %d punteros - %d asociados, %d no asociados (%d falta en el almacén) settings.lfs_pointers.sha=Blob SHA settings.lfs_pointers.oid=OID settings.lfs_pointers.inRepo=En repositorio @@ -1522,6 +1571,7 @@ team_name=Nombre del equipo team_desc=Descripción team_name_helper=Los nombres de equipos deben ser cortos y destacados. team_desc_helper=Describa el propósito o rol del equipo. +team_access_desc=Acceso al repositorio team_permission_desc=Permiso team_unit_desc=Permitir acceso a las secciones del repositorio @@ -1568,6 +1618,8 @@ members.invite_now=Invitar teams.join=Unirse teams.leave=Abandonar +teams.can_create_org_repo=Crear repositorios +teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio. teams.read_access=Acceso de Lectura teams.read_access_helper=Los miembros pueden ver y clonar los repositorios del equipo. teams.write_access=Acceso de Escritura @@ -1587,15 +1639,24 @@ teams.delete_team_success=El equipo ha sido eliminado. teams.read_permission_desc=Este equipo tiene permisos de Lectura: los miembros pueden ver y clonar los repositorios del equipo. teams.write_permission_desc=Este equipo tiene permisos de Escritura: los miembros pueden ver y hacer push a los repositorios del equipo. teams.admin_permission_desc=Este equipo tiene permisos de Administración: los miembros pueden ver, hacer push y añadir colaboradores a los repositorios del equipo. +teams.create_repo_permission_desc=Adicionalmente, este equipo concede permiso Crear repositorio: los miembros pueden crear nuevos repositorios en la organización. teams.repositories=Repositorios del equipo teams.search_repo_placeholder=Buscar repositorio… teams.remove_all_repos_title=Eliminar todos los repositorios del equipo +teams.remove_all_repos_desc=Esto eliminará todos los repositorios del equipo. teams.add_all_repos_title=Añadir todos los repositorios +teams.add_all_repos_desc=Esto añadirá todos los repositorios de la organización al equipo. teams.add_nonexistent_repo=El repositorio que estás intentando añadir no existe, por favor, créalo primero. teams.add_duplicate_users=El usuario ya es miembro del equipo. teams.repos.none=Este equipo no tiene repositorios accesibles. teams.members.none=No hay miembros en este equipo. +teams.specific_repositories=Repositorios específicos +teams.specific_repositories_helper=Los miembros sólo tendrán acceso a repositorios explícitamente agregados al equipo. Seleccionar este no eliminará automáticamente los repositorios ya añadidos con Todos los repositorios. teams.all_repositories=Todos los repositorios +teams.all_repositories_helper=El equipo tiene acceso a todos los repositorios. Seleccionar esto añadirá todos los repositorios existentes al equipo. +teams.all_repositories_read_permission_desc=Este equipo concede Leer a todos los repositorios: los miembros pueden ver y clonar repositorios. +teams.all_repositories_write_permission_desc=Este equipo concede Escribir a todos los repositorios: los miembros pueden leer y enviar a los repositorios. +teams.all_repositories_admin_permission_desc=Este equipo concede a Administrador acceso a todos los repositorios: los miembros pueden leer, enviar y agregar colaboradores a los repositorios. [admin] dashboard=Panel de control @@ -1770,6 +1831,16 @@ auths.oauth2_authURL=URL de Autorización auths.oauth2_profileURL=URL del perfil auths.oauth2_emailURL=URL de correo auths.enable_auto_register=Hablilitar Auto-Registro +auths.sspi_auto_create_users=Crear usuarios automáticamente +auths.sspi_auto_create_users_helper=Permitir al método de autenticación SSPI crear automáticamente nuevas cuentas para los usuarios que se conectan por primera vez +auths.sspi_auto_activate_users=Activar los usuarios automáticamente +auths.sspi_auto_activate_users_helper=Permitir al método de autenticación SSPI activar automáticamente los nuevos usuarios +auths.sspi_strip_domain_names=Eliminar los nombres de dominio de nombres de usuario +auths.sspi_strip_domain_names_helper=Si está marcado, los nombres de dominio se eliminarán de los nombres de inicio de sesión (por ejemplo, "DOMINIO\usuario" y "usuario@ejemplo.org" se convertirán en sólo "usuario"). +auths.sspi_separator_replacement=Separador a usar en lugar de \, / y @ +auths.sspi_separator_replacement_helper=El carácter a usar para reemplazar los separadores de los nombres de inicio de sesión de nivel inferior (por ejemplo, la \ en "DOMINIO\usuario") y en los nombres principales del usuario (por ejemplo, la @ en "user@example.org"). +auths.sspi_default_language=Idioma predeterminado del usuario +auths.sspi_default_language_helper=Idioma predeterminado para los usuarios creados automáticamente por el método de autenticación SSPI. Deje vacío si prefiere que el idioma sea detectado automáticamente. auths.tips=Consejos auths.tips.oauth2.general=Autenticación OAuth2 auths.tips.oauth2.general.tip=Al registrar una nueva autenticación vía OAuth2, la URL de devolución/redirección debe ser: /user/oauth2//callback @@ -1795,6 +1866,7 @@ auths.delete_auth_desc=Eliminar un origen de autenticación impide que los usuar auths.still_in_used=El orígen de autenticación todavía está en uso. Convierta o elimine cualquier usuario que utilice este origen de autenticación primero. auths.deletion_success=El origen de autenticación ha sido eliminado. auths.login_source_exist=El origen de autenticación '%s' ya existe. +auths.login_source_of_type_exist=Ya existe un origen de autenticación de este tipo. config.server_config=Configuración del servidor config.app_name=Título del sitio @@ -1939,6 +2011,11 @@ monitor.process=Procesos en ejecución monitor.desc=Descripción monitor.start=Hora de Inicio monitor.execute_time=Tiempo de ejecución +monitor.process.cancel=Cancelar el proceso +monitor.process.cancel_desc=Cancelar un proceso puede ocasionar una pérdida de datos +monitor.process.cancel_notices=Cancelar: %s? + + notices.system_notice_list=Notificaciones del Sistema notices.view_detail_header=Ver detalles de notificación @@ -1965,6 +2042,7 @@ create_pull_request=`creado pull request %s#%[2]s` close_pull_request=`cerró el pull request %s#%[2]s` reopen_pull_request=`reabrió el pull request %s#%[2]s` comment_issue=`comentó en la incidencia %s#%[2]s` +comment_pull=`comentado en pull request %s#%[2]s` merge_pull_request=`fusionado pull request %s#%[2]s` transfer_repo=transfirió el repositorio %s a %s push_tag=hizó push la etiqueta %[2]s a %[3]s diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 2ce0dd246c9d..e9fc99c500d9 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -2026,6 +2026,8 @@ monitor.process.cancel=لغو فرآیند monitor.process.cancel_desc=لغو کردن یک فرآیند ممکن است باعث از دست رفتن داده ها شود monitor.process.cancel_notices=لغو: %s؟ + + notices.system_notice_list=هشدارهای سامانه notices.view_detail_header=مشاهده جزئیات اخطار notices.actions=اقدامات diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 1d93a69029ec..8607c43c2393 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -786,6 +786,8 @@ monitor.desc=Kuvaus monitor.start=Alkamisaika monitor.execute_time=Suoritusaika + + notices.system_notice_list=Järjestelmän ilmoitukset notices.actions=Toiminnot notices.select_all=Valitse kaikki diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 375bca5e8c9b..389ad0519930 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1194,7 +1194,7 @@ activity.git_stats_deletion_n=%d suppressions search=Chercher search.search_repo=Rechercher dans le dépôt -search.results=Résulats de la recherche « %s » dans %s +search.results=Résultats de la recherche « %s » dans %s settings=Paramètres settings.desc=Les paramètres sont l'endroit où gérer les options du dépôt @@ -2003,6 +2003,8 @@ monitor.process.cancel=Annuler le processus monitor.process.cancel_desc=L'annulation d'un processus peut entraîner une perte de données monitor.process.cancel_notices=Annuler : %s? + + notices.system_notice_list=Informations notices.view_detail_header=Voir les détails de l'information système notices.actions=Actions diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 9b8b0852f327..ae988f45bb4e 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -911,6 +911,8 @@ monitor.desc=Leírás monitor.start=Kezdés Időpontja monitor.execute_time=Végrehajtási Idő + + notices.system_notice_list=Rendszer Értesítések notices.view_detail_header=Értesítés Részletei notices.actions=Műveletek diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 7594a64cc400..cd49c4133a26 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -957,6 +957,8 @@ monitor.desc=Deskripsi monitor.start=Waktu mulai monitor.execute_time=Waktu pelaksanaan + + notices.system_notice_list=Pemberitahuan sistem notices.view_detail_header=Lihat rincian pemberitahuan notices.actions=Tindakan diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index cf11fd1355a6..8a59d221310c 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1509,6 +1509,8 @@ monitor.desc=Descrizione monitor.start=Orario Avvio monitor.execute_time=Tempo di Esecuzione + + notices.system_notice_list=Avvisi di Sistema notices.view_detail_header=Visualizza dettagli dell'avviso notices.actions=Azioni diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 20ec8bd23bb4..673fdceeb49f 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -10,6 +10,7 @@ link_account=アカウント連携 register=登録 website=Webサイト version=バージョン +powered_by=Powered by %s page=ページ template=テンプレート language=言語 @@ -1053,6 +1054,7 @@ pulls.is_checking=マージのコンフリクトを確認中です。 少し待 pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。 pulls.required_status_check_administrator=管理者であるため、このプルリクエストをマージすることは可能です。 pulls.blocked_by_approvals=このプルリクエストはまだ承認数が足りません。 %[1]d/%[2]dの承認を得ています。 +pulls.blocked_by_rejection=このプルリクエストは公式レビューアにより変更要請されています。 pulls.can_auto_merge_desc=このプルリクエストは自動的にマージできます。 pulls.cannot_auto_merge_desc=コンフリクトが存在するため、このプルリクエストは自動的にマージできません。 pulls.cannot_auto_merge_helper=コンフリクトを解消するため手動でマージしてください。 @@ -1416,6 +1418,8 @@ settings.update_protect_branch_success=ブランチ '%s' の保護を更新し settings.remove_protected_branch_success=ブランチ '%s' の保護を無効にしました。 settings.protected_branch_deletion=ブランチ保護の無効化 settings.protected_branch_deletion_desc=ブランチ保護を無効にすると、書き込み権限を持つユーザーにブランチへのプッシュを許可することになります。 続行しますか? +settings.block_rejected_reviews=不承認レビューでマージをブロック +settings.block_rejected_reviews_desc=公式レビューアが変更を要請しているときは、承認数を満たしていても、マージできないようにします。 settings.default_branch_desc=プルリクエストやコミット表示のデフォルトのブランチを選択: settings.choose_branch=ブランチを選択… settings.no_protected_branch=保護しているブランチはありません。 @@ -2022,6 +2026,8 @@ monitor.process.cancel=処理をキャンセル monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります monitor.process.cancel_notices=キャンセル: %s? + + notices.system_notice_list=システム通知 notices.view_detail_header=通知の詳細を表示 notices.actions=アクション diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index c602990e4e12..55951c19d1fb 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -3,7 +3,9 @@ dashboard=대시보드 explore=탐색 help=도움말 sign_in=로그인 +sign_in_with=냐후 ㅑㅜ 쨔소 sign_out=로그아웃 +sign_up=ㄲㄷ햔ㅅㄷㄱ link_account=계정 연결 register=가입하기 website=웹 사이트 @@ -12,12 +14,32 @@ page=페이지 template=템플릿 language=언어 notifications=알림 +create_new=ㅊㄱㄷㅁㅅㄷ... +user_profile_and_more=ㅖ개랴ㅣㄷ 뭉 ㄴㄷㅅ샤ㅜㅎㄴ... signed_in_as=다음 사용자로 로그인됨 +enable_javascript=쏘ㅑㄴ ㅈ듀냣ㄷ 재간 ㅠㄷㅅㅅㄷㄱ 쟈소 ㅓㅁㅍㅁㄴㅊ갸ㅔㅅ. username=사용자명 +email=뜨먀ㅣ ㅁㅇㅇㄱㄷㄴㄴ password=비밀번호 +re_type=ㄲㄷ-쑈ㅔㄷ ㅖㅁㄴㄴ잭ㅇ +captcha=ㅊ몠촘 +twofa=ㅆ재-ㄻㅊ색 며소두샻ㅁ샤ㅐㅜ +twofa_scratch=ㅆ재-ㄻㅊ색 ㄴㅊㄱㅁㅅ초 챙ㄷ passcode=인증코드 +u2f_insert_key=보안 키를 입력해주세요. +u2f_sign_in=보안 키에 있는 버튼을 눌러주십시오. 버튼이 없는 경우, 보안 키를 다시 입력해주십시오. +u2f_press_button=보안 키에 있는 버튼을 눌러주십시오... +u2f_use_twofa=모바일 기기에 표시된 이중인증 코드를 사용하십시오 +u2f_error=보안 키를 읽을 수 없습니다. +u2f_unsupported_browser=이 브라우저에서는 U2F 보안 키를 지원하지 않습니다. +u2f_error_1=알 수 없는 오류가 발생하였습니다. 다시 시도하십시오. +u2f_error_2=암호화 또는 정확한 URL(https://) 을 사용해주세요. +u2f_error_3=서버에서 요청을 처리하지 못했습니다. +u2f_error_4=허용되지 않은 보안 키입니다. 해당 키가 등록된 키인지 확인해주세요. +u2f_error_5=보안 키를 읽기 전에 시간이 만료되었습니다. 페이지를 다시 로드하거나 재시도해주세요. +u2f_reload=새로고침 repository=저장소 organization=조직 @@ -28,8 +50,12 @@ new_mirror=새로운 미러 new_fork=새 저장소 포크 new_org=새로운 조직 manage_org=조직 관리 +admin_panel=냣ㄷ ㅁ으ㅑㅜㅑㄴㅅㄱㅁ샤ㅐㅜ account_settings=계정 설정 settings=설정 +your_profile=ㅖ개랴ㅣㄷ +your_starred=ㄴㅅㅁㄱㄱㄷㅇ +your_settings=ㄴㄷㅅ샤ㅜㅎㄴ all=전체 sources=소스 @@ -43,39 +69,117 @@ issues=이슈들 cancel=취소 +write=쓰기 +preview=미리보기 +loading=불러오는 중... [startpage] [install] install=설치 +title=ㅑㅜㅑ샤미 채ㅜ랴혁ㅁ샤ㅐㅜ +docker_helper=Gitea를 Docker에서 실행하려면 설정 전에 이 문서를 읽어보세요. +requite_db_desc=Gitea를 실행하려면 MySQL, PostgreSQL, MSSQL 또는 SQLite3 중 하나가 필요합니다. db_title=데이터베이스 설정 db_type=데이터베이스 유형 host=호스트 +user=ㅕㄴㄷ구믇 password=비밀번호 db_name=데이터베이스 이름 +ssl_mode=ㄴ니 +charset=문자셋 path=경로 - +sqlite_helper=SQLite3 데이터베이스에 대한 파일 경로입니다.
Gitea를 서비스로 구동할 경우, 절대 경로를 입력해주십시오. +err_empty_db_path=SQLite3 데이터베이스 경로는 필수 입력 값입니다. +no_admin_and_disable_registration=ㅛㅐㅕ ㅊ무ㅜㅐㅅ 얀뮤ㅣㄷ ㅕㄴㄷㄱ ㄴ딜-ㄱㄷ햔ㅅㄱㅁ샤ㅐㅜ 쟈쇄ㅕㅅ ㅊㄱㄷㅁ샤ㅜㅎ 무 ㅁ으ㅑㅜㅑㄴㅅㄱㅁ색 ㅁㅊ채ㅕㅜㅅ. +err_empty_admin_password=쏟 ㅁ으ㅑㅜㅑㄴㅅㄱㅁ색 ㅔㅁㄴㄴ잭ㅇ ㅊ무ㅜㅐㅅ ㅠㄷ 드ㅔ쇼. +err_empty_admin_email=관리자 이메일은 비어 있을 수 없습니다. +err_admin_name_is_reserved=관리자 사용자 이름이 올바르지 않습니다, 제한된 사용자 이름입니다 +err_admin_name_is_invalid=관리자 사용자 이름이 올바르지 않습니다 + +general_title=ㅎ둗ㄱ미 ㄴㄷㅅ샤ㅜㅎㄴ +app_name=냣ㄷ 쌰싣 +app_name_helper=ㅛㅐㅕ ㅊ무 둣ㄷㄱ ㅛㅐㅕㄱ 채ㅡㅔ무ㅛ ㅜ믇 ㅗㄷㄱㄷ. repo_path=저장소 최상위 경로 +repo_path_helper=ㄲ드ㅐㅅㄷ 햣 ㄱ데ㅐ냐새갿ㄴ 쟈ㅣㅣ ㅠㄷ ㄴㅁㅍㄷㅇ 새 소ㅑㄴ 약ㄷㅊ새교. +lfs_path=햣 ㅣㄹㄴ 깨ㅐㅅ ㅖㅁ소 +lfs_path_helper=랴ㅣㄷㄴ ㅅㄱㅁ찯ㅇ ㅠㅛ 햣 ㅣㄹㄴ 쟈ㅣㅣ ㅠㄷ ㄴ색ㄷㅇ ㅑㅜ 소ㅑㄴ 약ㄷㅊ새교. ㅣㄷㅁㅍㄷ 드ㅔ쇼 새 얀뮤ㅣㄷ. +run_user=껴ㅜ ㅁㄴ ㅕㄴㄷ구믇 +run_user_helper=뚯ㄷㄱ 솓 ㅐㅔㄷㄱㅁ샤ㅜㅎ 뇬ㅅ드 ㅕㄴㄷ구믇 솜ㅅ 햣ㄷㅁ 겨ㅜㄴ ㅁㄴ. ㅜㅐㅅㄷ 솜ㅅ 소ㅑㄴ ㅕㄴㄷㄱ ㅡㅕㄴㅅ ㅗㅁㅍㄷ ㅁㅊㅊㄷㄴㄴ 새 솓 ㄱ데ㅐ냐새교 개ㅐㅅ ㅔㅁ소. +domain=ㄴ노 ㄴㄷㄱㅍㄷㄱ 애ㅡ먀ㅜ +domain_helper=애ㅡ먀ㅜ ㅐㄱ ㅙㄴㅅ ㅁㅇㅇㄱㄷㄴㄴ 랙 노ㅗ 치ㅐㅜㄷ ㅕ낀. +ssh_port=ㄴ노 ㄴㄷㄱㅍㄷㄱ ㅖㅐㄳ +ssh_port_helper=ㅖㅐㄳ ㅜㅕㅡㅠㄷㄱ ㅛㅐㅕㄱ 노ㅗ ㄴㄷㄱㅍㄷㄱ ㅣㅑㄴㅅ둔 ㅐㅜ. ㅣㄷㅁㅍㄷ 드ㅔ쇼 새 얀뮤ㅣㄷ. +http_port=햣ㄷㅁ ㅗㅆ쎄 ㅣㅑㄴㅅ두 ㅖㅐㄳ +http_port_helper=ㅖㅐㄳ ㅜㅕㅡㅠㄷㄱ 솓 햣ㄷㅁㄴ ㅈ듀 ㄴㄷㄱㅍㄷㄱ 쟈ㅣㅣ ㅣㅑㄴㅅ두 ㅐㅜ. +app_url=햣ㄷㅁ ㅠㅁㄴㄷ ㅕ끼 +app_url_helper=ㅠㅁㄴㄷ ㅁㅇㅇㄱㄷㄴㄴ 랙 ㅗㅆ쎼(ㄴ) 치ㅐㅜㄷ ㅕ낀 뭉 드먀ㅣ ㅜㅐ샤럋ㅁ샤ㅐㅜㄴ. log_root_path=로그 경로 +log_root_path_helper=ㅣㅐㅎ 랴ㅣㄷㄴ 쟈ㅣㅣ ㅠㄷ ㅈ걋ㅅ두 새 소ㅑㄴ 약ㄷㅊ새교. optional_title=추가설정 +email_title=뜨먀ㅣ ㄴㄷㅅ샤ㅜㅎㄴ smtp_host=SMTP 호스트 +smtp_from=ㄴ둥 뜨먀ㅣ ㅁㄴ +smtp_from_helper=뜨먀ㅣ ㅁㅇㅇㄱㄷㄴㄴ 햣ㄷㅁ 쟈ㅣㅣ ㅕㄴㄷ. 뚯ㄷㄱ ㅁ ㅔㅣ먀ㅜ 드먀ㅣ ㅁㅇㅇㄱㄷㄴㄴ ㅐㄱ ㅕㄴㄷ 솓 "ㅜ믇" <드먀ㅣ@ㄷㅌ므ㅔㅣㄷ.kr> 래금ㅅ. +mailer_user=느쎼 ㅕㄴㄷ구믇 +mailer_password=느쎼 ㅖㅁㄴㄴ잭ㅇ +register_confirm=ㄲㄷ벼ㅑㄱㄷ 뜨먀ㅣ 채ㅜ랴금샤ㅐㅜ 새 ㄲㄷ햔ㅅㄷㄱ +mail_notify=뚜뮤ㅣㄷ 뜨먀ㅣ ㅜㅐ샤럋ㅁ샤ㅐㅜㄴ +server_service_title=ㄴㄷㄱㅍㄷㄱ 뭉 쏘ㅑㄱㅇ-ㅖㅁㄱ쇼 ㄴㄷㄱ퍛ㄷ ㄴㄷㅅ샤ㅜㅎㄴ +offline_mode=뚜뮤ㅣㄷ ㅣㅐㅊ미 ㅡㅐㅇㄷ +offline_mode_popup=얀뮤ㅣㄷ 소ㅑㄱㅇ-ㅔㅁㄱ쇼 채ㅜㅅ둣 ㅇ디ㅑㅍㄷ교 ㅜㄷㅅ재간 뭉 ㄴㄷㄱㅍㄷ 미ㅣ ㄱㄷ내ㅕㄱㅊㄷㄴ ㅣㅐㅊ미ㅣㅛ. +disable_gravatar=얀뮤ㅣㄷ ㅎㄱㅁㅍㅍㅁㅅㅁㄱ +disable_gravatar_popup=얀뮤ㅣㄷ ㅎㄱㅁㅍㅁㅅㅁㄱ 뭉 소ㅑㄱㅇ-ㅔㅁㄱ쇼 ㅁㅍㅁㄴㅅㅁㄱ 내ㅕㄱㅊㄷㄴ. ㅁ ㅇㄷㄹ며ㅣㅅ ㅁㅍㅁㅅㅁㄱ 쟈ㅣㅣ ㅠㄷ ㅕㄴㄷㅇ ㅕㅟㄷㄴㄴ ㅁ ㅕㄴㄷㄱ ㅣㅐㅊ미ㅣㅛ ㅕㅔㅣㅐㅁㅇㄴ 무 ㅁㅍㅁㅅㅁㄱ. +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_signup=OpenID 가입 가능여부 +openid_signup_popup=OpenID를 통한 가입 가능여부 +enable_captcha=CAPTCHA 사용 가능여부 enable_captcha_popup=사용자 등록시 캡차 요구 +require_sign_in_view=페이지를 보기 위해 로그인 하기 +require_sign_in_view_popup=로그인한 사용자만 페이지에 접근할 수 있도록 제한합니다. 방문자들은 오직 sign in과 등록페이지만 볼 수 있습니다. +admin_setting_desc=관리자 계정을 만드는 것은 선택사항입니다. 첫번째로 등록된 사용자는 자동적으로 관리자로 지정됩니다. +admin_title=관리자 계정 설정 +admin_name=관리자 이름 admin_password=비밀번호 confirm_password=비밀번호 확인 +admin_email=이메일 주소 install_btn_confirm=Gitea 설치하기 test_git_failed='git' 명령 테스트 실패: %v +sqlite3_not_available=해당 버전에서는 SQLite3를 지원하지 않습니다. %s에서 공식 버전을 다운로드해주세요. ('gobuild' 버전이 아닙니다.) +invalid_db_setting=데이터베이스 설정이 올바르지 않습니다: %v +invalid_repo_path=저장소(레파지토리) 의 경로가 올바르지 않습니다: %v +run_user_not_match=실행 사용자명이 현재 사용자명과 다릅니다.: %s -> %s save_config_failed=설정을 저장할 수 없습니다: %v +invalid_admin_setting=관리자 계정 설정이 올바르지 않습니다: %v +install_success=환영합니다! Gitea를 찾아주셔서 감사합니다. 즐거운 시간 보내세요! +invalid_log_root_path=로그(Log) 의 경로가 올바르지 않습니다: %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'의 숨겨진 이메일 도메인이 'noreply.example.org'로 설정되어 있으면 'joe@noreply.example.org'로 로그인 됩니다. [home] +uname_holder=사용자 이름 또는 이메일 주소 password_holder=비밀번호 switch_dashboard_context=대시보드 컨텍스트 바꾸기 +my_repos=저장소 +show_more_repos=더 많은 저장소 보기 collaborative_repos=협업 저장소 my_orgs=내 조직 my_mirrors=내 미러 저장소들 view_home=%s 보기 +search_repos=저장소 찾기.. issues.in_your_repos=당신의 저장소에 @@ -84,24 +188,63 @@ repos=저장소 users=유저 organizations=조직 search=검색 +code=코드 +repo_no_results=일치하는 레포지토리가 없습니다. +user_no_results=일치하는 사용자가 없습니다. +org_no_results=일치하는 조직이 없습니다. +code_no_results=검색어와 일치하는 소스코드가 없습니다. +code_search_results='%s'에 대한 검색결과 [auth] +create_new_account=계정 등록 register_helper_msg=이미 계정을 가지고 계신가요? 로그인하세요! +social_register_helper_msg=이미 계정을 가지고 계신가요? 지금 연결하세요! +disable_register_prompt=계정 등록이 비활성화 되었습니다. 사이트 관리자에게 문의해주십시오. +disable_register_mail=계정 등록을 위한 이메일 검증이 비활성화 되었습니다. remember_me=자동 로그인 forgot_password_title=비밀번호 찾기 forgot_password=비밀번호를 잊으셨나요? +sign_up_now=계정이 필요하신가요? 지금 가입하세요. +sign_up_successful=가입이 완료되었습니다. confirmation_mail_sent_prompt=새로운 확인 메일이 %s로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 등록 절차를 완료하십시오. +must_change_password=비밀번호를 변경하세요. +allow_password_change=사용자에게 비밀번호 변경을 요청 (권장됨) +reset_password_mail_sent_prompt=확인 메일이 %s로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 비밀번호 찾기 절차를 완료하십시오. active_your_account=계정 활성화 +account_activated=계정이 활성화 되었습니다 +prohibit_login=로그인이 금지됨 +prohibit_login_desc=이 계정으로는 로그인 할 수 없습니다, 사이트 관리자에게 문의하세요. +resent_limit_prompt=활성화를 위한 이메일을 이미 전송했습니다. 3분 내로 이메일을 받지 못한 경우 재시도해주세요. has_unconfirmed_mail=안녕하세요 %s, 이메일 주소(%s)가 확인되지 않았습니다. 확인 메일을 받으시지 못하겼거나 새로운 확인 메일이 필요하다면, 아래 버튼을 클릭해 재발송하실 수 있습니다. resend_mail=여기를 눌러 확인 메일 재전송 email_not_associate=이 이메일 주소로 등록된 계정이 없습니다. +send_reset_mail=복구 이메일 보내기 +reset_password=비밀번호 재설정 +invalid_code=검증 코드가 유효하지 않거나 만료되었습니다. +reset_password_helper=계정 복구 +password_too_short=비밀번호의 길이는 최소 %d 자가 되어야 합니다. +non_local_account=로컬 유저가 아닌 경우 Gitea 웹 인터페이스를 통해 비밀번호를 변경할 수 없습니다. verify=확인 scratch_code=스크래치 코드 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_signin_title=로그인하여 연결된 계정 검증하기 +oauth_signin_submit=계정 연결 openid_connect_submit=연결 openid_connect_title=기존 계정으로 연결하기 +openid_connect_desc=선택된 OpenID의 URI를 찾을 수 없습니다. 여기서 새 계정으로 연동할 수 있습니다. openid_register_title=새 계정 생성 +openid_register_desc=선택된 OpenID의 URI를 찾을 수 없습니다. 여기서 새 계정으로 연동할 수 있습니다. +openid_signin_desc=OpenID URI를 입력하십시오. 다음과 같은 형식이 될 수 있습니다: https://anne.me, bob.openid.org.cn 또는 gnusocial.net/carry. +email_domain_blacklisted=당신의 이메일 주소로 등록할 수 없습니다. [mail] activate_account=계정을 활성화하세요 @@ -112,12 +255,14 @@ register_notify=Gitea에 오신것을 환영합니다! [modal] yes=예 no=아니오 +modify=변경하기 [form] UserName=사용자 이름 RepoName=저장소 이름 Email=이메일 주소 Password=비밀번호 +Retype=비밀번호 재입력 SSHTitle=SSH 키 이름 HttpsUrl=HTTPS URL PayloadUrl=페이로드 URL @@ -134,6 +279,9 @@ Content=컨텐츠 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 글자를 넘을 수 없습니다.` @@ -141,139 +289,304 @@ email_error=` 올바른 이메일 주소가 아닙니다.` url_error=` 올바른 URL이 아닙니다.` include_error=` 반드시 '%s'를 포함해야 합니다.` unknown_error=알 수 없는 오류: - +captcha_incorrect=CAPTCHA 코드가 올바르지 않습니다. +password_not_match=비밀번호가 일치하지 않습니다. + +username_been_taken=이미 사용하고 있는 아이디입니다. +repo_name_been_taken=이미 사용하고 있는 저장소 이름입니다. +org_name_been_taken=이미 사용중인 조직 이름입니다. +team_name_been_taken=이미 사용중인 팀 이름입니다. +team_no_units_error=최소 하나 이상의 레포지토리 섹션에 대한 접근을 허용하십시오. +email_been_used=이미 사용 중인 이메일 주소입니다. +openid_been_used=이미 사용 중인 OpenID 주소(%s) 입니다. +username_password_incorrect=사용자 이름 또는 암호가 올바르지 않습니다. +enterred_invalid_repo_name=입력한 저장소의 이름이 올바르지 않습니다. +enterred_invalid_owner_name=새로운 소유자 이름이 올바르지 않습니다. +enterred_invalid_password=입력한 비밀번호는 올바르지 않습니다. user_not_exist=존재하지 않는 사용자입니다. +last_org_owner=소유자(owners) 팀의 마지막 사용자는 삭제할 수 없습니다. 어떤 팀이든 최소한 1명의 소유자는 존재해야합니다. +cannot_add_org_to_team=이 조직은 팀 구성원으로 추가할 수 없습니다. +invalid_ssh_key=확인되지 않은 SSH 키입니다: %s +invalid_gpg_key=확인되지 않은 GPG 키입니다: %s +unable_verify_ssh_key=SSH 키를 검증할 수 없습니다. 잘못 입력한 부분이 없는지 확인하여 주십시오. auth_failed=인증 실패: %v +still_own_repo=먼저 삭제하거나 전송해야할 저장소들이 하나 이상 존재합니다. +still_has_org=먼저 탈퇴해야하는 조직이 하나 이상 존재합니다. +org_still_own_repo=먼저 삭제하거나 전송해야할 저장소들이 하나 이상 존재합니다. target_branch_not_exist=대상 브랜치가 존재하지 않습니다. [user] +change_avatar=아바타 변경 join_on=가입 : repositories=저장소 activity=공개 활동 followers=팔로워 +starred=관심있는 저장소 following=팔로우 중 follow=추적하기 unfollow=추적해제 +heatmap.loading=Heatmap 불러오는 중... form.name_reserved=사용자 이름 '%s'는 예약되어 있습니다. +form.name_pattern_not_allowed=%s 패턴은 사용자 이름에 사용할 수 없습니다. [settings] profile=프로필 +account=계정 password=비밀번호 +security=보안 avatar=아바타 ssh_gpg_keys=SSH / GPG 키 social=소셜 계정 +applications=어플리케이션 +orgs=조직 관리 +repos=저장소 delete=계정 삭제 twofa=2단계 인증 +account_link=연결된 계정 +organization=조직 uid=Uid +u2f=보안 키 public_profile=공개 프로필 +profile_desc=이메일 주소는 알림 및 기타 작업에 사용 됩니다. +password_username_disabled=로컬 사용자가 아닌 경우 사용자 이름 변경을 할 수 없습니다. 자세한 내용은 관리자에게 문의해주세요. full_name=성명 website=웹 사이트 location=위치 +update_theme=테마 갱신 update_profile=프로필 업데이트 update_profile_success=프로필이 업데이트 되었습니다. +change_username=사용자 이름 변경 되었습니다. +change_username_prompt=사용자 이름 및 계정 URL 정보가 변경되었습니다. continue=계속하기 cancel=취소 +language=언어 +ui=테마 +lookup_avatar_by_mail=이메일 주소로 아바타 찾기 federated_avatar_lookup=연합 아바타 조회 enable_custom_avatar=사용자정의 아바타를 사용 choose_new_avatar=새로운 아바타 선택 +update_avatar=아바타 변경하기 delete_current_avatar=현재 아바타 삭제 +uploaded_avatar_not_a_image=업로드 된 파일은 이미지가 아닙니다. +update_avatar_success=아바타가 변경되었습니다. +change_password=비밀번호 변경 old_password=현재 비밀번호 new_password=새 비밀번호 +retype_new_password=새 비밀번호 다시 입력 +password_incorrect=현재 비밀번호가 올바르지 않습니다. +change_password_success=비밀번호가 업데이트되었습니다. 다음 번 로그인하실 때는 새 비밀번호를 사용해 주십시오. +password_change_disabled=로컬 유저가 아닌 경우 Gitea 웹 인터페이스를 통해 비밀번호를 변경할 수 없습니다. emails=이메일 주소 +manage_emails=이메일 주소 관리 +manage_themes=기본 테마 선택 +manage_openid=OpenID 주소 관리 email_desc=주 사용 이메일 주소는 알림과 기타 작업에 사용됩니다. +theme_desc=이 테마가 사이트 전체 기본 테마가 됩니다. primary=기본 +primary_email=프라이머리로 만들기 +delete_email=삭제 +email_deletion=이메일 주소 삭제 +email_deletion_desc=계정의 이메일 주소와 관련된 정보가 삭제됩니다. 이메일 주소로 이미 커밋된 내용들은 바뀌지 않고 남아있게 됩니다. 계속 진행하시겠습니까? +email_deletion_success=이메일 주소가 삭제되었습니다. +theme_update_success=테마가 갱신되었습니다. +theme_update_error=선택한 테마가 존재하지 않습니다. +openid_deletion=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_confirmation_sent=검증 메일이 '%s'로 전송되었습니다. '%s' 안에 받은 편지함을 확인하여 이메일 주소를 검증하여 주십시오. +add_email_success=새로운 이메일 주소가 추가되었습니다. +add_openid_success=새로운 OpenID 주소가 추가되었습니다. +keep_email_private=이메일 주소 숨기기 +keep_email_private_popup=당신의 이메일 주소가 다른 사람에게 숨겨질 것입니다. +openid_desc=OpenID를 사용하면 외부 서비스 제공자에게 인증을 위임할 수 있습니다. manage_ssh_keys=SSH 키 관리 manage_gpg_keys=GPG 키 관리 add_key=키 추가 +ssh_desc=이러한 SSH 공용 키는 귀하의 계정과 연결되어 있습니다. 해당 개인 키는 당신의 저장소에 대한 전체 액세스를 가능하게 합니다. +gpg_desc=이러한 GPG 공개키는 당신의 계정과 연결되어있습니다. 커밋이 검증될 수 있도록 당신의 개인 키를 안전하게 유지하십시오. +ssh_helper=도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: SSH 키 생성하기 또는 SSH를 사용할 때 일반적인 문제 +gpg_helper=도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: GPG키에 대하여. add_new_key=SSH 키 추가 add_new_gpg_key=GPG 키 추가 +ssh_key_name_used=같은 이름의 SSH 키가 이미 당신의 계정에 추가되어 있습니다. +gpg_key_id_used=같은 ID의 GPG 공개키가 이미 존재합니다. +gpg_no_key_email_found=이 GPG 키는 귀하의 계정에 연결된 어떠한 이메일 주소로도 사용할 수 없습니다. +subkeys=하위 키 key_id=키 ID key_name=키 이름 key_content=컨텐츠 +add_key_success=SSH 키('%s') 가 추가 되었습니다. +add_gpg_key_success=GPG 키('%s') 가 추가 되었습니다. +delete_key=제거 +ssh_key_deletion=SSH 키 제거 +gpg_key_deletion=GPG 키 제거 +ssh_key_deletion_desc=SSH 키를 제거하면 계정에 대한 액세스 권한이 회수됩니다. 계속 하시겠습니까? +gpg_key_deletion_desc=GPG 키를 삭제하면 해당 키로 서명 한 커밋은 검증이 불가능합니다. 계속 하시겠습니까? +ssh_key_deletion_success=SSH 키가 삭제되었습니다. +gpg_key_deletion_success=GPG 키가 삭제되었습니다. add_on=추가 : valid_until=까지 유효 valid_forever=영원히 유효 last_used=마지막 사용 : no_activity=최근 활동 없음 +can_read_info=읽기 +can_write_info=쓰기 key_state_desc=이 키는 최근 1주일 동안 사용된 적이 있습니다. token_state_desc=이 토큰은 최근 1주일 동안 사용된 적이 있습니다. show_openid=프로필에 표시 hide_openid=프로필에서 숨기기 +ssh_disabled=SSH 사용불가 manage_social=SNS계정 관리 +social_desc=이러한 소셜 계정이 Gitea 계정과 연결되어 있습니다. 소셜 계정을 통해 당신의 Gitea 계정으로 로그인 할 수 있다는 점을 기억하십시오. +unbind=연결 해제 +unbind_success=소셜 계정이 Gitea 계정에서 연결해제 되었습니다. +manage_access_token=액세스 토큰 관리 generate_new_token=새 토큰을 생성 +tokens_desc=이 토큰들은 당신의 계정을 이용하여 Gitea API를 사용할 수 있습니다. +new_token_desc=토큰을 사용하면 어플리케이션에서 귀하의 계정에 대한 전체 접근 권한을 가지게 됩니다. token_name=토큰 이름 generate_token=토큰 생성 +generate_token_success=새로운 토큰이 생성되었습니다. 이 토큰은 다시 보이지 않으니 지금 복사하십시오. delete_token=삭제 - - - +access_token_deletion=액세스 토큰 삭제 +access_token_deletion_desc=토큰을 삭제하면 해당 토큰을 이용하는 어플리케이션으로부터 귀하의 계정에 대한 접근권한이 사라지게 됩니다. 계속하시겠습니까? +delete_token_success=토큰이 삭제되었습니다. 해당 토큰을 사용하는 어플리케이션은 더 이상 이 계정으로 접근할 수 없습니다. + +oauth2_client_id=클라이언트 ID + + +twofa_desc=2단계 인증은 계정의 보안을 향상시킵니다. +twofa_is_enrolled=귀하의 계정은 현재 2단계 인증에 등록되어 있습니다. +twofa_not_enrolled=귀하의 계정은 현재 2단계 인증에 등록되어 있지 않습니다. +twofa_disable=2단계 인증 해제 +twofa_scratch_token_regenerate=스크래치 토큰 재생성 +twofa_scratch_token_regenerated=이제 스크래치 토큰은 %s입니다. 안전한 장소에 보관하여 주십시오. +twofa_enroll=2단계 인증에 등록하기 +twofa_disable_note=필요한 경우 2단계 인증을 해제할 수 있습니다. +twofa_disable_desc=2단계 인증을 해제하면 귀하의 계정이 보안에 취약해질 것 입니다. 계속하시겠습니까? +regenerate_scratch_token_desc=스크래치 토큰을 분실 했거나 이미 로그인에 사용되었다면 여기에서 재설정 할 수 있습니다. +twofa_disabled=2단계 인증이 해제되었습니다. scan_this_image=이 이미지를 당신의 인증 애플리케이션에서 스캔하세요: or_enter_secret=또는 이 비밀키를 입력하세요: %s - - +then_enter_passcode=어플리케이션에 표시된 인증코드를 입력하여 주십시오: +passcode_invalid=인증코드가 올바르지 않습니다. 재시도해주십시오. +twofa_enrolled=당신의 계정에 2단계 인증이 설정되었습니다. 스크래치 토큰 (%s) 은 한 번만 표시되므로 안전한 장소에 보관하십시오! + +u2f_require_twofa=보안 키를 사용하기 위해서는 귀하의 계정을 2단계 인증에 등록해야 합니다. +u2f_register_key=보안 키 추가 +u2f_nickname=별명 +u2f_press_button=보안 키에 있는 버튼을 눌러 등록해주십시오... +u2f_delete_key=보안키 제거 +u2f_delete_key_desc=보안 키를 제거하면 해당 키로는 더 이상 로그인 할 수 없게 됩니다. 계속하시겠습니까? + +manage_account_links=연결된 계정 관리 +manage_account_links_desc=Gitea 계정에 연결된 외부 계정입니다. +account_links_not_available=현재 Gitea 계정에 연결된 외부 계정이 없습니다. +remove_account_link=연결된 계정 제거 +remove_account_link_desc=해당 계정을 연결해제 하는 경우 Gitea 계정에 대한 접근 권한이 사라지게 됩니다. 계속하시겠습니까? +remove_account_link_success=연결된 계정이 제거 되었습니다. orgs_none=당신은 어떤 조직의 구성원도 아닙니다. repos_none=어떤 레포지터리도 존재하지 않습니다. delete_account=계정 삭제 confirm_delete_account=삭제 승인 +delete_account_title=사용자 계정 삭제 +delete_account_desc=이 계정을 정말로 삭제하시겠습니까? [repo] owner=소유자 repo_name=저장소 이름 +repo_name_helper=좋은 저장소 이름은 보통 짧고 기억하기 좋은 특별한 키워드로 이루어 집니다. +template_helper=템플릿으로 저장소 만들기 visibility=가시성 +visibility_helper=개인 저장소로 만들기 +visibility_helper_forced=사이트 관리자가 새 레포지토리에 대해 비공개로만 생성되도록 하였습니다. +visibility_fork_helper=(변경사항을 적용하는 경우 모든 포크가 영향을 받게 됩니다.) +clone_helper=클론하는데에 도움이 필요하면 Help에 방문하세요. fork_repo=저장소 포크 fork_from=원본 프로젝트 : +fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다. +use_template=이 템플릿을 사용 repo_desc=설명 repo_lang=언어 +repo_gitignore_helper=.gitignore 템플릿 선택 license=라이센스 +license_helper=라이센스 파일을 선택해주세요. +readme=README +readme_helper=README 파일 템플릿을 선택해주세요. +auto_init=저장소 초기화 (.gitignore, License 그리고 README 추가) create_repo=저장소 만들기 default_branch=기본 브랜치 mirror_prune=정리 +mirror_prune_desc=불필요하게된 원격 트래킹 참조 삭제 +mirror_interval=미러 간격 (올바른 시간 단위는 'h','m','s'). 0은 자동 동기화를 비활성화 합니다. +mirror_interval_invalid=미러 간격이 올바르지 않습니다. +mirror_address=URL로 부터 클론 +mirror_last_synced=마지막 동기화 watchers=주시하고 있는 사람들 stargazers=별을 준 사람들 forks=포크 +pick_reaction=리액션 선택 +reactions_more=그리고 %d 더 form.reach_limit_of_creation=이미 최대치인 %d 개의 레포지터리를 가지고 있습니다. form.name_reserved=저장소 이름 '%s'은 예약 되어 있습니다. +form.name_pattern_not_allowed='%s' 패턴은 저장소명으로 허용되지 않습니다. +need_auth=클론시 인증 migrate_type=마이그레이션 유형 migrate_type_helper=이 저장소는 미러가 됩니다. +migrate_items_issues=이슈 migrate_repo=저장소 마이그레이션 +migrate.clone_address=URL로 부터 마이그레이트 / 클론 +migrate.clone_local_path=또는 로컬 서버의 경로 migrate.permission_denied=로컬 저장소는 가져오기를 할 수 없습니다. +migrate.invalid_local_path=로컬 경로가 올바르지 않습니다. 디렉토리가 아니거나 존재하지 않습니다. migrate.failed=마이그레이션 실패: %v mirror_from=의 미러 forked_from=원본 프로젝트 : +fork_from_self=자신의 저장소를 포크 할 수 없습니다. +fork_guest_user=로그인하고 Fork 이 창고. copy_link=복사 +copy_link_success=링크가 복사되었습니다. +copy_link_error=⌘C 또는 Ctrl-C 로 복사 copied=복사 완료 unwatch=보지않기 watch=보기 unstar=좋아요 취소 star=좋아요 fork=포크 +download_archive=저장소 다운로드 no_desc=설명 없음 quick_guide=퀵 가이드 clone_this_repo=이 저장소 복제 create_new_repo_command=커맨드 라인에서 새 레포리지터리 생성 push_exist_repo=커맨드라인에서 기존 레포지터리 푸시 +empty_message=이 저장소는 아무런 내용을 가지고 있지 않습니다. code=코드 +code.desc=소스 코드 접근, 파일, 커밋 그리고 브랜치 branch=브렌치 tree=트리 filter_branch_and_tag=브랜치나 태그로 필터 @@ -284,38 +597,66 @@ pulls=풀 리퀘스트 labels=레이블 milestones=마일스톤 commits=커밋 +commit=커밋 releases=릴리즈 +file_raw=Raw file_history=히스토리 file_view_raw=원본 보기 file_permalink=고유링크 +file_too_large=보여주기에는 파일이 너무 큽니다. +video_not_supported_in_browser=당신의 브라우저가 HTML5 'video' 태그를 지원하지 않습니다. +audio_not_supported_in_browser=당신의 브라우저가 HTML5 'audio' 태그를 지원하지 않습니다. stored_lfs=Git LFS에 저장되어 있습니다 +commit_graph=커밋 그래프 +editor.new_file=새 파일 +editor.upload_file=파일 업로드 +editor.edit_file=파일 편집 editor.preview_changes=변경내용 미리보기 +editor.cannot_edit_non_text_files=바이너리 파일을 웹 인터페이스에서 편집하실 수 없습니다. +editor.edit_this_file=파일 편집 +editor.delete_this_file=파일 삭제 +editor.file_delete_success='%s' 파일이 삭제되었습니다. +editor.name_your_file=파일명을 입력하세요... editor.or=혹은 +editor.cancel_lower=취소 editor.commit_changes=변경 내용을 커밋 editor.add=추가 '%s' editor.update=업데이트 '%s' editor.delete=삭제 '%s' +editor.commit_message_desc=선택적 확장 설명을 추가... editor.commit_directly_to_this_branch=%s 브랜치에서 직접 커밋해주세요. editor.create_new_branch=이 커밋에 대한 새로운 브랜치를 만들고 끌어오기 요청을 시작합니다. +editor.new_branch_name_desc=새로운 브랜치 명... editor.cancel=취소 +editor.filename_cannot_be_empty=파일명이 빈칸입니다. editor.branch_already_exists=이 저장소에 브랜치 '%s'가 이미 존재합니다. +editor.file_already_exists='%s' 파일명은 이미 이 저장소에 존재합니다. editor.no_changes_to_show=표시할 변경사항이 없습니다. editor.fail_to_update_file=파일 '%s'를 변경/추가 하는데 실패하였습니다. 에러: %v +editor.add_subdir=경로 추가... editor.unable_to_upload_files=파일 '%s'를 업로드하는데 실패하였습니다. 에러: %v editor.upload_files_to_dir=파일 업로드 '%s' +editor.cannot_commit_to_protected_branch=보호된 '%s' 브랜치로 커밋할 수 없습니다. +commits.desc=소스 코드 변경 내역 탐색 commits.commits=커밋 +commits.search=커밋 찾기... commits.find=검색 +commits.search_all=모든 브랜치 commits.author=작성자 commits.message=메시지 commits.date=날짜 commits.older=이전 commits.newer=최신 commits.signed_by=로그인 계정 +commits.gpg_key_id=GPG 키 ID +ext_issues=외부 버그 +ext_issues.desc=외부 이슈 트래커 연결. issues.new=새로운 이슈 +issues.new.title_empty=제목은 비워둘 수 없습니다. issues.new.labels=레이블 issues.new.no_label=레이블 없음 issues.new.clear_labels=레이블 초기화 @@ -324,19 +665,37 @@ issues.new.no_milestone=마일스톤 없음 issues.new.clear_milestone=마일스톤 초기화 issues.new.open_milestone=마일스톤 생성 issues.new.closed_milestone=마일스톤 닫기 +issues.new.assignees=담당자 +issues.new.clear_assignees=담당자 초기화 +issues.new.no_assignees=담당자 없음 issues.no_ref=Branch/Tag 가 지정되어 있지 않습니다. issues.create=이슈 생성 issues.new_label=새로운 레이블 +issues.new_label_placeholder=레이블 이름 +issues.new_label_desc_placeholder=설명 issues.create_label=레이블 만들기 issues.label_templates.title=사전정의 라벨 로드 +issues.label_templates.info=아직 레이블이 없습니다. 레이블 'New Label'을 만들거나 미리 정의된 레이블 셋을 사용하십시오: issues.label_templates.helper=라벨 세트 선택 +issues.label_templates.use=레이블 세트 사용 issues.label_templates.fail_to_load_file=라벨 템플릿 파일 '%s'를 로드하는데 실패하였습니다.: %v +issues.add_milestone_at=`%s %s 마일스톤을 추가하였습니다.` +issues.change_milestone_at=`%s 에서 %s %s 마일스톤으로 변경되었습니다` +issues.remove_milestone_at=`%s %s 마일스톤이 삭제되었습니다.` issues.deleted_milestone=`(삭제됨)` +issues.self_assign_at=`자체적으로 할당됨 %s` +issues.add_assignee_at=`다음으로부터 할당됨 %s %s` +issues.remove_assignee_at=`다음으로부터 할당취소됨 %s %s` +issues.remove_self_assignment=`%s 할당들이 삭제됨` +issues.delete_branch_at=`삭제된 브랜치 %s %s` issues.open_tab=%d 오픈 issues.close_tab=%d 닫힘 issues.filter_label=레이블 +issues.filter_label_no_select=모든 레이블 issues.filter_milestone=마일스톤 +issues.filter_milestone_no_select=모든 마일스톤 issues.filter_assignee=담당자 +issues.filter_assginee_no_select=모든 담당자 issues.filter_type=유형 issues.filter_type.all_issues=모든 이슈 issues.filter_type.assigned_to_you=나에게 할당됨 @@ -349,6 +708,10 @@ issues.filter_sort.recentupdate=최근 업데이트 issues.filter_sort.leastupdate=가장 최근에 업데이트 issues.filter_sort.mostcomment=가장 많은 코멘트 issues.filter_sort.leastcomment=가장 적은 코멘트 +issues.filter_sort.moststars=좋아요 많은 순 +issues.filter_sort.feweststars=좋아요 적은 순 +issues.filter_sort.mostforks=포크 많은 순 +issues.filter_sort.fewestforks=포크 적은 순 issues.action_open=열기 issues.action_close=닫기 issues.action_label=레이블 @@ -367,7 +730,9 @@ issues.commented_at=`코멘트됨, %s` issues.delete_comment_confirm=이 댓글을 정말 삭제하시겠습니까? issues.no_content=아직 콘텐츠가 없습니다. issues.close_issue=닫기 +issues.close_comment_issue=클로즈 및 코멘트 issues.reopen_issue=다시 열기 +issues.reopen_comment_issue=다시 오픈 및 코멘트 issues.create_comment=코멘트 issues.closed_at=`%[2]s가 Close` issues.reopened_at=`%[2]s를 다시 열음` @@ -380,40 +745,110 @@ issues.edit=수정 issues.cancel=취소 issues.save=저장 issues.label_title=레이블 이름 +issues.label_description=레이블 설명 issues.label_color=레이블 색상 issues.label_count=레이블 %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.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.subscribe=구독하기 issues.unsubscribe=구독 취소 +issues.tracker=타임 트래커 issues.start_tracking_short=시작 +issues.start_tracking=타임 트래킹 시작 issues.start_tracking_history=`%s가 작업 시작` +issues.tracking_already_started=`이슈에서 이미 타임 트래킹을 사용하고 있습니다!` issues.stop_tracking=중단 issues.stop_tracking_history=`작업 중단 %s` +issues.add_time=수동으로 시간 입력 +issues.add_time_short=시간 입력 issues.add_time_cancel=취소 issues.add_time_history=`사용 시간이 추가됨 %s` issues.add_time_hours=시간 issues.add_time_minutes=분 +issues.add_time_sum_to_small=시간이 입력되지 않았습니다. issues.cancel_tracking=취소 +issues.cancel_tracking_history=`%s 타임 트래킹이 취소되었습니다` +issues.time_spent_total=총 경과된 시간 +issues.time_spent_from_all_authors=`총 경과된 시간: %s` +issues.due_date=마감일 +issues.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다. +issues.error_modifying_due_date=마감일 수정을 실패하였습니다. +issues.error_removing_due_date=마감일 삭제를 실패하였습니다. +issues.due_date_form=yyyy-mm-dd +issues.due_date_form_add=마감일 추가 +issues.due_date_form_edit=편집 +issues.due_date_form_remove=삭제 +issues.due_date_not_writer=이슈의 마감일을 갱신하려면 저장소 쓰기 권한이 필요합니다. +issues.due_date_not_set=마감일이 설정되지 않았습니다. +issues.due_date_added=마감일 %s 를 추가 %s +issues.due_date_modified=%s 마감일이 %s %s 로 변경되었습니다 +issues.due_date_remove=%s %s 마감일이 삭제되었습니다. +issues.due_date_overdue=기한 초과 +issues.due_date_invalid=기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오. +issues.dependency.title=의존성 +issues.dependency.issue_no_dependencies=이 이슈는 어떠한 의존성도 가지지 않습니다. +issues.dependency.pr_no_dependencies=이 풀 리퀘스트는 어떠한 의존성도 가지지 않습니다. +issues.dependency.add=의존성 추가... +issues.dependency.cancel=취소 +issues.dependency.remove=제거 +issues.dependency.remove_info=이 의존성 제거 +issues.dependency.added_dependency=`%[2]s 새 의존성 추가 %[3]s` +issues.dependency.removed_dependency=`%[2]s 의존성 제거 %[3]s` +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.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=의존성이 이미 존재합니다. +issues.dependency.add_error_dep_not_same_repo=두 이슈는 같은 레포지토리 안에 있어야 합니다. +issues.review.self.approval=자신의 풀 리퀘스트를 승인할 수 없습니다. +issues.review.self.rejection=자신의 풀 리퀘스트에 대한 변경을 요청할 수 없습니다. +issues.review.approve=이 변경사항을 승인하였습니다. %s +issues.review.comment=검토됨 %s +issues.review.pending=보류 +issues.review.review=검토 +issues.review.reviewers=리뷰어 +issues.review.show_outdated=오래된 내역 보기 +issues.review.hide_outdated=오래된 내역 숨기기 pulls.new=새 풀 리퀘스트 +pulls.compare_changes=새 풀 리퀘스트 +pulls.compare_base=병합하기 +pulls.compare_compare=다음으로부터 풀 pulls.filter_branch=Filter Branch pulls.no_results=결과 없음 pulls.create=풀 리퀘스트 생성 +pulls.title_desc=%[2]s 에서 %[3]s 로 %[1]d commits 를 머지하려 합니다 pulls.merged_title_desc=%[2]s 에서 %[3]s 로 %[1]d commits 를 머지했습니다 %[4]s pulls.tab_conversation=대화 pulls.tab_commits=커밋 +pulls.tab_files=파일 변경됨 pulls.reopen_to_merge=머지 작업을 수행하려면 이 풀 리퀘스트를 다시 열어주세요. pulls.merged=병합 +pulls.has_merged=풀 리퀘스트가 머지 되었습니다. pulls.can_auto_merge_desc=이 풀리퀘스트는 자동적으로 머지될 수 있습니다. +pulls.cannot_auto_merge_helper=충돌을 해결하려면 수동으로 머지하십시오. +pulls.no_merge_desc=모든 저장소 머지 옵션이 비활성화 되어있기 때문에 이 풀 리퀘스트를 머지할 수 없습니다. pulls.merge_pull_request=풀리퀘스트 머지 +pulls.rebase_merge_pull_request=리베이스와 머지 +pulls.rebase_merge_commit_pull_request=리베이스와 머지 (--no-ff) +pulls.squash_merge_pull_request=스쿼시하고 머지 +pulls.invalid_merge_option=이 풀 리퀘스트에서 설정한 머지 옵션을 사용하실 수 없습니다. milestones.new=새로운 마일스톤 milestones.open_tab=%d개 열림 @@ -422,77 +857,244 @@ milestones.closed=닫힘 %s milestones.no_due_date=기한 없음 milestones.open=열기 milestones.close=닫기 +milestones.completeness=%d%% 완료됨 milestones.create=마일스톤 생성 milestones.title=타이틀 milestones.desc=설명 milestones.due_date=기한 (선택 사항) milestones.clear=지우기 +milestones.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다. +milestones.create_success=마일스톤 '%s'가 생성되었습니다. milestones.edit=마일스톤 편집 milestones.cancel=취소 - +milestones.modify=마일스톤 갱신 +milestones.edit_success=마일스톤 '%s' 가 갱신되었습니다. +milestones.deletion=마일스톤 삭제 +milestones.deletion_desc=마일스톤을 삭제하면 연관된 모든 이슈에서 삭제됩니다. 계속 하시겠습니까? +milestones.deletion_success=마일스톤이 삭제되었습니다. +milestones.filter_sort.closest_due_date=마감일이 가까운 순 +milestones.filter_sort.furthest_due_date=마감일이 먼 순 +milestones.filter_sort.least_complete=완료율이 낮은 순 +milestones.filter_sort.most_complete=완료율이 높은 순 +milestones.filter_sort.most_issues=이슈 많은 순 +milestones.filter_sort.least_issues=이슈 적은 순 + +ext_wiki=외부 위키 +ext_wiki.desc=외부 위키에 연결하기. wiki=위키 +wiki.welcome=위키에 오신것을 환영합니다! +wiki.welcome_desc=Wiki를 사용하여 공동 작업자들과 문서를 작성 하고 공유 할 수 있습니다. +wiki.desc=공동 작업자들과 문서 작성 및 공유. +wiki.create_first_page=첫 페이지 작성 wiki.page=페이지 +wiki.filter_page=페이지 필터링 +wiki.new_page=페이지 +wiki.default_commit_message=이 페이지에 대한 메모를 작성하세요.(선택사항) wiki.save_page=페이지 저장하기 wiki.last_commit_info=%s이(가) %s에 이 페이지를 수정함 wiki.edit_page_button=수정하기 wiki.new_page_button=새로운 페이지 wiki.delete_page_button=페이지 삭제 +wiki.delete_page_notice_1=Wiki 페이지 '%s' 를 삭제하면 취소할 수 없습니다. 계속 하시겠습니까? wiki.page_already_exists=같은 이름의 위키 페이지가 이미 존재 합니다. +wiki.reserved_page=Wiki 페이지 이름 '%s' 는 예약되어 있습니다. wiki.pages=페이지 wiki.last_updated=마지막 업데이트: %s +activity=활동 +activity.period.filter_label=기간: +activity.period.daily=1일 +activity.period.halfweekly=3일 +activity.period.weekly=1주 +activity.period.monthly=1개월 +activity.overview=개요 +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=%s 가 %s 로부터 머지 되었습니다. +activity.title.prs_opened_by=%s 가 %s 로 부터 제안 되었습니다. +activity.merged_prs_label=병합됨 +activity.opened_prs_label=제안중 +activity.active_issues_count_1=%d 개의 활성화된 이슈 +activity.active_issues_count_n=%d 개의 활성화된 이슈 +activity.closed_issues_count_1=클로즈된 이슈 +activity.closed_issues_count_n=클로즈된 이슈 +activity.title.issues_1=이슈 %d개 +activity.title.issues_n=이슈 %d개 +activity.title.issues_closed_by=%s 가 %s 에 의해 클로즈되었습니다. +activity.title.issues_created_by=%s 가 %s 에 의해 생성되었습니다. +activity.closed_issue_label=닫힘 +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_published_by=%s 가 %s 에 의하여 배포되었습니다. +activity.published_release_label=배포됨 +search=검색 +search.search_repo=저장소 검색 +search.results=%s 에서 "%s" 에 대한 검색 결과 settings=설정 +settings.desc=설정은 저장소 설정을 관리할 수 있습니다. +settings.options=저장소 +settings.collaboration=공동작업자 +settings.collaboration.admin=관리자 settings.collaboration.write=쓰기 settings.collaboration.read=읽기 settings.collaboration.undefined=미정의 +settings.hooks=웹훅 +settings.githooks=Git 훅 settings.basic_settings=기본 설정 settings.mirror_settings=미러 설정 +settings.sync_mirror=지금 동기화 +settings.mirror_sync_in_progress=미러 동기화 진행중입니다. 잠시 후 다시 확인해주십시오. +settings.site=웹 사이트 settings.update_settings=설정 저장 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이 올바른 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.tracker_url_format=외부 이슈 트래커 URL 형식 +settings.tracker_issue_style=외부 이슈 트래커 숫자 포맷 settings.tracker_issue_style.numeric=숫자 settings.tracker_issue_style.alphanumeric=문자 숫자 +settings.enable_timetracker=시간 추적 활성화 +settings.allow_only_contributors_to_track_time=기여자 트랙 타임만 +settings.pulls_desc=저장소 풀 리퀘스트 활성화 +settings.pulls.ignore_whitespace=공백은 충돌에서 무시하기 +settings.pulls.allow_merge_commits=커밋 병합 활성화 +settings.admin_settings=관리자 설정 +settings.admin_enable_health_check=저장소 헬스 체크 활성화 (git fsck) settings.danger_zone=위험 설정 settings.new_owner_has_same_repo=새로운 소유자가 같은 이름의 저장소를 이미 가지고 있습니다. 다른 이름을 선택해주세요. +settings.convert=일반 저장소로 변환 +settings.convert_confirm=저장소 변환 settings.transfer=소유권 이전 +settings.wiki_delete=위키 데이터 삭제 +settings.confirm_wiki_delete=위키 데이터 삭제 settings.delete=이 저장소 삭제 settings.delete_notices_1=- 이 작업은 취소할 수 없습니다. +settings.deletion_success=저장소가 삭제되었습니다. +settings.update_settings_success=저장소 설정이 갱신되었습니다. settings.transfer_owner=새 소유자 +settings.make_transfer=이전 실행 +settings.transfer_succeed=저장소가 이전 되었습니다. +settings.confirm_delete=저장소 삭제 +settings.add_collaborator=새 공동작업자 추가 +settings.add_collaborator_success=공동작업자가 추가 되었습니다. +settings.delete_collaborator=제거 +settings.collaborator_deletion=공동작업자 삭제 +settings.search_user_placeholder=사용자 검색... settings.add_webhook=Webhook 추가 +settings.webhook_deletion=Webhook 삭제 +settings.webhook_deletion_success=Webhook을 삭제했습니다. settings.webhook.test_delivery=전달 시험 +settings.webhook.test_delivery_desc=이 웹훅을 가상 이벤트로 테스트 settings.webhook.request=요청 settings.webhook.response=응답 settings.webhook.headers=제목 +settings.webhook.payload=내용 settings.webhook.body=본문 settings.githook_edit_desc=후크가 비활성인 경우 샘플 콘텐츠가 표시됩니다. 내용을 빈 값으로 두면 이 훅은 비활성화됩니다. settings.githook_name=Hook 이름 settings.githook_content=Hook 내용 settings.update_githook=Hook 갱신 +settings.payload_url=대상 URL +settings.content_type=POST Content Type settings.secret=비밀 settings.slack_username=사용자 이름 settings.slack_icon_url=아이콘 URL +settings.discord_username=사용자명 +settings.discord_icon_url=아이콘 URL settings.slack_color=색 +settings.event_desc=트리거: +settings.event_push_only=푸시 이벤트 +settings.event_send_everything=모든 이벤트 +settings.event_choose=사용자 정의 이벤트... settings.event_create=생성 +settings.event_create_desc=브랜치 또는 태그가 생성되었습니다. +settings.event_delete=삭제 +settings.event_delete_desc=브랜치 또는 태그가 삭제되었습니다. +settings.event_fork=포크 +settings.event_fork_desc=저장소 포크됨 +settings.event_issues=이슈 +settings.event_issue_comment=이슈 댓글 +settings.event_issue_comment_desc=이슈 댓글이 작성, 편집 또는 삭제되었습니다. +settings.event_release=릴리즈 +settings.event_release_desc=릴리즈가 저장소에서 배포, 갱신 또는 제거되었습니다. settings.event_pull_request=끌어오기 요청 settings.event_push=푸시 +settings.event_push_desc=저장소로 푸시 +settings.event_repository=저장소 +settings.event_repository_desc=저장소가 생성되거나 삭제됩니다. +settings.active=사용 +settings.active_helper=이벤트에 대한 정보가 이 웹훅 URL로 전송될 것 입니다. +settings.add_hook_success=웹훅이 추가되었습니다. settings.update_webhook=Webhook 갱신 +settings.update_hook_success=웹훅이 갱신되었습니다. +settings.delete_webhook=웹훅 삭제 settings.recent_deliveries=최근의 Deliveries settings.hook_type=훅 타입 +settings.add_slack_hook_desc=Slack을 저장소와 연동. settings.slack_token=토큰 settings.slack_domain=도메인 settings.slack_channel=채널 +settings.add_discord_hook_desc=Discord를 저장소와 연동. +settings.add_dingtalk_hook_desc=Dingtalk을 저장소와 연동. settings.deploy_keys=배포 키 settings.add_deploy_key=배포 키 추가 +settings.deploy_key_desc=배포키는 저장소에서 풀만 할 수 있는 읽기 전용입니다. +settings.is_writable=쓰기 활성화 +settings.is_writable_info=이 배포키를 저장소로 푸시할 수 있도록 허용합니다. +settings.no_deploy_keys=배포키가 없습니다. settings.title=제목 settings.deploy_key_content=내용 +settings.key_been_used=동일한 내용의 배포키를 이미 사용중입니다. +settings.key_name_used=같은 이름의 배포키가 이미 있습니다. +settings.add_key_success='%s' 배포키가 추가되었습니다. +settings.deploy_key_deletion=배포키 삭제 +settings.deploy_key_deletion_success=배포키가 삭제되었습니다. settings.branches=브랜치 settings.protected_branch=브랜치 보호 +settings.protected_branch_can_push=푸시를 허용하시겠습니까? +settings.protected_branch_can_push_yes=푸시할 수 있습니다. +settings.protected_branch_can_push_no=푸시할 수 없습니다. +settings.branch_protection='%s' 브랜치 보호 +settings.protect_this_branch=브랜치 보호 활성화 +settings.protect_whitelist_search_users=사용자 찾기... +settings.protect_whitelist_search_teams=팀 찾기... +settings.protect_merge_whitelist_committers=머지 화이트리스트 활성화 +settings.protect_required_approvals=필요한 승인: +settings.protect_approvals_whitelist_users=화이트리스트된 리뷰어: settings.add_protected_branch=보호 활성화 settings.delete_protected_branch=보호 비활성화 +settings.protected_branch_deletion=브랜치 보호 비활성화 +settings.choose_branch=브랜치 선택... +settings.no_protected_branch=보호된 브랜치가 없습니다. +settings.edit_protected_branch=편집 +settings.archive.button=아카이브 저장소 +settings.archive.header=이 저장소를 아카이브 +settings.archive.text=아카이빙하면 저장소 전체가 읽기 전용이 됩니다. 이 작업을 진행하면 대시보드에서 숨겨지며 커밋, 이슈 또는 풀 리퀘스트를 생성할 수 없게 됩니다. +settings.archive.success=저장소가 성공적으로 아카이브 되었습니다. diff.browse_source=소스 검색 diff.parent=부모 @@ -500,11 +1102,23 @@ diff.commit=커밋 diff.data_not_available=변경 데이터를 사용할 수 없습니다. diff.show_split_view=분할 보기 diff.show_unified_view=통합 보기 +diff.whitespace_button=공백 +diff.whitespace_show_everything=모든 변경사항 보기 diff.stats_desc=%d개의 변경된 파일%d개의 추가작업 그리고 %d개의 파일을 삭제 diff.bin=BIN diff.view_file=파일 보기 diff.file_suppressed=파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. diff.too_many_files=이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다. +diff.comment.placeholder=댓글 남기기 +diff.comment.add_single_comment=간단한 설명 추가 +diff.comment.add_review_comment=댓글 추가 +diff.comment.start_review=리뷰 시작 +diff.comment.reply=답변 +diff.review=리뷰 +diff.review.header=리뷰 전송 +diff.review.placeholder=댓글 검토 +diff.review.comment=댓글 +diff.review.approve=승인 release.releases=릴리즈 release.new_release=새로운 릴리즈 @@ -518,13 +1132,37 @@ release.tag_name=태그 이름 release.target=대상 release.title=제목 release.content=컨텐츠 +release.prerelease_desc=프리-릴리즈로 표시 release.cancel=취소 release.publish=릴리즈 게시 release.save_draft=초안 저장 +release.edit_release=릴리즈 갱신 +release.delete_release=릴리즈 삭제 +release.deletion=릴리즈 삭제 release.deletion_success=릴리즈가 삭제되었습니다. +release.tag_name_already_exist=이 태그명의 릴리즈가 이미 존재합니다. +release.tag_name_invalid=태그명이 올바르지 않습니다. release.downloads=다운로드 - +branch.name=브랜치명 +branch.search=브랜치 검색 +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_success='%s' 브랜치가 생성되었습니다. +branch.branch_already_exists=이 저장소에 '%s' 브랜치가 이미 존재합니다. +branch.deleted_by=%s 에 의해 삭제되었습니다. +branch.restore_success='%s' 브랜치가 복구되었습니다. +branch.restore_failed='%s' 브랜치를 복구하는데 실패하였습니다. +branch.protected_deletion_failed='%s' 브랜치가 보호되었습니다. 삭제할 수 없습니다. + +topic.manage_topics=토픽 관리 +topic.done=완료 +topic.count_prompt=25개 이상의 토픽을 선택하실 수 없습니다. [org] org_name_holder=조직 이름 @@ -535,23 +1173,34 @@ people=사람 teams=팀 lower_members=회원 lower_repositories=저장소 +create_new_team=새 팀 +create_team=팀 만들기 org_desc=설명 team_name=팀 이름 team_desc=설명 +team_permission_desc=권한 settings=설정 +settings.options=조직 settings.full_name=성명 settings.website=웹 사이트 settings.location=위치 settings.update_settings=설정 업데이트 settings.update_setting_success=조직 설정이 변경되었습니다. +settings.update_avatar_success=조직의 아바타가 갱신되었습니다. settings.delete=조직 삭제 settings.delete_account=이 조직을 삭제합니다. settings.confirm_delete_account=삭제 승인 +settings.delete_org_title=조직 삭제 +settings.delete_org_desc=이 조직이 영구히 삭제됩니다. 계속 하시겠습니까? members.membership_visibility=회원 표시: +members.public=보임 +members.public_helper=숨기기 +members.private=숨김 +members.private_helper=보이기 members.member_role=회원 역할: members.owner=소유자 members.member=멤버 @@ -564,19 +1213,27 @@ teams.join=가입 teams.leave=탈퇴 teams.read_access=읽기 접근 teams.write_access=쓰기 접근 +teams.admin_access=관리자 액세스 teams.no_desc=이 팀은 설명이 없습니다. teams.settings=설정 teams.members=팀 구성원 teams.update_settings=설정 업데이트 +teams.delete_team=팀 삭제 teams.add_team_member=팀 구성원 추가 +teams.delete_team_title=팀 삭제 teams.delete_team_success=팀이 삭제되었습니다. teams.repositories=팀 저장소 +teams.search_repo_placeholder=저장소 찾기... teams.add_nonexistent_repo=추가하려는 저장소를 존재하지 않습니다. 먼저 생성해주세요. +teams.add_duplicate_users=사용자가 이미 팀 멤버입니다. +teams.members.none=이 팀에 멤버가 없습니다. [admin] dashboard=대시보드 +users=사용자 계정 organizations=조직 repositories=저장소 +authentication=인증 소스 config=설정 notices=시스템 공지 monitor=모니터링 @@ -584,15 +1241,27 @@ first_page=처음 last_page=마지막 total=총: %d +dashboard.statistic=요약 +dashboard.system_status=시스템 상태 dashboard.operation_name=작업 명 dashboard.operation_switch=스위치 dashboard.operation_run=실행 +dashboard.delete_repo_archives=모든 저장소 아카이브 삭제 +dashboard.delete_repo_archives_success=모든 저장소 아카이브가 삭제되었습니다. +dashboard.git_gc_repos=모든 저장소 가비지 콜렉트 +dashboard.git_gc_repos_success=모든 저장소의 가비지 콜렉션이 끝났습니다. +dashboard.sync_external_users=외부 사용자 데이터 동기화 +dashboard.sync_external_users_started=외부 사용자 데이터 동기화가 시작되었습니다. +dashboard.git_fsck=모든 저장소 헬스 체크 실행 +dashboard.git_fsck_started=저장소 헬스 체크가 시작되었습니다. dashboard.server_uptime=서버를 켠 시간 dashboard.current_goroutine=현재 Go루틴 dashboard.current_memory_usage=현재 메모리 사용율 dashboard.total_memory_allocated=전체 할당 메모리 dashboard.memory_obtained=메모리 확보 dashboard.pointer_lookup_times=포인터 조회 시간 +dashboard.memory_allocate_times=사용 메모리 +dashboard.memory_free_times=가용 메모리 dashboard.current_heap_usage=현재 힙 사용현황 dashboard.heap_memory_obtained=힙 메모리 확보 dashboard.heap_memory_idle=힙 메모리 유휴 @@ -615,28 +1284,58 @@ dashboard.total_gc_pause=모든 가비지 콜렉션 중지 dashboard.last_gc_pause=마지막 가비지 콜렉션 중지 dashboard.gc_times=가비지 콜렉션 시간 +users.user_manage_panel=사용자 계정 관리 +users.new_account=사용자 계정 생성 +users.name=사용자명 users.activated=활성화됨 users.admin=관리자 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=로컬 - +users.auth_login_name=인증 로그인 명 +users.password_helper=비밀번호를 비워두시면 변경하지 않고 유지됩니다. +users.update_profile_success=사용자 계정이 갱신되었습니다. +users.edit_account=사용자 계정 편집 +users.max_repo_creation_desc=(-1을 입력하면 전역 설정된 기본 제한을 따릅니다.) +users.is_activated=사용자 계정이 활성화 되었습니다. +users.prohibit_login=로그인 비활성화 +users.is_admin=관리자 +users.allow_git_hook=Git 훅 생성 허용 +users.allow_import_local=로컬 저장소 가져오기 허용 +users.allow_create_organization=조직 생성 허용 +users.update_profile=사용자 계정 갱신 +users.delete_account=사용자 계정 삭제 +users.deletion_success=사용자 계정이 삭제되었습니다. + +orgs.org_manage_panel=조직 관리 orgs.name=이름 orgs.teams=팀 orgs.members=멤버 +orgs.new_orga=새 조직 +repos.repo_manage_panel=저장소 관리 repos.owner=소유자 repos.name=이름 repos.private=비공개 repos.watches=지켜보기 +repos.stars=별 +repos.forks=포크 repos.issues=이슈 +repos.size=크기 +auths.auth_manage_panel=인증 소스 관리 +auths.new=인증 소스 추가 auths.name=이름 auths.type=유형 auths.enabled=활성화됨 +auths.syncenabled=사용자 동기화 활성화 auths.updated=업데이트됨 auths.auth_type=인증 유형 auths.auth_name=인증 이름 @@ -648,6 +1347,13 @@ auths.bind_dn=DN 연결 auths.bind_password=비밀번호 연결 auths.user_base=사용자 검색 기준 auths.user_dn=사용자 DN +auths.attribute_username=사용자명 속성 +auths.attribute_name=이름 속성 +auths.attribute_surname=성 속성 +auths.attribute_mail=이메일 속성 +auths.attribute_ssh_public_key=SSH 공개 키 속성 +auths.use_paged_search=페이지 검색 사용 +auths.search_page_size=페이지 크기 auths.filter=사용자 필터 auths.admin_filter=관리자 필터 auths.smtp_auth=SMTP 인증 유형 @@ -657,19 +1363,51 @@ auths.allowed_domains=허용된 도메인 auths.enable_tls=TLS 암호화 활성화 auths.skip_tls_verify=TLS 검증 건너뛰기 auths.pam_service_name=PAM 서비스 명 +auths.oauth2_provider=OAuth2 프로바이더 +auths.oauth2_clientID=클라이언트 ID (키) +auths.oauth2_clientSecret=클라이언트 시크릿 +auths.openIdConnectAutoDiscoveryURL=OpenID 자동 연결 탐색 URL +auths.oauth2_use_custom_url=기본 URLs 대신 사용자 정의 URLs 사용 +auths.oauth2_tokenURL=토큰 URL +auths.oauth2_authURL=인가 URL +auths.oauth2_profileURL=프로필 URL +auths.oauth2_emailURL=이메일 URL auths.enable_auto_register=자동 등록을 활성화 auths.tips=도움말 +auths.tips.oauth2.general=OAuth2 인증 +auths.tip.oauth2_provider=OAuth2 프로바이더 +auths.edit=인증 소스 편집 +auths.activated=인증 소스가 활성화 되었습니다. +auths.new_success='%s' 인증이 추가되었습니다. +auths.update_success=인증 소스가 갱신되었습니다. +auths.update=인증 소스 갱신 +auths.delete=인증 소스 삭제 +auths.delete_auth_title=인증 소스 삭제 +auths.deletion_success=인증 소스가 삭제되었습니다. +auths.login_source_exist='%s' 인증 소스가 이미 존재합니다. config.server_config=서버 설정 +config.app_name=사이트 제목 +config.app_ver=Gitea 버전 +config.app_url=Gitea의 기본 URL +config.custom_conf=설정 파일 경로 +config.domain=SSH 서버 도메인 +config.offline_mode=로컬 모드 config.disable_router_log=라우터 로그 비활성화 +config.run_user=실행 사용자명 config.run_mode=실행 모드 +config.git_version=Git 버전 config.repo_root_path=저장소 최상위 경로 +config.lfs_root_path=LFS 루트 경로 config.static_file_root_path=정적 파일 최상위 경로 +config.log_file_root_path=로그 경로 config.script_type=스크립트 유형 config.reverse_auth_user=역방향 사용자 인증 config.ssh_config=SSH 설정 config.ssh_enabled=활성화됨 +config.ssh_start_builtin_server=빌트-인 서버 사용 +config.ssh_domain=서버 도메인 config.ssh_port=포트 config.ssh_listen_port=수신 대기 포트 config.ssh_root_path=최상위 경로 @@ -683,22 +1421,47 @@ config.db_config=데이터베이스 설정 config.db_type=유형 config.db_host=호스트 config.db_name=이름 +config.db_user=사용자명 +config.db_ssl_mode=SSL config.db_path=경로 config.service_config=서비스 설정 +config.register_email_confirm=가입시 이메일 확인 필수 +config.disable_register=자체등록 사용안함 +config.allow_only_external_registration=외부 서비스를 통해서만 등록 허용 +config.enable_openid_signup=OpenID 자체등록 활성화 +config.enable_openid_signin=OpenID 로그인 활성화 config.show_registration_button=등록 버튼을 표시 +config.require_sign_in_view=페이지를 보려면 로그인 필수 +config.mail_notify=이메일 알림 활성화 config.disable_key_size_check=최소 키 크기 검사를 비활성화 +config.enable_captcha=CAPTCHA 활성화 config.active_code_lives=코드 만료 기한 +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.default_enable_dependencies=기본적으로 이슈 종속성을 활성화 config.webhook_config=웹훅 설정 config.queue_length=큐 길이 config.deliver_timeout=시간 제한 사용 +config.skip_tls_verify=TLS 검증 건너뛰기 +config.mailer_config=SMTP 메일러 설정 config.mailer_enabled=활성화됨 config.mailer_disable_helo=HELO 비활성화 config.mailer_name=이름 config.mailer_host=호스트 config.mailer_user=사용자 +config.mailer_use_sendmail=Sendmail 사용 +config.mailer_sendmail_path=Sendmail 경로 +config.mailer_sendmail_args=Sendmail 추가 인수 +config.send_test_mail=테스트 이메일 전송 +config.test_mail_failed='%s'에게 테스트 이메일을 보내는데 실패하였습니다: %v +config.test_mail_sent='%s'에게 테스트 이메일이 전송되었습니다. config.oauth_config=OAuth 설정 config.oauth_enabled=활성화됨 @@ -718,6 +1481,7 @@ config.session_life_time=세션 수명 config.https_only=HTTPS만 config.cookie_life_time=쿠키 수명 +config.picture_config=사진과 아바타 설정 config.picture_service=이미지 서비스 config.disable_gravatar=Gravatar 사용안함 config.enable_federated_avatar=연합 아바타 사용 @@ -742,12 +1506,16 @@ monitor.name=이름 monitor.schedule=스케줄 monitor.next=다음 시간 monitor.previous=이전 시간 +monitor.execute_times=실행 수 monitor.process=실행중인 프로세스들 monitor.desc=설명 monitor.start=시작 시간 monitor.execute_time=실행 시간 + + notices.system_notice_list=시스템 공지 +notices.view_detail_header=알림 세부정보 보기 notices.actions=동작 notices.select_all=모두 선택 notices.deselect_all=모두 선택 해제 @@ -758,6 +1526,7 @@ notices.type=유형 notices.type_1=저장소 notices.desc=설명 notices.op=일. +notices.delete_success=시스템 알림이 삭제되었습니다. [action] create_repo=저장소를 만들었습니다. %s @@ -796,17 +1565,27 @@ raw_seconds=초 raw_minutes=분 [dropzone] +invalid_input_type=이 형식의 파일을 업로드할 수 없습니다. file_too_big=파일 크기({{filesize}} MB) 가 최대 크기({{maxFilesize}} MB) 를 초과합니다. remove_file=파일 제거 [notification] notifications=알림 +unread=읽지 않음 +read=읽음 +no_unread=읽지 않은 알림이 없습니다. +no_read=알림이 없습니다. +pin=알림 고정 mark_as_read=읽음으로 표시 mark_as_unread=읽지 않음으로 표시 +mark_all_as_read=모두 읽음으로 표시 [gpg] error.extract_sign=서명 추출에 실패 error.generate_hash=커밋의 해시 생성에 실패 +error.not_signed_commit=서명되지 않은 커밋입니다. [units] +error.no_unit_allowed_repo=이 저장소의 어떤 섹션에도 접근할 수 없습니다. +error.unit_not_allowed=이 저장소 섹션에 접근할 수 없습니다. diff --git a/options/locale/locale_lt-LT.ini b/options/locale/locale_lt-LT.ini index f005477a1d59..d0920f405adc 100644 --- a/options/locale/locale_lt-LT.ini +++ b/options/locale/locale_lt-LT.ini @@ -270,6 +270,8 @@ issues.save=Saugoti + + diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index a1e1cf260132..f16f8bd512d7 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -10,6 +10,7 @@ link_account=Saistītie konti register=Reģistrēties website=Mājas lapa version=Versija +powered_by=Darbina %s page=Lapa template=Sagatave language=Valoda @@ -959,6 +960,7 @@ issues.add_time=Manuāli pievienot laiku issues.add_time_short=Pievienot laiku issues.add_time_cancel=Atcelt issues.add_time_history=` pievienoja patērēto laiku %s` +issues.del_time_history=`dzēsts patērētais laiks %s` issues.add_time_hours=Stundas issues.add_time_minutes=Minūtes issues.add_time_sum_to_small=Nav norādīts laiks. @@ -1052,6 +1054,7 @@ pulls.is_checking=Notiek konfliktu pārbaude, mirkli uzgaidiet un atjaunojiet la pulls.required_status_check_failed=Dažas no pārbaudēm nebija veiksmīgas. pulls.required_status_check_administrator=Kā administrators Jūs varat sapludināt šo izmaiņu pieprasījumu. pulls.blocked_by_approvals=Šim izmaiņu pieprasījumam nav nepieciešamais apstiprinājumu daudzums. %d no %d apstiprinājumi piešķirti. +pulls.blocked_by_rejection=Šo izmaiņu pieprasījumu nevar sapludināt, jo tam ir peprasītas izmaiņas. pulls.can_auto_merge_desc=Šo izmaiņu pieprasījumu var automātiski sapludināt. pulls.cannot_auto_merge_desc=Šis izmaiņu pieprasījums nevar tikt automātiski sapludināts konfliktu dēļ. pulls.cannot_auto_merge_helper=Sapludiniet manuāli, lai atrisinātu konfliktus. @@ -1415,6 +1418,8 @@ settings.update_protect_branch_success=Atzara aizsardzība atzaram '%s' tika sag settings.remove_protected_branch_success=Atzara aizsardzība atzaram '%s' tika atspējota. settings.protected_branch_deletion=Atspējot atzara aizsardzību settings.protected_branch_deletion_desc=Atspējojot atzara aizsardzību, ļaus lietotājiem ar rakstīšanas tiesībām nosūtīt izmaiņas uz atzaru. Vai turpināt? +settings.block_rejected_reviews=Neļaut sapludināt izmaiņu pieprasījumus, kam ir pieprasītas izmaiņas +settings.block_rejected_reviews_desc=Sapludināšana nebūs iespējama, kad ir pieprasītas izmaiņas, pat ja ir nepieciešamais apstiprinājumu skaits. settings.default_branch_desc=Norādiet noklusēto repozitorija atzaru izmaiņu pieprasījumiem un koda revīzijām: settings.choose_branch=Izvēlieties atzaru… settings.no_protected_branch=Nav neviena aizsargātā atzara. @@ -2021,6 +2026,8 @@ monitor.process.cancel=Atcelt procesu monitor.process.cancel_desc=Procesa atcelšana var radīt datu zaudējumus monitor.process.cancel_notices=Atcelt: %s? + + notices.system_notice_list=Sistēmas paziņojumi notices.view_detail_header=Skatīt paziņojuma detaļas notices.actions=Darbības @@ -2046,6 +2053,7 @@ create_pull_request=`izveidoja izmaiņu pieprasījumu %s#% close_pull_request=`aizvēra izmaiņu pieprasījumu %s#%[2]s` reopen_pull_request=`atkārtoti atvēra izmaiņu pieprasījumu %s#%[2]s` comment_issue=`pievienoja komentāru problēmai %s#%[2]s` +comment_pull=`komentēja izmaiņu pieprasījumu %s#%[2]s` merge_pull_request=`sapludināja izmaiņu pieprasījumu %s#%[2]s` transfer_repo=mainīja repozitorija %s īpašnieku uz %s push_tag=pievienoja tagu %[2]s repozitorijam %[3]s diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index 6706b9ca2b07..a3ae274f5477 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -1,76 +1,759 @@ - - - - - - - +home=പൂമുഖം +dashboard=ഡാഷ്ബോർഡ് +explore=കണ്ടെത്തൂ +help=സഹായം +sign_in=പ്രവേശിക്കുക +sign_in_with=ഉപയോഗിച്ചു് പ്രവേശിയ്ക്കുക +sign_out=പുറത്തുകടക്കുക +sign_up=രജിസ്റ്റർ +link_account=അക്കൌണ്ട് ബന്ധിപ്പിയ്ക്കുക +register=രജിസ്റ്റർ +website=വെബ് സൈറ്റ് +version=പതിപ്പ് +page=പേജ് +template=ടെംപ്ലേറ്റ് +language=ഭാഷ +notifications=അറിയിപ്പുകൾ +create_new=സൃഷ്ടിക്കുക… +user_profile_and_more=പ്രൊഫൈലും ക്രമീകരണങ്ങളും… +signed_in_as=ഇയാളായി പ്രവേശിയ്ക്കുക +enable_javascript=ഈ വെബ്‌സൈറ്റ് ജാവാസ്ക്രിപ്റ്റിനൊപ്പം മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്നു. + +username=ഉപയോക്ത്രു നാമം +email=ഈമെയില്‍ വിലാസം +password=രഹസ്യവാക്കു് +re_type=രഹസ്യവാക്കു് വീണ്ടും നല്‍കുക +captcha=ക്യാപ്ച +twofa=ഇരട്ട ഘടക പ്രാമാണീകരണം +twofa_scratch=ഇരട്ട ഫാക്ടർ സ്ക്രാച്ച് കോഡ് +passcode=രഹസ്യ കോഡ് + +u2f_insert_key=സെക്യൂരിറ്റി കീ ഇന്‍സേര്‍ട്ടു് ചെയ്യുക +u2f_sign_in=സെക്യൂരിറ്റി കീയിലെ ബട്ടണ്‍ അമര്‍ത്തുക. സെക്യൂരിറ്റി കീയില്‍ ബട്ടണൊന്നും ഇല്ലെങ്കില്‍ വീണ്ടും ഇന്‍സേര്‍ട്ടു് ചെയ്യുക. +u2f_press_button=ദയവായി സെക്യൂരിറ്റി കീയിലെ ബട്ടണ്‍ അമര്‍ത്തൂ… +u2f_use_twofa=നിങ്ങളുടെ ഫോണിൽ നിന്നുള്ള ഇരട്ട-ഘടക കോഡ് ഉപയോഗിക്കുക +u2f_error=സെക്യൂരിറ്റി കീ വായിയ്ക്കാനാകുന്നില്ല. +u2f_unsupported_browser=നീങ്ങളുടെ ബ്രൗസര്‍ ഇരട്ട ഘടക സെക്യൂരിറ്റി പിന്തുണയ്ക്കുന്നില്ല. +u2f_error_1=ഒരു അവിചാരിതമായ പിശക് സംഭവിച്ചു. ദയവായി വീണ്ടും ശ്രമിക്കുക. +u2f_error_2=നിങ്ങള്‍ ഉപയോഗിക്കുന്നത് ശരിയായ, എൻ‌ക്രിപ്റ്റ് ചെയ്ത (https://) യുആർഎൽ ആണെന്നു ദയവായി ഉറപ്പാക്കുക. +u2f_error_3=നിങ്ങളുടെ അഭ്യർത്ഥന പ്രോസസ്സ് ചെയ്യാൻ സെർവറിന് കഴിഞ്ഞില്ല. +u2f_error_4=ഈ അഭ്യർത്ഥന പൂര്‍ത്തിയാക്കാന്‍ സുരക്ഷാ കീ അനുവദനിയ്ക്കുന്നില്ല. ഈ കീ ഇതിനോടകം രജിസ്റ്റർ ചെയ്തിട്ടില്ലെന്ന് ഉറപ്പു വരുത്തുക. +u2f_error_5=നിങ്ങളുടെ കീ വായിക്കുന്നതിന് പൂര്‍ത്തിയാക്കാനായില്ല. ദയവായി ഈ പേജ് പുതുക്കി വീണ്ടും ശ്രമിക്കുക. +u2f_reload=പുതുക്കുക + +repository=കലവറ +organization=സംഘടന +mirror=മിറര്‍ +new_repo=പുതിയ കലവറ +new_migrate=പുതിയ കുടിയേറ്റിപ്പാര്‍പ്പിക്കല്‍ +new_mirror=പുതിയ മിറര്‍ +new_fork=കലവറയുടെ പുതിയ ശിഖരം +new_org=പുതിയ സംഘടന +manage_org=സംഘടനകളെ നിയന്ത്രിക്കുക +admin_panel=സൈറ്റിന്റെ കാര്യനിര്‍വ്വാഹണം +account_settings=അക്കൌണ്ട് ക്രമീകരണങള്‍ +settings=ക്രമീകരണങ്ങള്‍ +your_profile=പ്രൊഫൈൽ +your_starred=നക്ഷത്ര ചിഹ്നമിട്ടവ +your_settings=ക്രമീകരണങ്ങള്‍ + +all=എല്ലാം +sources=ഉറവിടങ്ങൾ +mirrors=മിററുകള്‍ +collaborative=സഹകരിക്കുന്ന +forks=ശാഖകള്‍ + +activities=പ്രവര്‍ത്തനങ്ങള്‍ +pull_requests=ലയന അഭ്യർത്ഥനകൾ +issues=പ്രശ്നങ്ങൾ + +cancel=റദ്ദാക്കുക + +write=എഴുതുക +preview=തിരനോട്ടം +loading=ലഭ്യമാക്കുന്നു… [startpage] [install] - - +install=സന്നിവേശിപ്പിയ്ക്കുക +title=പ്രാരംഭ ക്രമീകരണങ്ങള്‍ +docker_helper=ഡോക്കറിനുള്ളിലാണ് ഗിറ്റീ പ്രവര്‍ത്തിപ്പിയ്ക്കുന്നതെങ്കില്‍, മാറ്റങ്ങള്‍ വരുത്തുന്നതിനു മുമ്പു് ദയവായി ഡോക്യുമെന്റേഷൻ വായിയ്ക്കുക. +requite_db_desc=ഗിറ്റീയ്ക്കു് MySQL, PostgreSQL, MSSQL അല്ലെങ്കിൽ SQLite3 ആവശ്യമാണ്. +db_title=ഡാറ്റാബേസ് ക്രമീകരണങ്ങൾ +db_type=ഡാറ്റാബേസിന്റെ തരം +host=ഹോസ്റ്റ് +user=ഉപയോക്ത്രു നാമം +password=രഹസ്യവാക്കു് +db_name=ഡാറ്റാബേസിന്റെ പേര് +db_helper=MySQL ഉപയോക്താക്കൾക്കുള്ള കുറിപ്പ്: ദയവായി InnoDB സ്റ്റോറേജ് എഞ്ചിൻ ഉപയോഗിക്കുക. നിങ്ങൾ "utf8mb4" ഉപയോഗിക്കുകയാണെങ്കിൽ, InnoDB പതിപ്പ് 5.6 നേക്കാൾ വലുതായിരിക്കണം. +ssl_mode=SSL +charset=ക്യാര്‍സെറ്റ് +path=പാത +sqlite_helper=SQLite3 ഡാറ്റാബേസിന്റെ ഫയല്‍ പാത്ത്.
നിങ്ങൾ ഗിറ്റീയെ ഒരു സേവനമായി പ്രവർത്തിപ്പിക്കുകയാണെങ്കിൽ സമ്പൂര്‍ണ്ണ ഫയല്‍ പാത നൽകുക. +err_empty_db_path=SQLite3 ഡാറ്റാബേസ് പാത്ത് ശൂന്യമായിരിക്കരുത്. +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=അഡ്മിനിസ്ട്രേറ്റർ ഉപയോക്തൃനാമം അസാധുവാണ് + +general_title=പൊതുവായ ക്രമീകരണങ്ങൾ +app_name=സൈറ്റ് ശീർഷകം +app_name_helper=നിങ്ങളുടെ കമ്പനിയുടെ പേര് ഇവിടെ നൽകാം. +repo_path=സംഭരണിയുടെ റൂട്ട് പാത്ത് +repo_path_helper=വിദൂര ഗിറ്റു് സംഭരണികള്‍ ഈ ഡയറക്ടറിയിലേക്ക് സംരക്ഷിക്കും. +lfs_path=Git LFS റൂട്ട് പാത്ത് +lfs_path_helper=Git LFS ട്രാക്കുചെയ്ത ഫയലുകൾ ഈ ഡയറക്ടറിയിൽ സൂക്ഷിക്കും. പ്രവർത്തനരഹിതമാക്കാൻ ഈ കളം ശൂന്യമായി വിടുക. +run_user=ഉപയോക്താവായി പ്രവര്‍ത്തിപ്പിക്കുക +run_user_helper=ഗിറ്റീ പ്രവർത്തിക്കുന്ന ഓപ്പറേറ്റിംഗ് സിസ്റ്റത്തിന്റെ ഉപയോക്തൃനാമം നല്കുക. ഈ ഉപയോക്താവിന് സംഭരണിയുടെ റൂട്ട് പാത്തിലേക്ക് പ്രവേശനം ഉണ്ടായിരിക്കണം. +domain=SSH സെർവർ ഡൊമെയ്ൻ +domain_helper=SSH ക്ലോൺ URL- കൾക്കായുള്ള ഡൊമെയ്ൻ അല്ലെങ്കിൽ ഹോസ്റ്റ് വിലാസം. +ssh_port=SSH സെർവർ പോര്‍ട്ട് +ssh_port_helper=നിങ്ങളുടെ SSH സെർവർ ശ്രവിക്കുന്ന പോർട്ട് നമ്പർ നല്‍കുക. പ്രവർത്തനരഹിതമാക്കാൻ കളം ശൂന്യമായി വിടുക. +http_port=ഗിറ്റീ എച്ച്ടിടിപി ശ്രവിയ്ക്കുന്ന പോർട്ട് +http_port_helper=ഗിറ്റീ വെബ് സെർവർ ശ്രവിയ്ക്കുന്ന പോർട്ട് നമ്പർ. +app_url=ഗിറ്റീയുടെ അടിസ്ഥാന വിലാസം +app_url_helper=എച്ച്ടിടിപി(എസ്) ക്ലോണുകള്‍ക്കും ഇമെയിൽ അറിയിപ്പുകൾക്കുമായുള്ള അടിസ്ഥാന വിലാസം. +log_root_path=ലോഗ് പാത്ത് +log_root_path_helper=ലോഗ് ഫയലുകൾ ഈ ഡയറക്ടറിയിലേക്ക് എഴുതപ്പെടും. + +optional_title=ഐച്ഛികമായ ക്രമീകരണങ്ങൾ +email_title=ഇമെയിൽ ക്രമീകരണങ്ങൾ +smtp_host=SMTP ഹോസ്റ്റ് +smtp_from=ഈ വിലാസത്തില്‍ ഇമെയിൽ അയയ്‌ക്കുക +smtp_from_helper=ഗിറ്റീ ഉപയോഗിയ്ക്കുന്ന ഇമെയില്‍ വിലാസം. ഒരു സാധാ ഇമെയിൽ വിലാസം നൽകുക അല്ലെങ്കിൽ "പേര്" എന്ന ഘടന ഉപയോഗിക്കുക. +mailer_user=SMTP ഉപയോക്തൃനാമം +mailer_password=SMTP രഹസ്യവാക്കു് +register_confirm=രജിസ്റ്റർ ചെയ്യുന്നതിന് ഇമെയിൽ സ്ഥിരീകരണം ആവശ്യമാക്കുക +mail_notify=ഇമെയിൽ അറിയിപ്പുകൾ പ്രാപ്തമാക്കുക +server_service_title=സെർവറിന്റെയും മൂന്നാം കക്ഷി സേവനങ്ങളുടെയും ക്രമീകരണങ്ങള്‍ +offline_mode=പ്രാദേശിക മോഡ് പ്രവർത്തനക്ഷമമാക്കുക +offline_mode_popup=മൂന്നാം കക്ഷി ഉള്ളടക്ക ഡെലിവറി നെറ്റ്‌വർക്കുകൾ അപ്രാപ്‌തമാക്കി എല്ലാ വിഭവങ്ങളും പ്രാദേശികമായി നല്‍കുക. +disable_gravatar=ഗ്രവതാര്‍ പ്രവർത്തനരഹിതമാക്കുക +disable_gravatar_popup=ഗ്രവതാര്‍ അല്ലെങ്കില്‍ മൂന്നാം കക്ഷി അവതാർ ഉറവിടങ്ങൾ പ്രവർത്തനരഹിതമാക്കുക. ഒരു ഉപയോക്താവ് പ്രാദേശികമായി ഒരു അവതാർ അപ്‌ലോഡുചെയ്യുന്നില്ലെങ്കിൽ സ്ഥിരസ്ഥിതി അവതാർ ഉപയോഗിക്കും. +federated_avatar_lookup=കേന്ദ്രീകൃത അവതാര്‍ പ്രാപ്തമാക്കുക +federated_avatar_lookup_popup=ലിബ്രാവതാർ ഉപയോഗിച്ച് കേന്ദ്രീക്രത അവതാർ തിരയൽ പ്രാപ്തമാക്കുക. +disable_registration=സ്വയം രജിസ്ട്രേഷൻ അപ്രാപ്തമാക്കുക +disable_registration_popup=ഉപയോക്താക്കള്‍ സ്വയം രജിസ്റ്റര്‍ ചെയ്യുന്നതു അപ്രാപ്യമാക്കുക. അഡ്മിനിസ്ട്രേറ്റർമാർക്ക് മാത്രമേ പുതിയ ഉപയോക്തൃ അക്കൌണ്ടുകൾ സൃഷ്ടിക്കാന്‍ കഴിയൂ. +allow_only_external_registration_popup=ബാഹ്യ സേവനങ്ങളിലൂടെ മാത്രം രജിസ്ട്രേഷന്‍ അനുവദിക്കുക +openid_signin=OpenID പ്രവേശനം പ്രവർത്തനക്ഷമമാക്കുക +openid_signin_popup=OpenID വഴി ഉപയോക്തൃ പ്രവേശനം പ്രാപ്തമാക്കുക. +openid_signup=OpenID സ്വയം രജിസ്ട്രേഷൻ പ്രാപ്തമാക്കുക +openid_signup_popup=OpenID അടിസ്ഥാനമാക്കിയുള്ള ഉപയോക്തൃ സ്വയം രജിസ്ട്രേഷൻ പ്രാപ്തമാക്കുക. +enable_captcha=ക്യാപ്ച പ്രാപ്തമാക്കുക +enable_captcha_popup=ഉപയോക്താക്കള്‍ സ്വയം രജിസ്ട്രേഷന്‍ ചെയ്യുന്നതിനു് ഒരു ക്യാപ്ച ആവശ്യമാണ്. +require_sign_in_view=പേജുകൾ കാണുന്നതിന് സൈറ്റില്‍ പ്രവേശിക്കണം +require_sign_in_view_popup=പേജ് ആക്‌സസ്സ്, പ്രവേശിച്ച ഉപയോക്താക്കൾക്കുമാത്രമായി പരിമിതപ്പെടുത്തുക. സന്ദർശകർ 'പ്രവേശനം', രജിസ്ട്രേഷൻ പേജുകൾ എന്നിവ മാത്രമേ കാണൂ. +admin_setting_desc=ഒരു അഡ്മിനിസ്ട്രേറ്റര്‍ അക്കൗണ്ട് സൃഷ്ടിക്കുന്നത് ഐച്ഛികമാണ്. ആദ്യം രജിസ്റ്റര്‍ ചെയ്ത ഉപയോക്താവ് യാന്ത്രികമായി ഒരു അഡ്മിനിസ്ട്രേറ്ററായി മാറും. +admin_title=അഡ്മിനിസ്ട്രേറ്റര്‍ അക്കൗണ്ട് ക്രമീകരണങ്ങൾ +admin_name=അഡ്മിനിസ്ട്രേറ്ററുടെ ഉപയോക്തൃനാമം +admin_password=രഹസ്യവാക്കു് +confirm_password=രഹസ്യവാക്കു് സ്ഥിരീകരിക്കുക +admin_email=ഇ-മെയില്‍ വിലാസം +install_btn_confirm=ഗിറ്റീ സന്നിവേശിപ്പിയ്ക്കുക +test_git_failed='git' കമാന്‍ഡ് പരീക്ഷിക്കാന്‍ കഴിഞ്ഞില്ല: %v +sqlite3_not_available=ഗിറ്റീയുടെ ഈ വേര്‍ഷന്‍ SQLite3യെ പിന്തുണക്കുന്നില്ല. %s ൽ നിന്നും ഔദ്യോഗിക ബൈനറി പതിപ്പ് ഡൌണ്‍‌ലോഡ് ചെയ്യുക ('gobuild' പതിപ്പല്ല). +invalid_db_setting=ഡാറ്റാബേസ് ക്രമീകരണങ്ങൾ അസാധുവാണ്: %v +invalid_repo_path=കലവറയുടെ റൂട്ട് പാത്ത് അസാധുവാണ്: %v +run_user_not_match='റൺ ആസ്' ഉപയോക്തൃനാമം നിലവിലെ ഉപയോക്തൃനാമമല്ല: %s -> %s +save_config_failed=കോൺഫിഗറേഷൻ സംരക്ഷിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %v +invalid_admin_setting=അഡ്മിനിസ്ട്രേറ്റര്‍ അക്കൌണ്ട് ക്രമീകരണം അസാധുവാണ്: %v +install_success=സ്വാഗതം! ഗിറ്റീ തിരഞ്ഞെടുത്തതിന് നന്ദി. സൂക്ഷിക്കുക, ആസ്വദിക്കൂ,! +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=മറഞ്ഞിരിക്കുന്ന ഇമെയിൽ വിലാസമുള്ള ഉപയോക്താക്കൾക്കുള്ള ഡൊമെയ്ൻ നാമം. ഉദാഹരണത്തിന്, മറഞ്ഞിരിക്കുന്ന ഇമെയിൽ ഡൊമെയ്ൻ 'noreply.example.org' ആയി സജ്ജീകരിച്ചിട്ടുണ്ടെങ്കിൽ 'joe' എന്ന ഉപയോക്താവു് 'joe@noreply.example.org' ആയി ലോഗിൻ ചെയ്യും. [home] - +uname_holder=ഉപയോക്തൃനാമമോ ഇമെയിൽ വിലാസമോ +password_holder=രഹസ്യവാക്കു് +switch_dashboard_context=ഡാഷ്‌ബോർഡ് സന്ദർഭം മാറ്റുക +my_repos=കലവറകള്‍ +show_more_repos=കൂടുതൽ കലവറകള്‍ കാണിക്കുക… +collaborative_repos=സഹകരിക്കാവുന്ന കലവറകള്‍ +my_orgs=എന്റെ സംഘടനകള്‍ +my_mirrors=എന്റെ മിററുകള്‍ +view_home=%s കാണുക +search_repos=ഒരു കലവറ കണ്ടെത്തുക… + +issues.in_your_repos=നിങ്ങളുടെ കലവറകളില്‍ [explore] +repos=കലവറകള്‍ +users=ഉപയോക്താക്കള്‍ +organizations=സംഘടനകള്‍ +search=തിരയുക +code=കോഡ് +repo_no_results=പൊരുത്തപ്പെടുന്ന കലവറകളൊന്നും കണ്ടെത്താനായില്ല. +user_no_results=പൊരുത്തപ്പെടുന്ന ഉപയോക്താക്കളെയൊന്നും കണ്ടെത്താനായില്ല. +org_no_results=പൊരുത്തപ്പെടുന്ന സംഘടനകളൊന്നും കണ്ടെത്താനായില്ല. +code_no_results=നിങ്ങളുടെ തിരയൽ പദവുമായി പൊരുത്തപ്പെടുന്ന സോഴ്സ് കോഡുകളൊന്നും കണ്ടെത്താനായില്ല. +code_search_results=%s എന്നതിനായുള്ള തിരയൽ ഫലങ്ങൾ [auth] +create_new_account=അക്കൗണ്ട് രജിസ്റ്റർ ചെയ്യുക +register_helper_msg=ഇതിനകം ഒരു അക്കൗണ്ട് ഉണ്ടോ? ഇപ്പോൾ പ്രവേശിക്കുക! +social_register_helper_msg=ഇതിനകം ഒരു അക്കൗണ്ട് ഉണ്ടോ? ഇത് ഇപ്പോൾ ബന്ധിപ്പിയ്ക്കുക! +disable_register_prompt=രജിസ്ട്രേഷൻ അപ്രാപ്തമാക്കി. നിങ്ങളുടെ സൈറ്റ് അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക. +disable_register_mail=രജിസ്ട്രേഷനായുള്ള ഇമെയിൽ സ്ഥിരീകരണം അപ്രാപ്തമാക്കി. +remember_me=ഓര്‍മ്മിയ്ക്കുക +forgot_password_title=അടയാളവാക്യം മറന്നുപോയോ +forgot_password=അടയാള വാക്ക് ഓർക്കുന്നില്ലേ? +sign_up_now=ഒരു അക്കൗണ്ട് ആവശ്യമുണ്ടോ? ഇപ്പോള്‍ രജിസ്റ്റര്‍ ചെയ്യുക. +sign_up_successful=അക്കൗണ്ട് വിജയകരമായി സൃഷ്ടിച്ചു. +confirmation_mail_sent_prompt=%s ലേക്ക് ഒരു പുതിയ സ്ഥിരീകരണ ഇമെയിൽ അയച്ചു. രജിസ്ട്രേഷൻ പ്രക്രിയ പൂർത്തിയാക്കുന്നതിന് അടുത്ത %s നുള്ളിൽ നിങ്ങളുടെ ഇൻ‌ബോക്സ് പരിശോധിക്കുക. +must_change_password=നിങ്ങളുടെ രഹസ്യവാക്കു് പുതുക്കുക +allow_password_change=രഹസ്യവാക്കു് മാറ്റാൻ ഉപയോക്താവിനോട് ആവശ്യപ്പെടുക (ശുപാർശിതം) +reset_password_mail_sent_prompt=%s ലേക്ക് ഒരു പുതിയ സ്ഥിരീകരണ ഇമെയിൽ അയച്ചു. അക്കൗണ്ട് വീണ്ടെടുക്കൽ പ്രക്രിയ പൂർത്തിയാക്കുന്നതിന് അടുത്ത %s നുള്ളിൽ നിങ്ങളുടെ ഇൻ‌ബോക്സ് പരിശോധിക്കുക. +active_your_account=നിങ്ങളുടെ അക്കൗണ്ട് സജീവമാക്കുക +account_activated=നിങ്ങളുടെ അക്കൗണ്ട് സജീവമാക്കി +prohibit_login=പ്രവേശനം നിരോധിച്ചിരിക്കുന്നു +prohibit_login_desc=നിങ്ങളുടെ അക്കൗണ്ടിലേയ്ക്കുള്ള പ്രവേശനം നിരോധിച്ചിരിക്കുന്നു, ദയവായി നിങ്ങളുടെ സൈറ്റ് അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക. +resent_limit_prompt=നിങ്ങൾ അടുത്തിടെ ഒരു സജീവമാക്കൽ ഇമെയിൽ അഭ്യർത്ഥിച്ചു. 3 മിനിറ്റ് കാത്തിരുന്ന് വീണ്ടും ശ്രമിക്കുക. +has_unconfirmed_mail=ഹായ് %s, നിങ്ങൾക്ക് സ്ഥിരീകരിക്കാത്ത ഇമെയിൽ വിലാസം (%s) ഉണ്ട്. നിങ്ങൾക്ക് ഒരു സ്ഥിരീകരണ ഇമെയിൽ ലഭിച്ചില്ലെങ്കിലോ പുതിയതൊന്ന് വീണ്ടും അയയ്‌ക്കേണ്ടതുണ്ടെങ്കിലോ, ചുവടെയുള്ള ബട്ടണിൽ ക്ലിക്കുചെയ്യുക. +resend_mail=നിങ്ങളുടെ സജീവമാക്കൽ ഇമെയിൽ വീണ്ടും അയയ്‌ക്കാൻ ഇവിടെ ക്ലിക്കുചെയ്യുക +email_not_associate=ഇമെയിൽ വിലാസം ഏതെങ്കിലും അക്കൗണ്ടുമായി ബന്ധപ്പെടുത്തിയിട്ടില്ല. +send_reset_mail=അക്കൗണ്ട് വീണ്ടെടുക്കൽ ഇമെയിൽ അയയ്‌ക്കുക +reset_password=അക്കൗണ്ട് വീണ്ടെടുക്കൽ +invalid_code=നിങ്ങളുടെ സ്ഥിരീകരണ കോഡ് അസാധുവാണ് അല്ലെങ്കിൽ കാലഹരണപ്പെട്ടു. +reset_password_helper=അക്കൗണ്ട് വീണ്ടെടുക്കുക +reset_password_wrong_user=നിങ്ങൾ %s ആയി സൈൻ ഇൻ ചെയ്‌തു, പക്ഷേ അക്കൗണ്ട് വീണ്ടെടുക്കൽ ലിങ്ക് %s എന്നതിനാണ് +password_too_short=പാസ്‌വേഡ് ദൈർഘ്യം %d അക്ഷരങ്ങളിലും കുറവായിരിക്കരുത്. +non_local_account=പ്രാദേശിക ഇതര ഉപയോക്താക്കൾക്ക് ഗിറ്റീ വെബ് വഴി പാസ്‌വേഡ് പുതുക്കാന്‍ ചെയ്യാൻ കഴിയില്ല. +verify=പ്രമാണീകരിയ്ക്കുക +scratch_code=സ്ക്രാച്ച് കോഡ് +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_signin_title=അക്കൗണ്ട് ബന്ധിപ്പിയ്ക്കുന്നതു് അംഗീകരിക്കുന്നതിനായി സൈറ്റിലേയ്ക്കു് പ്രവേശിക്കുക +oauth_signin_submit=അക്കൌണ്ട് ബന്ധിപ്പിയ്ക്കുക +openid_connect_submit=ബന്ധിപ്പിക്കുക +openid_connect_title=നിലവിലുള്ള അക്കൗണ്ടുമായി ബന്ധിപ്പിയ്ക്കുക +openid_connect_desc=തിരഞ്ഞെടുത്ത ഓപ്പൺഐഡി യുആർഐ അജ്ഞാതമാണ്. ഇവിടെ നിന്നും ഒരു പുതിയ അക്കൗണ്ടുമായി ബന്ധപ്പെടുത്തുക. +openid_register_title=അംഗത്വമെടുക്കുക +openid_register_desc=തിരഞ്ഞെടുത്ത ഓപ്പൺഐഡി യുആർഐ അജ്ഞാതമാണ്. ഇവിടെ നിന്നും ഒരു പുതിയ അക്കൗണ്ടുമായി ബന്ധപ്പെടുത്തുക. +openid_signin_desc=നിങ്ങളുടെ OpenID URI നൽകുക. ഉദാഹരണത്തിന്: https://anne.me, bob.openid.org.cn അല്ലെങ്കിൽ gnusocial.net/carry. +disable_forgot_password_mail=അക്കൗണ്ട് വീണ്ടെടുക്കൽ പ്രവർത്തനരഹിതമാണ്. നിങ്ങളുടെ സൈറ്റ് അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക. +email_domain_blacklisted=നിങ്ങളുടെ ഇമെയിൽ വിലാസത്തിൽ രജിസ്റ്റർ ചെയ്യാൻ കഴിയില്ല. +authorize_application=അപ്ലിക്കേഷനു് അംഗീകാരം നല്കുക +authorize_application_created_by=%s സൃഷ്‌ടിച്ച അപ്ലിക്കേഷൻ ആണ്. +authorize_application_description=നിങ്ങൾ പ്രവേശനം അനുവദിക്കുകയാണെങ്കിൽ, സ്വകാര്യ റിപ്പോകളും ഓർഗനൈസേഷനുകളും ഉൾപ്പെടെ നിങ്ങളുടെ എല്ലാ അക്കൌണ്ട് വിവരങ്ങള്‍ നേടാനും വേണമെങ്കില്‍‍ മാറ്റങ്ങള്‍ വരുത്താനും അതിന് കഴിയും. +authorize_title=നിങ്ങളുടെ അക്കൌണ്ടില്‍ പ്രവേശിയ്ക്കുന്നതിനു് "%s"നു് അംഗീകാരം നൽകണോ? +authorization_failed=അംഗീകാരം നല്‍കുന്നതില്‍ പരാജയപ്പെട്ടു +authorization_failed_desc=അസാധുവായ ഒരു അഭ്യർത്ഥന കണ്ടെത്തിയതിനാൽ ഞങ്ങൾ അംഗീകാരം പരാജയപ്പെടുത്തി. ദയവായി നിങ്ങൾ അംഗീകരിക്കാൻ ശ്രമിച്ച അപ്ലിക്കേഷന്റെ പരിപാലകനുമായി ബന്ധപ്പെടുക. [mail] +activate_account=നിങ്ങളുടെ അക്കൗണ്ട് സജീവമാക്കുക +activate_email=ഇമെയില്‍ വിലാസം സ്ഥിരീകരിയ്ക്കുക +reset_password=നിങ്ങളുടെ അക്കൗണ്ട് വീണ്ടെടുക്കുക +register_success=രജിസ്ട്രേഷൻ വിജയകരം +register_notify=ഗിറ്റീയിലേയ്ക്കു് സ്വാഗതം [modal] +yes=അതെ +no=ഇല്ല +modify=പുതുക്കുക [form] - - - - - - - +UserName=ഉപയോക്ത്രു നാമം +RepoName=കലവറയുടെ പേരു് +Email=ഇ-മെയില്‍ വിലാസം +Password=രഹസ്യവാക്കു് +Retype=രഹസ്യവാക്കു് വീണ്ടും നല്‍കുക +SSHTitle=SSH കീയുടെ പേരു് +HttpsUrl=HTTPS URL +PayloadUrl=പേലോഡ് URL +TeamName=ടീമിന്റെ പേരു് +AuthName=അംഗീകാരത്തിന്റെ പേരു് +AdminEmail=അഡ്‌മിൻ ഇമെയിൽ + +NewBranchName=പുതിയ ശാഖയുടെ പേരു് +CommitSummary=നിയോഗത്തിന്റെ സംഗ്രഹം +CommitMessage=നിയോഗത്തിന്റെ സന്ദേശം +CommitChoice=നിയോഗത്തിന്റെ തിരഞ്ഞെടുക്കല്‍ +TreeName=ഫയല്‍ പാത്ത് +Content=ഉള്ളടക്കം + + +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=സാധുവായ ഒരു ഈ-മെയിൽ വിലാസം അല്ല +url_error=`സാധുവായ ഒരു URL അല്ല.` +include_error=`%s'എന്ന ഉപവാക്യം അടങ്ങിയിരിക്കണം.` +glob_pattern_error=ഗ്ലോബു് ശൃേണി തെറ്റാണു്: %s +unknown_error=അജ്ഞാതമായ പിശക്: +captcha_incorrect=ക്യാപ്ച കോഡ് തെറ്റാണ്. +password_not_match=രഹസ്യവാക്കുകള്‍ യോജിക്കുന്നില്ല. + +username_been_taken=ഉപയോക്തൃനാമം ലഭ്യമല്ല. +repo_name_been_taken=കലവറയുടെ പേരു് ഇതിനോടകം ഉപയോഗിച്ചിട്ടുണ്ടു്. +visit_rate_limit=വിദൂര വിലാസം വിവരകൈമാറ്റത്തിനു് പരിധി നിശ്ചയിച്ചിട്ടുണ്ടു്. +2fa_auth_required=വിദൂര വിലാസം ഇരട്ട ഘടക പ്രാമാണീകരണം ആവശ്യപ്പെടുന്നുണ്ടു്. +org_name_been_taken=സംഘടനയുടെ പേര് ഇതിനകം എടുത്തിട്ടുണ്ട്. +team_name_been_taken=ടീമിന്റെ പേര് ഇതിനകം എടുത്തിട്ടുണ്ട്. +team_no_units_error=കുറഞ്ഞത് ഒരു കലവറ വിഭാഗത്തിലേക്ക് പ്രവേശനം അനുവദിക്കുക. +email_been_used=ഈ ഇമെയിൽ വിലാസം ഇതിനു മുന്നേ എടുത്തിട്ടുണ്ട്. +openid_been_used=%s എന്ന ഓപ്പണ്‍ഐഡി വിലാസം ഇതിനു മുന്നേ എടുത്തിട്ടുണ്ട്. +username_password_incorrect=ഉപഭോക്തൃനാമമോ രഹസ്യവാക്കോ തെറ്റാണ്. +enterred_invalid_repo_name=ഈ കവവറയുടെ പേരു് തെറ്റാണു്. +enterred_invalid_owner_name=പുതിയ ഉടമസ്ഥന്റെ പേരു് സാധുവല്ല. +enterred_invalid_password=താങ്കള്‍ നല്‍കിയ രഹസ്യവാക്കു് തെറ്റാണ്. +user_not_exist=ഉപയോക്താവ് നിലവിലില്ല. +last_org_owner='ഉടമകളുടെ' ടീമിൽ നിന്നും അവസാനത്തെ ഉപയോക്താവിനെ നീക്കംചെയ്യാൻ നിങ്ങൾക്ക് കഴിയില്ല. ടീമിൽ കുറഞ്ഞത് ഒരു ഉടമയെങ്കിലും ഉണ്ടായിരിക്കണം. +cannot_add_org_to_team=ഒരു സംഘടനയെ ടീം അംഗമായി ചേർക്കാൻ കഴിയില്ല. + +invalid_ssh_key=നിങ്ങളുടെ SSH കീ സ്ഥിരീകരിക്കാൻ കഴിയില്ല: %s +invalid_gpg_key=നിങ്ങളുടെ GPG കീ സ്ഥിരീകരിക്കാൻ കഴിയില്ല: %s +unable_verify_ssh_key=SSH കീ സ്ഥിരീകരിക്കാൻ കഴിയില്ല; തെറ്റുകളുണ്ടോയെന്നു് ഒന്നുകൂടി പരിശോധിക്കുക. +auth_failed=പ്രാമാണീകരണം പരാജയപ്പെട്ടു: %v + +still_own_repo=നിങ്ങളുടെ അക്കൗണ്ടിന് ഒന്നോ അതിലധികമോ കലവറകള്‍ ഉണ്ട്; ആദ്യം അവ ഇല്ലാതാക്കുക അല്ലെങ്കിൽ കൈമാറുക. +still_has_org=നിങ്ങളുടെ അക്കൗണ്ട് ഒന്നോ അതിലധികമോ സംഘടനകളില്‍ അംഗമാണ്; ആദ്യം അവ വിടുക. +org_still_own_repo=നിങ്ങളുടെ സംഘടന ഇനിയും ഒന്നോ അതിലധികമോ കലവറകളുടെ ഉടമസ്ഥനാണു്; ആദ്യം അവ ഇല്ലാതാക്കുക അല്ലെങ്കിൽ കൈമാറുക. + +target_branch_not_exist=ലക്ഷ്യമാക്കിയ ശാഖ നിലവിലില്ല. [user] - +change_avatar=നിങ്ങളുടെ അവതാർ മാറ്റുക… +join_on=ചേർന്നതു് +repositories=കലവറകള്‍ +activity=പൊതുവായ പ്രവർത്തനങ്ങള്‍ +followers=പിന്തുടരുന്നവര്‍‌ +starred=നക്ഷത്രമിട്ട കലവറകള്‍ +following=പിന്തുടരുന്നവര്‍ +follow=പിന്തുടരൂ +unfollow=പിന്തുടരുന്നത് നിര്‍ത്തുക +heatmap.loading=ഹീറ്റ്മാപ്പ് ലോഡുചെയ്യുന്നു… +user_bio=ജീവചരിത്രം + +form.name_reserved='%s' എന്ന ഉപയോക്തൃനാമം മറ്റാവശ്യങ്ങള്‍ക്കായി നീക്കിവച്ചിരിക്കുന്നു. +form.name_pattern_not_allowed=ഉപയോക്തൃനാമത്തിൽ '%s' എന്ന ശ്രേണി അനുവദനീയമല്ല. [settings] - - - - - - - - - - - - - - - +profile=പ്രൊഫൈൽ +account=അക്കൗണ്ട് +password=രഹസ്യവാക്കു് +security=സുരക്ഷ +avatar=അവതാര്‍ +ssh_gpg_keys=SSH / GPG കീകള്‍ +social=സോഷ്യൽ അക്കൗണ്ടുകൾ +applications=അപ്ലിക്കേഷനുകൾ +orgs=സംഘടനകളെ നിയന്ത്രിക്കുക +repos=കലവറകള്‍ +delete=അക്കൗണ്ട് ഇല്ലാതാക്കുക +twofa=ഇരട്ട ഘടക പ്രാമാണീകരണം +account_link=ബന്ധിപ്പിച്ച അക്കൌണ്ടുകള്‍ +organization=സംഘടനകള്‍ +uid=Uid +u2f=സുരക്ഷാ കീകൾ + +public_profile=പരസ്യമായ പ്രൊഫൈൽ +profile_desc=അറിയിപ്പുകൾക്കും മറ്റ് പ്രവർത്തനങ്ങൾക്കുമായി നിങ്ങളുടെ ഇമെയിൽ വിലാസം ഉപയോഗിക്കും. +password_username_disabled=പ്രാദേശികമല്ലാത്ത ഉപയോക്താക്കൾക്ക് അവരുടെ ഉപയോക്തൃനാമം മാറ്റാൻ അനുവാദമില്ല. കൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങളുടെ സൈറ്റ് അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക. +full_name=പൂർണ്ണമായ പേര് +website=വെബ് സൈറ്റ് +location=സ്ഥലം +update_theme=പ്രമേയം പുതുക്കുക +update_profile=പ്രോഫൈല്‍ പരിഷ്കരിക്കുക +update_profile_success=നിങ്ങളുടെ പ്രൊഫൈൽ പരിഷ്കരിച്ചിരിക്കുന്നു. +change_username=നിങ്ങളുടെ ഉപയോക്തൃനാമം മാറ്റി. +change_username_prompt=കുറിപ്പ്: ഉപയോക്തൃനാമത്തിലെ മാറ്റം നിങ്ങളുടെ അക്കൗണ്ട് URLഉം മാറ്റുന്നു. +continue=തുടരുക +cancel=റദ്ദാക്കുക +language=ഭാഷ +ui=പ്രമേയങ്ങള്‍ + +lookup_avatar_by_mail=ഇമെയിൽ വിലാസം അനുസരിച്ച് അവതാർ കണ്ടെത്തുക +federated_avatar_lookup=കേന്ദ്രീക്രത അവതാര്‍ കണ്ടെത്തല്‍ +enable_custom_avatar=ഇഷ്‌ടാനുസൃത അവതാർ ഉപയോഗിക്കുക +choose_new_avatar=പുതിയ അവതാർ തിരഞ്ഞെടുക്കുക +update_avatar=അവതാർ പുതുക്കുക +delete_current_avatar=നിലവിലെ അവതാർ ഇല്ലാതാക്കുക +uploaded_avatar_not_a_image=അപ്‌ലോഡുചെയ്‌ത ഫയൽ ഒരു ചിത്രമല്ല. +uploaded_avatar_is_too_big=അപ്‌ലോഡുചെയ്‌ത ഫയൽ പരമാവധി വലുപ്പം കവിഞ്ഞു. +update_avatar_success=നിങ്ങളുടെ അവതാര്‍ പരിഷ്കരിച്ചിരിക്കുന്നു. + +change_password=പാസ്‌വേഡ് പുതുക്കുക +old_password=നിലവിലുള്ള രഹസ്യവാക്കു് +new_password=പുതിയ രഹസ്യവാക്കു് +retype_new_password=പുതിയ രഹസ്യവാക്കു് വീണ്ടും നല്‍കുക +password_incorrect=നിലവിലെ പാസ്‌വേഡ് തെറ്റാണ്. +change_password_success=നിങ്ങളുടെ പാസ്‌വേഡ് അപ്‌ഡേറ്റുചെയ്‌തു. ഇനി മുതൽ നിങ്ങളുടെ പുതിയ പാസ്‌വേഡ് ഉപയോഗിച്ച് പ്രവേശിക്കുക. +password_change_disabled=പ്രാദേശിക ഇതര ഉപയോക്താക്കൾക്ക് ഗിറ്റീ വെബ് വഴി പാസ്‌വേഡ് പുതുക്കാന്‍ ചെയ്യാൻ കഴിയില്ല. + +emails=ഇ-മെയില്‍ വിലാസങ്ങള്‍ +manage_emails=ഇമെയിൽ വിലാസങ്ങൾ നിയന്ത്രിക്കുക +manage_themes=സ്ഥിരസ്ഥിതി പ്രമേയം തിരഞ്ഞെടുക്കുക +manage_openid=ഓപ്പൺഐഡി വിലാസങ്ങൾ നിയന്ത്രിക്കുക +email_desc=അറിയിപ്പുകൾക്കും മറ്റ് പ്രവർത്തനങ്ങൾക്കുമായി നിങ്ങളുടെ പ്രാഥമിക ഇമെയിൽ വിലാസം ഉപയോഗിക്കും. +theme_desc=സൈറ്റിലുടനീളം ഇത് നിങ്ങളുടെ സ്ഥിരസ്ഥിതി പ്രമേയം ആയിരിക്കും. +primary=പ്രാഥമികം +primary_email=പ്രാഥമികമാക്കുക +delete_email=നീക്കം ചെയ്യുക +email_deletion=ഈ-മെയില്‍ വിലാസം നീക്കം ചെയ്യുക +email_deletion_desc=ഇമെയിൽ വിലാസവും അനുബന്ധ വിവരങ്ങളും നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് നീക്കംചെയ്യും. ഈ ഇമെയിൽ വിലാസം വഴിയുള്ള ഗിറ്റു് നിയോഗങ്ങളും മാറ്റമില്ലാതെ ഉണ്ടാകും. തുടരട്ടെ? +email_deletion_success=ഇമെയിൽ വിലാസം നീക്കംചെയ്‌തു. +theme_update_success=നിങ്ങളുടെ പ്രമേയം പുതുക്കി. +theme_update_error=തിരഞ്ഞെടുത്ത പ്രമേയം നിലവിലില്ല. +openid_deletion=OpenID വിലാസം നീക്കം ചെയ്യുക +openid_deletion_desc=നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് ഓപ്പൺഐഡി വിലാസം നീക്കംചെയ്യുന്നത് ഇതുപയോഗിച്ചു് ഇനി പ്രവേശിക്കുന്നതിൽ നിന്ന് നിങ്ങളെ തടയും. തുടരട്ടെ? +openid_deletion_success=ഓപ്പൺഐഡി വിലാസം നീക്കംചെയ്‌തു. +add_new_email=ഈ-മെയില്‍ വിലാസം ചേര്‍ക്കുക +add_new_openid=പുതിയ ഓപ്പണ്‍ ഐഡി വിലാസം ചേര്‍ക്കുക +add_email=ഈ-മെയില്‍ വിലാസം ചേര്‍ക്കുക +add_openid=ഓപ്പണ്‍ ഐഡി വിലാസം ചേര്‍ക്കുക +add_email_confirmation_sent=ഒരു സ്ഥിരീകരണ ഇമെയിൽ '%s' ലേക്ക് അയച്ചു. നിങ്ങളുടെ ഇമെയിൽ വിലാസം സ്ഥിരീകരിക്കുന്നതിന് അടുത്ത %s നുള്ളിൽ നിങ്ങളുടെ ഇൻ‌ബോക്സ് പരിശോധിക്കുക. +add_email_success=പുതിയ ഇമെയിൽ വിലാസം ചേര്‍ത്തു. +add_openid_success=പുതിയ ഓപ്പണ്‍ഐഡി വിലാസം ചേര്‍ത്തു. +keep_email_private=ഈ-മെയില്‍ വിലാസം മറയ്ക്കുക +keep_email_private_popup=നിങ്ങളുടെ ഇമെയിൽ വിലാസം മറ്റ് ഉപയോക്താക്കു് കാണാനാകില്ല. +openid_desc=ഒരു ബാഹ്യ ദാതാവിന് പ്രാമാണീകരണം നിയുക്തമാക്കാൻ ഓപ്പൺഐഡി നിങ്ങളെ അനുവദിക്കുന്നു. + +manage_ssh_keys=​എസ്. എസ്. എച്ച് കീകള്‍ നിയന്ത്രിക്കുക +manage_gpg_keys=ജീ പീ. ജി കീകള്‍ നിയന്ത്രിക്കുക +add_key=കീ ചേര്‍ക്കുക +ssh_desc=ഇവയാണു് നിങ്ങളുടെ അക്കൗണ്ടുമായി ബന്ധപ്പെടുത്തിയിരിക്കുന്ന പൊതുവായ എസ്. എസ്. എച്ച് കീകൾ. ഇതിനോടനു ബന്ധിപ്പിച്ചിട്ടുള്ള സ്വകാര്യ കീകൾ നിങ്ങളുടെ കലവറകളിലേയ്ക്കു് പൂർണ്ണ ആക്സസ് അനുവദിക്കുന്നു. +gpg_desc=ഈ പൊതു GPG കീകൾ നിങ്ങളുടെ അക്കൗണ്ടുമായി ബന്ധപ്പെട്ടിരിക്കുന്നു. കമ്മിറ്റുകളെ പരിശോധിച്ചുറപ്പിക്കാൻ നിങ്ങളുടെ സ്വകാര്യ കീകൾ അനുവദിക്കുന്നതിനാൽ അവ സുരക്ഷിതമായി സൂക്ഷിക്കുക. +ssh_helper=സഹായം ആവശ്യമുണ്ടോ? നിങ്ങളുടെ സ്വന്തം SSH കീകൾ സൃഷ്ടിക്കുക, അല്ലെങ്കിൽ പൊതുവായ പ്രശ്നങ്ങൾ എന്നിവയ്ക്കായുള്ള ഗിറ്റ്ഹബ്ബിന്റെ മാര്‍ഗദര്‍ശനങ്ങള്‍ ഉപയോഗിച്ചു് നിങ്ങൾക്ക് എസ്. എസ്. എച്ചുമായി ബന്ധപ്പെട്ട പ്രശ്നങ്ങള്‍ പരിഹരിക്കാം. +gpg_helper= സഹായം ആവശ്യമുണ്ടോ? ജിപിജിയെക്കുറിച്ച് ഗിറ്റ്ഹബിന്റെ മാര്‍ഗ്ഗനിര്‍ദ്ദേശങ്ങള്‍ പരിശോധിയ്ക്കുക. +add_new_key=SSH കീ ചേർക്കുക +add_new_gpg_key=GPG കീ ചേർക്കുക +ssh_key_been_used=ഈ SSH കീ ഇതിനകം ചേർത്തു. +ssh_key_name_used=ഇതേ പേരിലുള്ള ഒരു SSH കീ ഇതിനകം നിങ്ങളുടെ അക്കൗണ്ടിലേക്ക് ചേർത്തിട്ടുണ്ടു്. +gpg_key_id_used=സമാന ഐഡിയുള്ള ഒരു പൊതു ജിപിജി കീ ഇതിനകം നിലവിലുണ്ട്. +gpg_no_key_email_found=നിങ്ങളുടെ അക്കൗണ്ടുമായി ബന്ധപ്പെട്ട ഏതെങ്കിലും ഇമെയിൽ വിലാസത്തിൽ ഈ GPG കീ ഉപയോഗിക്കാൻ കഴിയില്ല. +subkeys=സബ് കീകള്‍ +key_id=കീ ഐഡി +key_name=കീയുടെ പേരു് +key_content=ഉള്ളടക്കം +add_key_success='%s' എന്ന SSH കീ ചേർത്തു. +add_gpg_key_success='%s' എന്ന GPG കീ ചേർത്തു. +delete_key=നീക്കം ചെയ്യുക +ssh_key_deletion=SSH കീ നീക്കം ചെയ്യുക +gpg_key_deletion=GPG കീ നീക്കം ചെയ്യുക +ssh_key_deletion_desc=ഒരു SSH കീ നീക്കംചെയ്യുന്നത് നിങ്ങളുടെ അക്കൌണ്ടിലേക്കുള്ള പ്രവേശനം അസാധുവാക്കുന്നു. തുടരട്ടെ? +gpg_key_deletion_desc=ഒരു ജി‌പി‌ജി കീ നീക്കംചെയ്യുന്നത് അതിൽ ഒപ്പിട്ട കമ്മിറ്റുകളെ സ്ഥിരീകരിക്കില്ല. തുടരട്ടെ? +ssh_key_deletion_success=SSH കീ നീക്കംചെയ്‌തു. +gpg_key_deletion_success=GPG കീ നീക്കംചെയ്‌തു. +add_on=ചേര്‍ത്തതു് +valid_until=വരെ സാധുവാണ് +valid_forever=എന്നും സാധുവാണു് +last_used=അവസാനം ഉപയോഗിച്ചത് +no_activity=സമീപകാലത്തു് പ്രവർത്തനങ്ങളൊന്നുമില്ല +can_read_info=വായിയ്ക്കുക +can_write_info=എഴുതുക +key_state_desc=കഴിഞ്ഞ 7 ദിവസങ്ങളിൽ ഈ കീ ഉപയോഗിച്ചു +token_state_desc=ഈ ടോക്കൺ കഴിഞ്ഞ 7 ദിവസങ്ങളിൽ ഉപയോഗിച്ചു +show_openid=പ്രൊഫൈലിൽ കാണുക +hide_openid=പ്രൊഫൈലിൽ നിന്ന് മറയ്‌ക്കുക +ssh_disabled=SSH അപ്രാപ്‌തമാക്കി + +manage_social=സഹവസിക്കുന്ന സോഷ്യൽ അക്കൗണ്ടുകളെ നിയന്ത്രിക്കുക +social_desc=ഈ സോഷ്യൽ അക്കൗണ്ടുകൾ നിങ്ങളുടെ ഗിറ്റീ അക്കൗണ്ടുമായി ലിങ്കുചെയ്‌തു. ഇവ നിങ്ങളുടെ ഗീറ്റീ അക്കൗണ്ടിലേക്ക് പ്രവേശിക്കാൻ ഉപയോഗിക്കാവുന്നതിനാൽ അവയെല്ലാം നിങ്ങൾ തിരിച്ചറിഞ്ഞുവെന്ന് ഉറപ്പാക്കുക. +unbind=അൺലിങ്ക് ചെയ്യുക +unbind_success=നിങ്ങളുടെ ഗീറ്റീ അക്കൗണ്ടിൽ നിന്ന് സോഷ്യൽ അക്കൗണ്ട് അൺലിങ്ക് ചെയ്തു. + +manage_access_token=ആക്‌സസ്സ് ടോക്കണുകൾ നിയന്ത്രിക്കുക +generate_new_token=പുതിയ ടോക്കൺ സൃഷ്‌ടിക്കുക +tokens_desc=ഈ ടോക്കണുകൾ ഗിറ്റീ API ഉപയോഗിച്ച് നിങ്ങളുടെ അക്കൌണ്ടിലേക്ക് പ്രവേശനം നൽകുന്നു. +new_token_desc=ഒരു ടോക്കൺ ഉപയോഗിക്കുന്ന അപ്ലിക്കേഷനുകൾക്ക് നിങ്ങളുടെ അക്കൌണ്ടിലേക്ക് പൂർണ്ണ പ്രവേശനം ഉണ്ട്. +token_name=ടോക്കണിന്റെ പേരു് +generate_token=ടോക്കൺ സൃഷ്‌ടിക്കുക +generate_token_success=നിങ്ങളുടെ പുതിയ ടോക്കൺ ജനറേറ്റുചെയ്‌തു. ഇത് വീണ്ടും കാണിക്കാത്തതിനാൽ ഇപ്പോൾ തന്നെ പകർത്തുക. +delete_token=നീക്കം ചെയ്യുക +access_token_deletion=ആക്‌സസ്സ് ടോക്കണ്‍ നീക്കം ചെയ്യുക +access_token_deletion_desc=ഒരു ടോക്കൺ ഇല്ലാതാക്കുന്നത് നിങ്ങളുടെ അക്കൗണ്ട് ഉപയോഗിക്കുന്ന അപ്ലിക്കേഷനുകൾക്കുള്ള പ്രവേശനം അസാധുവാക്കും. തുടരട്ടേ? +delete_token_success=ടോക്കൺ ഇല്ലാതാക്കി. ഇനി ഇത് ഉപയോഗിക്കുന്ന അപ്ലിക്കേഷനുകൾക്ക് നിങ്ങളുടെ അക്കൌണ്ടിലേക്ക് പ്രവേശനം ഉണ്ടാകില്ല. + +manage_oauth2_applications=OAuth2 അപ്ലിക്കേഷനുകൾ നിയന്ത്രിക്കുക +edit_oauth2_application=OAuth2 അപ്ലിക്കേഷൻ എഡിറ്റുചെയ്യുക +oauth2_applications_desc=നിങ്ങളുടെ മൂന്നാം കക്ഷി അപ്ലിക്കേഷനെ, ഈ ഗിറ്റീ ഇന്‍സ്റ്റാളേഷനുമായി സുരക്ഷിതമായി ഉപയോക്താക്കളെ പ്രാമാണീകരിക്കാൻ OAuth2 അപ്ലിക്കേഷനുകൾ പ്രാപ്തമാക്കുന്നു. +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_select_type=ഏത് തരം അപ്ലിക്കേഷനാണ് ഇതു്? +oauth2_type_web=വെബ് (e.g. Node.JS, Tomcat, Go) +oauth2_type_native=നേറ്റീവ് (ഉദാ. മൊബൈൽ, ഡെസ്ക്ടോപ്പ്, ബ്രൌസർ) +oauth2_redirect_uri=URI റീഡയറക്‌ട് ചെയ്യുക +save_application=സംരക്ഷിയ്ക്കുക +oauth2_client_id=ക്ലൈന്റ് ഐഡി +oauth2_client_secret=ക്ലൈന്റു് രഹസ്യം +oauth2_regenerate_secret=രഹസ്യം പുനഃസൃഷ്ടിയ്ക്കുക +oauth2_regenerate_secret_hint=നിങ്ങളുടെ രഹസ്യം നഷ്ടപ്പെട്ടോ? +oauth2_client_secret_hint=നിങ്ങൾ ഈ പേജ് വീണ്ടും സന്ദർശിക്കുകയാണെങ്കിൽ രഹസ്യം ദൃശ്യമാകില്ല. നിങ്ങളുടെ രഹസ്യം സംരക്ഷിക്കുക. +oauth2_application_edit=ക്രമീകരിക്കുക +oauth2_application_create_description=OAuth2 ആപ്ലിക്കേഷനുകൾ നിങ്ങളുടെ മൂന്നാം കക്ഷി ആപ്ലിക്കേഷൻ ഉപയോക്തൃ അക്കൌണ്ടുകളിലേക്ക് ആക്സസ് നൽകുന്നു. +oauth2_application_remove_description=ഒരു OAuth2 ആപ്ലിക്കേഷൻ നീക്കംചെയ്യുന്നത് ഈ സന്ദർഭത്തിൽ അംഗീകൃത ഉപയോക്തൃ അക്കൌണ്ടുകളിലേക്ക് പ്രവേശിക്കുന്നത് തടയും. തുടരട്ടെ? + +authorized_oauth2_applications=അംഗീകൃത OAuth2 അപ്ലിക്കേഷനുകൾ +authorized_oauth2_applications_description=ഈ മൂന്നാം കക്ഷി അപ്ലിക്കേഷനുകളിലേക്ക് നിങ്ങളുടെ സ്വകാര്യ ഗീറ്റീ അക്കൗണ്ടിലേക്ക് പ്രവേശനം അനുവദിച്ചു. അപ്ലിക്കേഷനുകൾക്കായുള്ള നിയന്ത്രണം ഇനി ആവശ്യമില്ല. +revoke_key=അസാധുവാക്കുക +revoke_oauth2_grant=നിയന്ത്രണം തിരിച്ചെടുക്കുക +revoke_oauth2_grant_description=ഈ മൂന്നാം കക്ഷി ആപ്ലിക്കേഷനായി ആക്സസ് അസാധുവാക്കുന്നത് നിങ്ങളുടെ ഡാറ്റ ആക്സസ് ചെയ്യുന്നതിൽ നിന്ന് ഈ ആപ്ലിക്കേഷനെ തടയും. നിങ്ങള്‍ക്ക് ഉറപ്പാണോ? +revoke_oauth2_grant_success=നിങ്ങൾ വിജയകരമായി പ്രവേശനം റദ്ദാക്കി. + +twofa_desc=ഇരട്ട ഘടക പ്രാമാണീകരണം നിങ്ങളുടെ അക്കൗണ്ടിന്റെ സുരക്ഷ വർദ്ധിപ്പിക്കുന്നു. +twofa_is_enrolled=നിങ്ങളുടെ അക്കൗണ്ട് നിലവിൽ ഇരട്ട ഘടക പ്രമാണീകരണത്തിനു് എൻറോൾ ചെയ്തിട്ടുണ്ട്. . +twofa_not_enrolled=നിങ്ങളുടെ അക്കൗണ്ട് നിലവിൽ ഇരട്ട ഘടക പ്രമാണീകരണത്തിനു് എൻറോൾ ചെയ്തിട്ടില്ല.. +twofa_disable=ഇരട്ട ഘടക പ്രാമാണീകരണം റദ്ദാക്കി +twofa_scratch_token_regenerate=സ്ക്രാച്ച് ടോക്കൺ പുനഃനിര്‍മ്മിയ്ക്കുക +twofa_scratch_token_regenerated=%s ആണ് ഇപ്പോൾ നിങ്ങളുടെ സ്ക്രാച്ച് ടോക്കൺ. സുരക്ഷിതമായ സ്ഥലത്ത് സൂക്ഷിക്കുക. +twofa_enroll=ഇരട്ട ഘടക പ്രാമാണീകരണത്തില്‍ അംഗമാകുക +twofa_disable_note=ആവശ്യമെങ്കിൽ നിങ്ങൾക്ക് രണ്ട്-ഘടക പ്രാമാണീകരണം അപ്രാപ്തമാക്കാൻ കഴിയും. +twofa_disable_desc=രണ്ട്-ഘടക പ്രാമാണീകരണം അപ്രാപ്‌തമാക്കുന്നത് നിങ്ങളുടെ അക്കൗണ്ട് സുരക്ഷിതമല്ലാത്തതാക്കും. തുടരട്ടെ? +regenerate_scratch_token_desc=നിങ്ങളുടെ സ്ക്രാച്ച് ടോക്കൺ തെറ്റായി സ്ഥാപിക്കുകയോ അല്ലെങ്കിൽ സൈൻ ഇൻ ചെയ്യാൻ ഇതിനകം ഉപയോഗിക്കുകയോ ചെയ്തിട്ടുണ്ടെങ്കിൽ അത് ഇവിടെനിന്നു് പുനഃസജ്ജമാക്കാൻ കഴിയും. +twofa_disabled=രണ്ട്-ഘട്ട പ്രാമാണീകരണം അപ്രാപ്‌തമാക്കി. +scan_this_image=നിങ്ങളുടെ പ്രാമാണീകരണ ആപ്ലിക്കേഷൻ ഉപയോഗിച്ച് ഈ ചിത്രം സൂക്ഷ്‌മപരിശോധന നടത്തുക: +or_enter_secret=അല്ലെങ്കിൽ രഹസ്യ കോഡ് നൽകുക: %s +then_enter_passcode=അപ്ലിക്കേഷനിൽ കാണിച്ചിരിക്കുന്ന പാസ്‌കോഡ് നൽകുക: +passcode_invalid=പാസ്‌കോഡ് തെറ്റാണ്. വീണ്ടും ശ്രമിക്കുക. +twofa_enrolled=നിങ്ങളുടെ അക്കൌണ്ട് രണ്ട്-ഘട്ട പ്രാമാണീകരണത്തിലേക്ക് ചേർത്തിട്ടുണ്ട്. നിങ്ങളുടെ സ്ക്രാച്ച് ടോക്കൺ (%s) ഒരു തവണ മാത്രം കാണിക്കുന്നതിനാൽ അതു് സുരക്ഷിതമായ സ്ഥലത്ത് സൂക്ഷിക്കുക! + +u2f_desc=ക്രിപ്‌റ്റോഗ്രാഫിക് കീകൾ അടങ്ങിയ ഹാർഡ്‌വെയർ ഉപകരണങ്ങളാണ് സുരക്ഷാ കീകൾ. രണ്ട്-ഘട്ട പ്രാമാണീകരണത്തിനായി അവ ഉപയോഗിക്കാം. പക്ഷേ സുരക്ഷാ കീകൾ FIDO U2F സ്റ്റാൻഡേർഡിനെ പിന്തുണയ്‌ക്കുന്നവയാകണം. +u2f_require_twofa=സുരക്ഷാ കീകൾ‌ ഉപയോഗിക്കുന്നതിന് നിങ്ങളുടെ അക്കൌണ്ട് രണ്ട്-ഘട്ട പ്രാമാണീകരണത്തിൽ‌ ചേർ‌ത്തിരിക്കണം. +u2f_register_key=സുരക്ഷാ കീ ചേർക്കുക +u2f_nickname=വിളിപ്പേരു് +u2f_press_button=രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങളുടെ സുരക്ഷാ കീയിലെ ബട്ടൺ അമർത്തുക. +u2f_delete_key=സുരക്ഷാ കീ നീക്കംചെയ്യുക +u2f_delete_key_desc=നിങ്ങൾ ഒരു സുരക്ഷാ കീ നീക്കംചെയ്യുകയാണെങ്കിൽ, നിങ്ങൾക്ക് ഇത് ഉപയോഗിച്ച് പ്രവേശിയ്ക്കാന്‍ കഴിയില്ല. തുടരട്ടെ? + +manage_account_links=ബന്ധിപ്പിച്ചിട്ടുള്ള അക്കൗണ്ടുകൾ നിയന്ത്രിക്കുക +manage_account_links_desc=ഈ ബാഹ്യ അക്കൗണ്ടുകൾ നിങ്ങളുടെ ഗിറ്റീ അക്കൗണ്ടുമായി ലിങ്കുചെയ്‌തു. +account_links_not_available=നിങ്ങളുടെ ഗിറ്റീ അക്കൌണ്ടുമായി നിലവിൽ മറ്റു് ബാഹ്യ അക്കൌണ്ടുകളൊന്നും ബന്ധിപ്പിച്ചിട്ടില്ല. +remove_account_link=ബന്ധിപ്പിച്ച അക്കൗണ്ട് നീക്കംചെയ്യുക +remove_account_link_desc=ഒരു ബന്ധിപ്പിച്ച അക്കൗണ്ട് നീക്കംചെയ്യുന്നത് നിങ്ങളുടെ ഗിറ്റീ അക്കൗണ്ടിലേക്കുള്ള പ്രവേശനം അസാധുവാക്കും. തുടരട്ടെ? +remove_account_link_success=ബന്ധിപ്പിച്ച അക്കൗണ്ട് നീക്കംചെയ്‌തു. + +orgs_none=നിങ്ങൾ ഏതെങ്കിലും സംഘടനയില്‍ അംഗമല്ല. +repos_none=നിങ്ങൾക്ക് ഒരു കലവറയും സ്വന്തമായി ഇല്ല + +delete_account=അക്കൗണ്ട് ഇല്ലാതാക്കുക +delete_prompt=ഈ പ്രവർത്തനം നിങ്ങളുടെ ഉപയോക്തൃ അക്കൗണ്ട് ശാശ്വതമായി ഇല്ലാതാക്കും. ഇത് പൂർ‌വ്വാവസ്ഥയിലാക്കാൻ‌ കഴിയില്ല.. +confirm_delete_account=ഇല്ലാതാക്കൽ സ്ഥിരീകരിക്കുക +delete_account_title=ഉപയോക്തൃ അക്കൗണ്ട് ഇല്ലാതാക്കുക +delete_account_desc=ഈ ഉപയോക്തൃ അക്കൗണ്ട് ശാശ്വതമായി ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ? + +email_notifications.enable=ഇമെയിൽ അറിയിപ്പുകൾ പ്രാപ്തമാക്കുക +email_notifications.onmention=ഇ-മെയിൽ പരാമര്‍ശിച്ചാൽ മാത്രം അയയ്ക്കുക +email_notifications.disable=ഇമെയിൽ അറിയിപ്പുകൾ അപ്രാപ്തമാക്കുക +email_notifications.submit=ഇ-മെയില്‍ മുൻഗണനകള്‍ [repo] - - - - - - - - - - - - - - - - - - +owner=ഉടമസ്ഥന്‍ +repo_name=കലവറയുടെ പേരു് +repo_name_helper=നല്ല കലവറയുടെ പേരു് ഹ്രസ്വവും അവിസ്മരണീയവും അതുല്യവുമായ കീവേഡുകൾ ഉപയോഗിക്കുന്നു. +visibility=കാണാനാവുന്നതു് +visibility_description=ഉടമയ്‌ക്കോ ഓർഗനൈസേഷൻ അംഗങ്ങൾക്കോ അവകാശങ്ങളുണ്ടെങ്കിൽ മാത്രമേ കാണാൻ കഴിയൂ. +visibility_helper=കലവറ സ്വകാര്യമാക്കുക +visibility_helper_forced=നിങ്ങളുടെ സൈറ്റ് അഡ്മിനിസ്ട്രേറ്റർ പുതിയ കലവറകളെ സ്വകാര്യമാക്കാൻ നിർബന്ധിക്കുന്നു. +visibility_fork_helper=(മാറ്റം എല്ലാ ഫോർക്കുകളെയും ബാധിക്കും.) +clone_helper=ക്ലോണ്‍ ചെയ്യാന്‍ സഹായം വേണോ? സഹായം സന്ദര്‍ശിക്കുക. +fork_repo=കലവറ ഫോര്‍ക്കു് ചെയ്യുക +fork_from=ല്‍ നിന്നും ഫോര്‍ക്കു് ചെയ്യൂ +fork_visibility_helper=ഒരു കലവറയുടെ ഫോര്‍ക്കിന്റെ ദൃശ്യപരത മാറ്റാൻ കഴിയില്ല. +repo_desc=വിരരണം +repo_lang=ഭാഷ +repo_gitignore_helper=.gitignore ടെംപ്ലേറ്റുകൾ തിരഞ്ഞെടുക്കുക. +license=ലൈസൻസ് +license_helper=ഒരു ലൈസൻസ് ഫയൽ തിരഞ്ഞെടുക്കുക. +readme=റീഡ്‍മീ +readme_helper=ഒരു റീഡ്‍മീ ഫയൽ ടെംപ്ലേറ്റ് തിരഞ്ഞെടുക്കുക. +auto_init=കലവറ സമാരംഭിക്കുക (.gitignore, ലൈസൻസ്, റീഡ്‍മീ എന്നിവ ചേർക്കുന്നു) +create_repo=കലവറ സൃഷ്ടിക്കുക +default_branch=സ്ഥിരസ്ഥിതി ശാഖ +mirror_prune=വെട്ടിഒതുക്കുക +mirror_prune_desc=കാലഹരണപ്പെട്ട വിദൂര ട്രാക്കിംഗ് റഫറൻസുകൾ നീക്കംചെയ്യുക +mirror_interval=മിറർ ചെയ്യാനുള്ള ഇടവേള (സാധുവായ സമയ യൂണിറ്റുകൾ 'h', 'm', 's' എന്നിവയാണ്). യാന്ത്രിക സമന്വയം പ്രവർത്തനരഹിതമാക്കാൻ 0 നല്‍കുക. +mirror_interval_invalid=മിറർ ചെയ്യാനുള്ള ഇടവേള സാധുവല്ല. +mirror_address=URL- ൽ നിന്നുള്ള ക്ലോൺ +mirror_address_url_invalid=നൽകിയ url അസാധുവാണ്. നിങ്ങൾ url- ന്റെ എല്ലാ ഘടകങ്ങളും ശരിയായി നല്‍കണം. +mirror_address_protocol_invalid=നൽകിയ url അസാധുവാണ്. http(s):// അല്ലെങ്കിൽ git:// ലൊക്കേഷനുകൾ മാത്രമേ മിറർ ചെയ്യാൻ കഴിയൂ. +mirror_last_synced=അവസാനം സമന്വയിപ്പിച്ചതു് +watchers=നിരീക്ഷകർ +stargazers=സ്റ്റാർഗാസറുകൾ +forks=ശാഖകള്‍ +pick_reaction=നിങ്ങളുടെ പ്രതികരണം തിരഞ്ഞെടുക്കുക +reactions_more=കൂടാതെ %d അധികം + + +archive.title=ഈ കലവറ ചരിത്രരേഖാപരമായി നിലനിര്‍ത്തിയിരിക്കുന്നു. നിങ്ങൾക്ക് ഫയലുകൾ കാണാനും ക്ലോൺ ചെയ്യാനും കഴിയും, പക്ഷേ പ്രശ്‌നങ്ങൾ / ലയന അഭ്യർത്ഥനകൾ ഉണ്ടാക്കാനോ തുറക്കാനോ കഴിയില്ല. +archive.issue.nocomment=ഈ കലവറ ചരിത്രപരമായി നിലനിര്‍ത്തിയിരിക്കുന്നതാണു്. നിങ്ങൾക്ക് പ്രശ്നങ്ങളിൽ അഭിപ്രായമിടാൻ കഴിയില്ല. +archive.pull.nocomment=ഈ കലവറ ചരിത്രപരമായി നിലനിര്‍ത്തിയിരിക്കുന്നതാണു്. നിങ്ങൾക്ക് ലയന അഭ്യർത്ഥനകളില്‍ അഭിപ്രായമിടാൻ കഴിയില്ല. + +form.reach_limit_of_creation=നിങ്ങളുടെ കലവറകളുടെ പരിധിയായ %d നിങ്ങൾ ഇതിനകം എത്തി. +form.name_reserved='%s' എന്ന കലവറയുടെ പേരു് മറ്റാവശ്യങ്ങള്‍ക്കായി നീക്കിവച്ചിരിക്കുന്നു. +form.name_pattern_not_allowed=കലവറനാമത്തിൽ '%s' എന്ന ശ്രേണി അനുവദനീയമല്ല. + +need_auth=ക്ലോൺ അംഗീകാരിയ്ക്കുക +migrate_type=മൈഗ്രേഷൻ തരം +migrate_type_helper=ഈ കലവറ ഒരു മിറർ ആയിരിക്കും +migrate_items=മൈഗ്രേഷൻ ഇനങ്ങൾ +migrate_items_wiki=വിക്കി +migrate_items_milestones=നാഴികക്കല്ലുകള്‍ +migrate_items_labels=ലേബലുകള്‍ +migrate_items_issues=പ്രശ്നങ്ങൾ +migrate_items_pullrequests=ലയന അഭ്യർത്ഥനകൾ +migrate_items_releases=പ്രസിദ്ധീകരണങ്ങള്‍ +migrate_repo=കലവറ മൈഗ്രേറ്റ് ചെയ്യുക +migrate.clone_address=URL- ൽ നിന്ന് മൈഗ്രേറ്റ് / ക്ലോൺ ചെയ്യുക +migrate.clone_address_desc=നിലവിലുള്ള ഒരു കലവറയുടെ HTTP(S) അല്ലെങ്കിൽ ഗിറ്റു് 'ക്ലോൺ' URL +migrate.clone_local_path=അല്ലെങ്കിൽ ഒരു പ്രാദേശിക സെർവർ പാത +migrate.permission_denied=പ്രാദേശിക കലവറകള്‍ ഇറക്കുമതി ചെയ്യാൻ നിങ്ങള്‍ക്കു് അനുവാദമില്ല. +migrate.invalid_local_path=പ്രാദേശിക പാത അസാധുവാണ്. ഇത് നിലവിലില്ല അല്ലെങ്കിൽ ഒരു ഡയറക്ടറിയല്ല. +migrate.failed=മൈഗ്രേഷൻ പരാജയപ്പെട്ടു: %v +migrate.lfs_mirror_unsupported=എൽ‌എഫ്‌എസ് ഒബ്‌ജക്റ്റുകളുടെ മിററിംഗ് പിന്തുണയ്‌ക്കുന്നില്ല - പകരം 'git lfs fetch --all', 'git lfs push --all' എന്നിവ ഉപയോഗിക്കുക. +migrate.migrate_items_options=ഗിറ്റ്ഹബിൽ നിന്ന് മൈഗ്രേറ്റ് ചെയ്യുമ്പോൾ, ഒരു ഉപയോക്തൃനാമവും മൈഗ്രേഷൻ ഓപ്ഷനുകളും നല്‍കാം. +migrated_from=%[2]s നിന്ന് മൈഗ്രേറ്റുചെയ്‌തു +migrated_from_fake=%[1]s നിന്ന് മൈഗ്രേറ്റുചെയ്തു + +mirror_from=ന്റെ കണ്ണാടി +forked_from=ല്‍ നിന്നും വഴിപിരിഞ്ഞതു് +fork_from_self=നിങ്ങളുടെ ഉടമസ്ഥതയിലുള്ള ഒരു ശേഖരം നിങ്ങൾക്ക് ഫോര്‍ക്കു് ചെയ്യാൻ കഴിയില്ല. +fork_guest_user=ഈ ശേഖരം ഫോർക്ക് ചെയ്യുന്നതിന് സൈൻ ഇൻ ചെയ്യുക. +copy_link=പകര്‍ത്തുക +copy_link_success=കണ്ണി പകർത്തി +copy_link_error=പകർത്താൻ ⌘C അല്ലെങ്കിൽ Ctrl-C ഉപയോഗിക്കുക +copied=പകര്‍ത്തല്‍ പൂര്‍ത്തിയായി +unwatch=ശ്രദ്ധിക്കാതിരിയ്ക്കുക +watch=ശ്രദ്ധിയ്ക്കുക +unstar=നക്ഷത്രം നീക്കുക +star=നക്ഷത്രം നല്‍ക്കുക +fork=ഫോര്‍ക്കു് +download_archive=കലവറ ഡൗൺലോഡുചെയ്യുക + +no_desc=വിവരണം ലഭ്യമല്ല +quick_guide=ദ്രുത മാര്‍ഗദര്‍ശനം +clone_this_repo=ഈ കലവറ ക്ലോൺ ചെയ്യുക +create_new_repo_command=കമാൻഡ് ലൈന്‍ വഴി ഒരു പുതിയ കലവറ സൃഷ്ടിക്കുക +push_exist_repo=കമാൻഡ് ലൈനിൽ നിന്ന് നിലവിലുള്ള ഒരു കലവറ തള്ളിക്കയറ്റുക +empty_message=ഈ കലവറയില്‍ ഉള്ളടക്കമൊന്നും അടങ്ങിയിട്ടില്ല. + +code=കോഡ് +code.desc=ഉറവിട കോഡ്, ഫയലുകൾ, കമ്മിറ്റുകളും ശാഖകളും പ്രവേശിയ്ക്കുക. +branch=ശാഖ +tree=മരം +filter_branch_and_tag=ശാഖ അല്ലെങ്കിൽ ടാഗ് അരിച്ചെടുക്കുക +branches=ശാഖകള്‍ +tags=ടാഗുകള്‍ +issues=പ്രശ്നങ്ങൾ +pulls=ലയന അഭ്യർത്ഥനകൾ +labels=ലേബലുകള്‍ +milestones=നാഴികക്കല്ലുകള്‍ +commits=കമ്മിറ്റുകള്‍ +commit=കമ്മിറ്റ് +releases=പ്രസിദ്ധപ്പെടുത്തുക +file_raw=കലര്‍പ്പില്ലാത്തതു് +file_history=നാള്‍വഴി +file_view_raw=കലര്‍പ്പില്ലാതെ കാണുക +file_permalink=സ്ഥിരമായ കണ്ണി +file_too_large=ഈ ഫയൽ കാണിക്കാൻ കഴിയാത്തത്ര വലുതാണ്. +video_not_supported_in_browser=നിങ്ങളുടെ ബ്രൌസർ HTML5 'വീഡിയോ' ടാഗിനെ പിന്തുണയ്ക്കുന്നില്ല. +audio_not_supported_in_browser=നിങ്ങളുടെ ബ്ര browser സർ HTML5 'ഓഡിയോ' ടാഗിനെ പിന്തുണയ്ക്കുന്നില്ല. +stored_lfs=ഗിറ്റു് LFS ഉപയോഗിച്ച് സംഭരിച്ചു +commit_graph=കമ്മിറ്റ് ഗ്രാഫ് +blame=ചുമതല +normal_view=സാധാരണ കാഴ്ച + +editor.new_file=പുതിയ ഫയൽ +editor.upload_file=ഫയൽ അപ്‌ലോഡ് +editor.edit_file=ഫയൽ തിരുത്തുക +editor.preview_changes=മാറ്റങ്ങൾ കാണുക +editor.cannot_edit_lfs_files=വെബ് ഇന്റർഫേസിൽ LFS ഫയലുകൾ എഡിറ്റുചെയ്യാൻ കഴിയില്ല. +editor.cannot_edit_non_text_files=വെബ് ഇന്റർഫേസിൽ ബൈനറി ഫയലുകൾ എഡിറ്റുചെയ്യാൻ കഴിയില്ല. +editor.edit_this_file=ഫയൽ തിരുത്തുക +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=ഒരു ഡയറക്‌ടറിയുടെ പേര് ടൈപ്പുചെയ്‌ത് സ്ലാഷും ('/') ചേർത്ത് ചേർക്കുക. ഇൻപുട്ട് ഫീൽഡിന്റെ തുടക്കത്തിൽ ബാക്ക്‌സ്‌പെയ്‌സ് ടൈപ്പുചെയ്‌ത് ഒരു ഡയറക്‌ടറി നീക്കംചെയ്യുക. +editor.or=അഥവാ +editor.cancel_lower=റദ്ദാക്കുക +editor.commit_changes=മാറ്റങ്ങൾ വരുത്തുക +editor.add_tmpl='<ഫയല്‍>' ചേർക്കുക +editor.add=%s ചേര്‍ക്കുക +editor.update=%s പുതുക്കുക +editor.delete=%s നീക്കം ചെയ്യുക +editor.propose_file_change=ഫയലിനു് മാറ്റങ്ങള്‍ നിർദ്ദേശിക്കുക +editor.new_branch_name_desc=പുതിയ ശാഖയുടെ പേരു്… +editor.cancel=റദ്ദാക്കുക +editor.filename_cannot_be_empty=ഫയലിന്റെ പേരു് ശൂന്യമായിരിക്കരുത്. +editor.add_subdir=ഒരു ഡയറക്ടറി ചേർക്കുക… +editor.upload_files_to_dir=ഫയലുകൾ %s ലേക്ക് അപ്‌ലോഡുചെയ്യുക + + + +issues.new.clear_labels=ലേബലുകൾ മായ്‌ക്കുക +issues.new.milestone=നാഴികക്കല്ല് +issues.new.no_milestone=നാഴികക്കല്ല് ഇല്ല +issues.new.clear_milestone=നാഴികക്കല്ല് എടുത്തു മാറ്റുക +issues.new.open_milestone=നാഴികക്കല്ലുകൾ തുറക്കുക +issues.new.closed_milestone=അടച്ച നാഴികക്കല്ലുകൾ +issues.new.assignees=നിശ്ചയിക്കുന്നവര്‍ +issues.new.clear_assignees=നിശ്ചയിക്കുന്നവരെ നീക്കം ചെയ്യുക +issues.new.no_assignees=നിശ്ചയിക്കുന്നവര്‍ ഇല്ല +issues.no_ref=ശാഖാ അഥവാ ടാഗ് വ്യക്തമാക്കിയിട്ടില്ല +issues.create=പ്രശ്നം സൃഷ്ടിക്കുക +issues.new_label=പുതിയ അടയാളം +issues.new_label_placeholder=അടയാള നാമം +issues.new_label_desc_placeholder=വിരരണം +issues.create_label=അടയാളം സൃഷ്ടിക്കുക +issues.label_templates.title=മുൻ‌നിശ്ചയിച്ച ഒരു കൂട്ടം ലേബലുകൾ‌ നിറയ്‌ക്കുക +issues.label_templates.info=ലേബലുകളൊന്നും ഇതുവരെ നിലവിലില്ല. 'പുതിയ ലേബൽ' ഉപയോഗിച്ച് ഒരു ലേബൽ സൃഷ്ടിക്കുക അല്ലെങ്കിൽ മുൻ‌നിശ്ചയിച്ച ലേബൽ സെറ്റ് ഉപയോഗിക്കുക: +issues.label_templates.helper=ഒരു ലേബൽ സെറ്റ് തിരഞ്ഞെടുക്കുക +issues.label_templates.use=ലേബൽ സെറ്റ് ഉപയോഗിക്കുക +issues.deleted_milestone=`(ഇല്ലാതാക്കി)` +issues.filter_type.all_issues=എല്ലാ ഇഷ്യൂകളും +issues.label_open_issues=%d തുറന്നനിലയിലുള്ള ഇഷ്യൂകള്‍ +issues.label_deletion_desc=ഒരു ലേബൽ ഇല്ലാതാക്കിയാല്‍, അതു് നിയുകതമാക്കിയ എല്ലാ ഇഷ്യൂകളില്‍ നിന്നും നീക്കംചെയ്യും. തുടരട്ടെ? +issues.dependency.issue_closing_blockedby=ഈ ലയന അഭ്യര്‍ത്ഥന അടയ്‌ക്കുന്നത് ഇനിപ്പറയുന്ന ഇഷ്യൂകള്‍ തടയുന്നു് +issues.dependency.pr_closing_blockedby=ഈ ഇഷ്യു അടയ്‌ക്കുന്നത് ഇനിപ്പറയുന്ന ലയന അഭ്യര്‍ത്ഥന തടയുന്നു് +issues.dependency.issue_close_blocks=ഈ ഇഷ്യു അടയ്‌ക്കുന്നത് ഇനിപ്പറയുന്ന ഇഷ്യൂകള്‍ തടയുന്നു് +issues.dependency.pr_close_blocks=ഈ ഇഷ്യൂകള്‍ അടയ്‌ക്കുന്നത് ഈ ലയന അഭ്യര്‍ത്ഥന തടയുന്നു് +issues.dependency.issue_close_blocked=ഈ ഇഷ്യൂ അടയ്‌ക്കുന്നതിന് മുമ്പ് ഇതിനെ തടയുന്ന എല്ലാ ഇഷ്യൂകളും നിങ്ങൾ അടയ്‌ക്കേണ്ടതുണ്ട്. +issues.dependency.pr_close_blocked=ഈ ലയന അഭ്യര്‍ത്ഥന സ്ഥിരീകരിയ്ക്കുന്നതിനു മുമ്പ് ഇതിനെ തടയുന്ന എല്ലാ ഇഷ്യൂകളും നിങ്ങൾ അടയ്‌ക്കേണ്ടതുണ്ട്. +issues.dependency.setting=ലയന അഭ്യര്‍ത്ഥനകള്‍ക്കും ഇഷ്യൂകള്‍ക്കുമായി ആശ്രിതത്വം സജ്ജമാക്കുക +issues.dependency.add_error_cannot_create_circular=രണ്ട് ഇഷ്യൂകളും പരസ്പരം തടയുന്നതാകുന്നതിലൂടെ നിങ്ങൾക്ക് ഒരു ആശ്രയത്വം സൃഷ്ടിക്കാൻ കഴിയില്ല. +issues.dependency.add_error_dep_not_same_repo=രണ്ട് പ്രശ്നങ്ങളും ഒരേ കലവറയിലേതു് ആയിരിക്കണം. + + +milestones.filter_sort.most_issues=മിക്ക ഇഷ്യൂകളും +milestones.filter_sort.least_issues=കുറഞ്ഞ ഇഷ്യൂകളെങ്കിലും + + + +activity.active_issues_count_n=%d സജ്ജീവ ഇഷ്യൂകള്‍ +activity.closed_issues_count_n=അടച്ച ഇഷ്യൂകള്‍ +activity.title.issues_n=%d ഇഷ്യൂകള്‍ +activity.new_issues_count_n=പുതിയ ഇഷ്യൂകള്‍ + + +settings.event_issues=ഇഷ്യൂകള്‍ @@ -88,6 +771,9 @@ +repos.issues=ഇഷ്യൂകള്‍ + + diff --git a/options/locale/locale_nb-NO.ini b/options/locale/locale_nb-NO.ini index a3b2e4acff03..0b2eb16208d6 100644 --- a/options/locale/locale_nb-NO.ini +++ b/options/locale/locale_nb-NO.ini @@ -206,6 +206,8 @@ forgot_password=Glemt passord? + + diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index dca7ec56501b..a275e2c45246 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1385,6 +1385,8 @@ monitor.desc=Omschrijving monitor.start=Starttijd monitor.execute_time=Uitvoertijd + + notices.system_notice_list=Systeem aankondigingen notices.actions=Acties notices.select_all=Alles selecteren diff --git a/options/locale/locale_nn-NO.ini b/options/locale/locale_nn-NO.ini index 6706b9ca2b07..d1eaa8e979aa 100644 --- a/options/locale/locale_nn-NO.ini +++ b/options/locale/locale_nn-NO.ini @@ -103,6 +103,8 @@ + + diff --git a/options/locale/locale_no-NO.ini b/options/locale/locale_no-NO.ini index 2142625cd4e9..9bf581353de9 100644 --- a/options/locale/locale_no-NO.ini +++ b/options/locale/locale_no-NO.ini @@ -141,6 +141,8 @@ smtp_host=SMTP-vert + + diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index d2887c58d0bb..754013212a15 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -80,6 +80,7 @@ loading=Ładowanie… [startpage] app_desc=Bezbolesna usługa Git na własnym serwerze install=Łatwa instalacja +install_desc=Po prostu odpal plik binarny dla swojej platformy. Albo uruchom Gitea przy pomocy Dockera lub Vagrant, albo zainstaluj z paczki. platform=Wieloplatformowość platform_desc=Gitea ruszy gdziekolwiek Go jest możliwe do skompilowania: Windows, macOS, Linux, ARM, itd. Wybierz swój ulubiony system! lightweight=Niskie wymagania @@ -268,6 +269,7 @@ authorize_application_description=Jeżeli udzielisz dostępu, aplikacja uzyska d authorize_title=Zezwolić "%s" na dostęp do Twojego konta? authorization_failed=Autoryzacja nie powiodła się authorization_failed_desc=Autoryzacja nie powiodła się ze względu na niewłaściwe żądanie. Skontaktuj się z osobami utrzymującymi aplikację, którą próbowano autoryzować. +sspi_auth_failed=Uwierzytelnianie SSPI nie powiodło się [mail] activate_account=Aktywuj swoje konto @@ -301,6 +303,8 @@ CommitChoice=Wybór commita TreeName=Ścieżka pliku Content=Treść +SSPISeparatorReplacement=Separator +SSPIDefaultLanguage=Domyślny język require_error=` nie może być puste.` alpha_dash_error=` powinno zawierać tylko znaki alfanumeryczne, myślniki ("-") i znaki podkreślenia ("_").` @@ -312,9 +316,11 @@ max_size_error=` musi zawierać co najwyżej %s znaków.` email_error=` nie jest poprawnym adresem e-mail.` url_error=` nie jest poprawnym adresem URL.` include_error=`musi zawierać tekst '%s'.` +glob_pattern_error=` wzorzec glob jest nieprawidłowy: %s.` unknown_error=Nieznany błąd: captcha_incorrect=Kod CAPTCHA jest nieprawidłowy. password_not_match=Hasła nie są identyczne. +lang_select_error=Wybierz język z listy. username_been_taken=Ta nazwa użytkownika jest już zajęta. repo_name_been_taken=Nazwa repozytorium jest już zajęta. @@ -326,6 +332,11 @@ team_no_units_error=Zezwól na dostęp do co najmniej jednej sekcji repozytorium email_been_used=Ten adres e-mail jest już używany. openid_been_used=Ten adres OpenID "%s" jest już używany. username_password_incorrect=Nazwa użytkownika lub hasło jest nieprawidłowe. +password_complexity=Hasło nie spełnia wymogów złożoności: +password_lowercase_one=Co najmniej jedna mała litera +password_uppercase_one=Co najmniej jedna duża litera +password_digit_one=Co najmniej jedna cyfra +password_special_one=Co najmniej jeden znak specjalny (interpunkcja, nawiasy, cudzysłowy, itp.) enterred_invalid_repo_name=Wprowadzona nazwa repozytorium jest niepoprawna. enterred_invalid_owner_name=Nowa nazwa właściciela nie jest prawidłowa. enterred_invalid_password=Wprowadzone hasło jest nieprawidłowe. @@ -579,6 +590,11 @@ email_notifications.submit=Ustaw preferencje wiadomości e-mail owner=Właściciel repo_name=Nazwa repozytorium repo_name_helper=Dobra nazwa repozytorium jest utworzona z krótkich, łatwych do zapamiętania i unikalnych słów kluczowych. +repo_size=Rozmiar repozytorium +template=Szablon +template_select=Wybierz szablon. +template_helper=Ustaw repozytorium jako szablon +template_description=Szablony repozytoriów pozwalają użytkownikom generować nowe repozytoria o takiej samej strukturze katalogów, plików i opcjonalnych ustawieniach. visibility=Widoczność visibility_description=Tylko właściciel lub członkowie organizacji, jeśli mają odpowiednie uprawnienia, będą mogli to zobaczyć. visibility_helper=Przekształć repozytorium na prywatne @@ -588,9 +604,14 @@ clone_helper=Potrzebujesz pomocy z klonowaniem? Odwiedź %[2]s migrated_from_fake=Zmigrowane z %[1]s +migrate.migrating=Migrowanie z %s... +migrate.migrating_failed=Migrowanie z %s nie powiodło się. mirror_from=kopia lustrzana forked_from=sforkowany z +generated_from=wygenerowane z fork_from_self=Nie możesz sforkować swojego własnego repozytorium. fork_guest_user=Zaloguj się, aby sforkować to repozytorium. copy_link=Kopiuj @@ -691,6 +725,8 @@ stored_lfs=Przechowane za pomocą Git LFS commit_graph=Wykres commitów blame=Wina normal_view=Zwykły widok +line=wiersz +lines=wiersze editor.new_file=Nowy plik editor.upload_file=Wyślij plik @@ -699,6 +735,7 @@ editor.preview_changes=Podgląd zmian editor.cannot_edit_lfs_files=Pliki LFS nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.cannot_edit_non_text_files=Pliki binarne nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.edit_this_file=Edytuj plik +editor.this_file_locked=Plik jest zablokowany editor.must_be_on_a_branch=Musisz znajdować się na gałęzi, aby nanieść lub zaproponować zmiany tego pliku. editor.fork_before_edit=Musisz sforkować to repozytorium, aby nanieść lub zaproponować zmiany tego pliku. editor.delete_this_file=Usuń plik @@ -716,6 +753,7 @@ editor.delete=Usuń '%s' editor.commit_message_desc=Dodaj dodatkowy rozszerzony opis… editor.commit_directly_to_this_branch=Zmieniaj bezpośrednio gałąź %s. editor.create_new_branch=Stwórz nową gałąź dla tego commita i rozpocznij Pull Request. +editor.create_new_branch_np=Stwórz nową gałąź dla tego commita. editor.propose_file_change=Zaproponuj zmiany w pliku editor.new_branch_name_desc=Nazwa nowej gałęzi… editor.cancel=Anuluj @@ -730,10 +768,13 @@ editor.file_editing_no_longer_exists=Edytowany plik '%s' już nie istnieje w tym editor.file_deleting_no_longer_exists=Usuwany plik '%s' już nie istnieje w tym repozytorium. editor.file_changed_while_editing=Zawartość pliku zmieniła się, odkąd rozpoczęto jego edycję. Kliknij tutaj, aby zobaczyć zmiany, lub ponownie Zatwierdź zmiany, aby je nadpisać. editor.file_already_exists=Plik o nazwie '%s' już istnieje w tym repozytorium. +editor.commit_empty_file_header=Commituj pusty plik +editor.commit_empty_file_text=Plik, który zamierzasz commitować, jest pusty. Kontynuować? editor.no_changes_to_show=Brak zmian do pokazania. editor.fail_to_update_file=Tworzenie/aktualizacja pliku '%s' nie powiodła się z błędem: %v editor.add_subdir=Dodaj katalog… editor.unable_to_upload_files=Wysyłanie plików do '%s' nie powiodło się z błędem: %v +editor.upload_file_is_locked=Plik '%s' jest zablokowany przez %s. editor.upload_files_to_dir=Prześlij pliki do '%s' editor.cannot_commit_to_protected_branch=Nie można commitować do chronionej gałęzi '%s'. @@ -786,12 +827,16 @@ issues.add_milestone_at=`dodaje to do kamienia milowego %s %s` issues.change_milestone_at=`zmienia kamień milowy z %s na %s %s` issues.remove_milestone_at=`usuwa to z kamienia milowego %s %s` issues.deleted_milestone=`(usunięto)` +issues.self_assign_at=`przypisuje to na siebie %s` +issues.add_assignee_at=`zostaje przypisany(-a) przez %s %s` issues.remove_assignee_at=`usunięto przypisanie przez %s %s` +issues.remove_self_assignment=`usuwa swoje przypisanie %s` issues.change_title_at=`zmieniono tytuł z %s na %s %s` issues.delete_branch_at=`usuwa gałąź %s %s` issues.open_tab=Otwarte %d issues.close_tab=Zamknięte %d issues.filter_label=Etykieta +issues.filter_label_exclude=`Użyj Alt + Kliknij/Enter, aby wykluczyć etykiety` issues.filter_label_no_select=Wszystkie etykiety issues.filter_milestone=Kamień milowy issues.filter_milestone_no_select=Wszystkie kamienie milowe @@ -835,6 +880,10 @@ issues.closed_title=Zamknięty issues.num_comments=%d komentarzy issues.commented_at=`skomentował(-a) %s` issues.delete_comment_confirm=Czy na pewno chcesz usunąć ten komentarz? +issues.context.copy_link=Skopiuj link +issues.context.quote_reply=Cytuj odpowiedź +issues.context.edit=Edytuj +issues.context.delete=Usuń issues.no_content=Nie ma jeszcze treści. issues.close_issue=Zamknij issues.close_comment_issue=Skomentuj i zamknij @@ -844,6 +893,13 @@ issues.create_comment=Skomentuj issues.closed_at=`zamknął(-ęła) %[2]s` issues.reopened_at=`otworzył(-a) ponownie %[2]s` issues.commit_ref_at=`wspomniał(-a) to zgłoszenie z commita %[2]s` +issues.ref_issue_from=`odwołał(-a) się do tego zgłoszenia %[4]s %[2]s` +issues.ref_pull_from=`odwołał(-a) się do tego Pull Requesta %[4]s %[2]s` +issues.ref_closing_from=`odwołał(-a) się do Pull Requesta %[4]s, który zamknie to zgłoszenie %[2]s` +issues.ref_reopening_from=`odwołał(-a) się do Pull Requesta %[4]s, który otworzy na nowo to zgłoszenie %[2]s` +issues.ref_closed_from=`zamknął(-ęła) to zgłoszenie %[4]s %[2]s` +issues.ref_reopened_from=`ponownie otworzył(-a) to zgłoszenie %[4]s %[2]s` +issues.ref_from=`z %[1]s` issues.poster=Autor issues.collaborator=Współpracownik issues.owner=Właściciel @@ -956,10 +1012,13 @@ issues.review.self.rejection=Nie możesz zażądać zmian w swoim własnym Pull issues.review.approve=zatwierdza te zmiany %s issues.review.comment=zrecenzowano %s issues.review.content.empty=Musisz pozostawić komentarz o pożądanej zmianie/zmianach. +issues.review.reject=zażądał(-a) zmian %s +issues.review.pending=Oczekująca issues.review.review=Recenzuj issues.review.reviewers=Recenzenci issues.review.show_outdated=Pokaż przedawnione issues.review.hide_outdated=Ukryj przedawnione +issues.assignee.error=Nie udało się dodać wszystkich wybranych osób do przypisanych przez nieoczekiwany błąd. pulls.desc=Włącz Pull Requesty i recenzjonowanie kodu. pulls.new=Nowy Pull Request @@ -972,6 +1031,7 @@ pulls.no_results=Nie znaleziono wyników. pulls.nothing_to_compare=Te gałęzie są sobie równe. Nie ma potrzeby tworzyć Pull Requesta. pulls.has_pull_request=`Pull Request pomiędzy tymi gałęziami już istnieje %[2]s#%[3]d` pulls.create=Utwórz Pull Request +pulls.title_desc=chce scalić %[1]d commity/ów z %[2]s do %[3]s pulls.merged_title_desc=scala %[1]d commity/ów z %[2]s do %[3]s %[4]s pulls.tab_conversation=Dyskusja pulls.tab_commits=Commity @@ -986,6 +1046,8 @@ pulls.cannot_merge_work_in_progress=Ten Pull Request został oznaczony jako prac pulls.data_broken=Ten Pull Request jest uszkodzony ze względu na brakujące informacje o forku. pulls.files_conflicted=Ten Pull Request zawiera zmiany konfliktujące z docelową gałęzią. pulls.is_checking=Sprawdzanie konfliktów ze scalaniem w toku. Spróbuj ponownie za chwilę. +pulls.required_status_check_failed=Niektóre kontrole stanów nie były pomyślne. +pulls.required_status_check_administrator=Jako administrator, możesz wciąż scalić ten Pull Request. pulls.blocked_by_approvals=Ten Pull Request nie ma jeszcze wymaganej ilości zatwierdzeń. Otrzymał %d z %d wymaganych zatwierdzeń. pulls.can_auto_merge_desc=Ten Pull Request może być automatycznie scalony. pulls.cannot_auto_merge_desc=Ten Pull Request nie może być automatycznie scalony z powodu konfliktów. @@ -993,11 +1055,16 @@ pulls.cannot_auto_merge_helper=Scal ręcznie, aby rozwiązać konflikty. pulls.no_merge_desc=Ten Pull Request nie może zostać scalony, ponieważ wszystkie opcje scalania dla tego repozytorium są wyłączone. pulls.no_merge_helper=Włącz opcje scalania w ustawieniach repozytorium, lub scal ten Pull Request ręcznie. pulls.no_merge_wip=Ten pull request nie może być automatycznie scalony, ponieważ jest oznaczony jako praca w toku. +pulls.no_merge_status_check=Ten Pull Request nie może być scalony, bo nie wszystkie kontrole stanów były pomyślne. pulls.merge_pull_request=Scal Pull Request pulls.rebase_merge_pull_request=Zmień bazę i scal pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff) pulls.squash_merge_pull_request=Zmiażdż i scal pulls.invalid_merge_option=Nie możesz użyć tej opcji scalania dla tego pull request'a. +pulls.merge_conflict=Scalenie nie powiodło się: Wystąpił konflikt przy scalaniu %[1]s
%[2]s
Porada: Wypróbuj innej strategii scalania +pulls.rebase_conflict=Scalenie nie powiodło się: Wystąpił konflikt przy zmianie bazy commita: %[1]s
%[2]s
%[3]s
Porada: Wypróbuj innej strategii scalania +pulls.unrelated_histories=Scalenie nie powiodło się: Head scalenia i baza nie mają wspólnej historii. Porada: Spróbuj innej strategii scalania +pulls.merge_out_of_date=Scalenie nie powiodło się: Przy generowaniu scalenia, baza została zaktualizowana. Porada: Spróbuj ponownie. pulls.open_unmerged_pull_exists=`Nie możesz wykonać operacji ponownego otwarcia, ponieważ jest już oczekujący pull request (#%d) z identycznymi właściwościami.` pulls.status_checking=Niektóre etapy są w toku pulls.status_checks_success=Wszystkie etapy powiodły się @@ -1066,6 +1133,9 @@ activity.period.daily=1 dzień activity.period.halfweekly=3 dni activity.period.weekly=1 tydzień activity.period.monthly=1 miesiąc +activity.period.quarterly=3 miesiące +activity.period.semiyearly=6 miesięcy +activity.period.yearly=1 rok activity.overview=Przegląd activity.active_prs_count_1=%d aktywny Pull Request activity.active_prs_count_n=%d aktywne Pull Requesty @@ -1218,6 +1288,9 @@ settings.search_user_placeholder=Szukaj użytkownika… settings.org_not_allowed_to_be_collaborator=Organizacji nie można dodać jako współpracownika. settings.change_team_access_not_allowed=Zmiana dostępu zespołu do repozytorium zostało zastrzeżone do właściciela organizacji settings.team_not_in_organization=Zespół nie jest w tej samej organizacji co repozytorium +settings.add_team_duplicate=Zespół już posiada repozytorium +settings.add_team_success=Zespół ma teraz dostęp do repozytorium. +settings.remove_team_success=Dostęp zespołu do repozytorium został usunięty. settings.add_webhook=Dodaj webhooka settings.add_webhook.invalid_channel_name=Nazwa kanału Webhooka nie może być pusta i nie może zawierać jedynie znaku #. settings.hooks_desc=Webhooki automatycznie tworzą zapytania HTTP POST do serwera, kiedy następują pewne zdarzenia w Gitea. Przeczytaj o tym więcej w przewodniku o Webhookach. @@ -1267,6 +1340,8 @@ settings.event_pull_request=Pull Request settings.event_pull_request_desc=Pull Request otwarty, zamknięty, ponownie otwarty, zaakceptowany, odrzucony, komentarz oceniający, przypisany, nieprzypisany, etykieta zaktualizowana, etykieta wyczyszczona lub zsynchronizowany. settings.event_push=Wypchnięcie settings.event_push_desc=Wypchnięcie git do repozytorium. +settings.branch_filter=Filtr gałęzi +settings.branch_filter_desc=Biała lista gałęzi dla przepychania, tworzenia i usuwania gałęzi, określona jako wzorzec glob. Jeśli pusta, lub *, zdarzenia dla wszystkich gałęzi są wyświetlane. Sprawdź dokumentację github.com/gobwas/glob dla składni. Przykładowo: master, {master,release*}. settings.event_repository=Repozytorium settings.event_repository_desc=Repozytorium stworzone lub usunięte. settings.active=Aktywne @@ -1287,11 +1362,18 @@ settings.add_telegram_hook_desc=Zintegruj Telegrama ze swoim re settings.add_msteams_hook_desc=Zintegruj Microsoft Teams ze swoim repozytorium. settings.deploy_keys=Klucze wdrożeniowe settings.add_deploy_key=Dodaj klucz wdrożeniowy +settings.deploy_key_desc=Klucze wdrożeniowe mają wyłącznie dostęp "tylko do odczytu" do pobierania danych z repozytorium. settings.is_writable=Włącz dostęp do zapisu +settings.is_writable_info=Zezwól temu kluczowi wdrożeniowemu na przepychanie zmian do tego repozytorium. +settings.no_deploy_keys=W tej chwili nie ma kluczy wdrożeniowych. settings.title=Tytuł settings.deploy_key_content=Treść +settings.key_been_used=Klucz wdrożeniowy z identyczną zawartością jest już w użyciu. +settings.key_name_used=Klucz wdrożeniowy z identyczną nazwą już istnieje. +settings.add_key_success=Klucz wdrożeniowy '%s' został dodany. settings.deploy_key_deletion=Usuń klucz wdrożeniowy settings.deploy_key_deletion_desc=Usunięcie klucza wdrożeniowego wycofa jego dostęp do tego repozytorium. Kontynuować? +settings.deploy_key_deletion_success=Klucz wdrożeniowy został usunięty. settings.branches=Gałęzie settings.protected_branch=Ochrona gałęzi settings.protected_branch_can_push=Umożliwić push? @@ -1299,11 +1381,29 @@ settings.protected_branch_can_push_yes=Możesz wysyłać settings.protected_branch_can_push_no=Nie możesz wysyłać settings.branch_protection=Ochrona gałęzi dla "%s settings.protect_this_branch=Włącz ochronę gałęzi +settings.protect_this_branch_desc=Zapobiega usunięciu oraz ogranicza wypychanie i scalanie zmian do tej gałęzi. +settings.protect_disable_push=Wyłącz wypychanie +settings.protect_disable_push_desc=Wypychanie do tej gałęzi nie będzie możliwe. +settings.protect_enable_push=Włącz wypychanie +settings.protect_enable_push_desc=Każdy użytkownik z uprawnieniem zapisu będzie miał możliwość wypychania do tej gałęzi (oprócz wymuszonego wypchnięcia). +settings.protect_whitelist_committers=Wypychanie ograniczone białą listą +settings.protect_whitelist_committers_desc=Tylko dopuszczeni użytkownicy oraz zespoły będą miały możliwość wypychania zmian do tej gałęzi (oprócz wymuszenia wypchnięcia). +settings.protect_whitelist_deploy_keys=Biała lista kluczy wdrożeniowych z uprawnieniem zapisu do push'a settings.protect_whitelist_users=Użytkownicy dopuszczeni do wypychania: settings.protect_whitelist_search_users=Szukaj użytkowników… settings.protect_whitelist_teams=Zespoły dopuszczone do wypychania: settings.protect_whitelist_search_teams=Szukaj zespołów… +settings.protect_merge_whitelist_committers=Włącz dopuszczenie scalania +settings.protect_merge_whitelist_committers_desc=Zezwól jedynie dopuszczonym użytkownikom lub zespołom na scalanie Pull Requestów w tej gałęzi. +settings.protect_merge_whitelist_users=Użytkownicy dopuszczeni do scalania: +settings.protect_merge_whitelist_teams=Zespoły dopuszczone do scalania: +settings.protect_check_status_contexts=Włącz kontrolę stanu +settings.protect_check_status_contexts_desc=Wymagaj powodzenia kontroli stanów przed scalaniem Wybierz które kontrole stanów muszą zostać ukończone pomyślnie, zanim gałęzie będą mogły zostać scalone z gałęzią, która pokrywa się z tą zasadą. Kiedy włączone, commity muszą być najpierw przepchnięte do innej gałęzi, a następnie scalone lub przepchnięte bezpośrednio do gałęzi, która pokrywa się z tą zasadą po pomyślnej kontroli stanów. Jeżeli nie zostaną wybrane konteksty, ostatni commit musi zakończyć się powodzeniem niezależnie od kontekstu. +settings.protect_check_status_contexts_list=Kontrole stanów w poprzednim tygodniu dla tego repozytorium settings.protect_required_approvals=Wymagane zatwierdzenia: +settings.protect_required_approvals_desc=Zezwól na scalanie Pull Requestów tylko z wystarczającą ilością pozytywnych recenzji. +settings.protect_approvals_whitelist_enabled=Ogranicz zatwierdzenia do dopuszczonych użytkowników i zespołów +settings.protect_approvals_whitelist_enabled_desc=Tylko recenzje pochodzące od użytkowników lub zespołów na białej liście będą liczyły się do wymaganych zatwierdzeń. Bez białej listy zatwierdzeń, recenzja od każdego użytkownika z uprawnieniem zapisu będzie liczyła się do wymaganych zatwierdzeń. settings.protect_approvals_whitelist_users=Dopuszczeni recenzenci: settings.protect_approvals_whitelist_teams=Dopuszczone zespoły do recenzji: settings.add_protected_branch=Włącz ochronę @@ -1312,6 +1412,7 @@ settings.update_protect_branch_success=Ochrona gałęzi dla gałęzi "%s" zosta settings.remove_protected_branch_success=Ochrona gałęzi dla gałęzi "%s" została wyłączona. settings.protected_branch_deletion=Wyłącz ochronę gałęzi settings.protected_branch_deletion_desc=Wyłączenie ochrony gałęzi pozwoli użytkownikom z uprawnieniami zapisu do przekazywania zmian do gałęzi. Kontynuować? +settings.default_branch_desc=Wybierz domyślną gałąź repozytorium dla Pull Requestów i commitów kodu: settings.choose_branch=Wybierz gałąź… settings.no_protected_branch=Nie ma chronionych gałęzi. settings.edit_protected_branch=Zmień @@ -1322,12 +1423,40 @@ settings.archive.button=Zarchiwizuj repozytorium settings.archive.header=Zarchiwizuj to repozytorium settings.archive.text=Zarchiwizowanie repozytorium sprawi, że będzie ono "tylko do odczytu". Zostanie ukryte z pulpitu, nie będzie możliwe commitowanie, otwieranie zgłoszeń, czy tworzenie Pull Requestów. settings.archive.success=Repozytorium zostało pomyślnie zarchiwizowane. +settings.archive.error=Wystąpił błąd przy próbie zarchiwizowania tego repozytorium. Sprawdź dziennik po więcej szczegółów. +settings.archive.error_ismirror=Nie możesz archiwizować kopii lustrzanej repozytorium. +settings.archive.branchsettings_unavailable=Ustawienia gałęzi nie są dostępne, kiedy repozytorium jest zarchiwizowane. +settings.unarchive.button=Przywróć repozytorium +settings.unarchive.header=Przywróć to repozytorium z archiwum +settings.unarchive.text=Przywrócenie repozytorium z archiwum ponownie umożliwi przyjmowanie commitów i przepchnięć, jak i nowych zgłoszeń oraz Pull Requestów. +settings.unarchive.success=Repozytorium zostało pomyślnie przywrócone z archiwum. +settings.unarchive.error=Wystąpił błąd przy próbie przywrócenia tego repozytorium z archiwum. Sprawdź dziennik po więcej szczegółów. +settings.update_avatar_success=Awatar repozytorium został zaktualizowany. +settings.lfs=LFS +settings.lfs_filelist=Pliki LFS przechowywane w tym repozytorium +settings.lfs_no_lfs_files=Brak plików LFS przechowywanych w tym repozytorium +settings.lfs_findcommits=Znajdź commity +settings.lfs_lfs_file_no_commits=Nie znaleziono commitów dla tego pliku LFS +settings.lfs_delete=Usuń plik LFS z OID %s +settings.lfs_delete_warning=Usunięcie pliku LFS może spowodować błędy typu 'obiekt nie istnieje' przy checkout'cie. Czy chcesz kontynuować? +settings.lfs_findpointerfiles=Znajdź pliki wskaźnika +settings.lfs_pointers.found=Znaleziono %d wskaźników blob - %d powiązanych, %d niepowiązanych (%d brakujących w magazynie danych) +settings.lfs_pointers.sha=SHA bloba +settings.lfs_pointers.oid=OID +settings.lfs_pointers.inRepo=W repozytorium +settings.lfs_pointers.exists=Istnieje w magazynie +settings.lfs_pointers.accessible=Dostępne dla użytkownika +settings.lfs_pointers.associateAccessible=Powiąż dostępne %d OID diff.browse_source=Przeglądaj źródła diff.parent=rodzic diff.commit=commit diff.git-notes=Notatki diff.data_not_available=Informacje nt. zmian nie są dostępne +diff.options_button=Opcje porównania +diff.show_diff_stats=Pokaż statystyki +diff.download_patch=Ściągnij plik aktualizacji +diff.download_diff=Ściągnij plik porównania diff.show_split_view=Widok podzielony diff.show_unified_view=Zunifikowany widok diff.whitespace_button=Znaki białe @@ -1338,6 +1467,11 @@ diff.whitespace_ignore_at_eol=Ignoruj zmiany w znakach białych przy EOL diff.stats_desc=%d zmienionych plików z %d dodań i %d usunięć diff.bin=BIN diff.view_file=Wyświetl plik +diff.file_before=Przed +diff.file_after=Po +diff.file_image_width=Szerokość +diff.file_image_height=Wysokość +diff.file_byte_size=Rozmiar diff.file_suppressed=Plik diff jest za duży diff.too_many_files=Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików diff.comment.placeholder=Zostaw komentarz @@ -1405,6 +1539,8 @@ branch.restore_failed=Nie udało się przywrócić gałęzi '%s'. branch.protected_deletion_failed=Gałąź '%s' jest chroniona. Nie można jej usunąć. branch.restore=Przywróć gałąź '%s' branch.download=Pobierz gałąź '%s' +branch.included_desc=Ta gałąź jest częścią domyślnej gałęzi +branch.included=Zawarte topic.manage_topics=Zarządzaj tematami topic.done=Gotowe @@ -1428,6 +1564,7 @@ team_name=Nazwa zespołu team_desc=Opis team_name_helper=Nazwy zespołów powinny być krótkie i łatwe do zapamiętania. team_desc_helper=Opisz cel lub rolę zespołu. +team_access_desc=Dostęp do repozytorium team_permission_desc=Uprawnienie team_unit_desc=Zezwól na dostęp do sekcji repozytoriów @@ -1441,6 +1578,7 @@ settings.full_name=Imię i nazwisko settings.website=Strona settings.location=Lokalizacja settings.permission=Uprawnienia +settings.repoadminchangeteam=Administrator repozytorium może dać oraz usunąć dostęp zespołom settings.visibility=Widoczność settings.visibility.public=Publiczne settings.visibility.limited=Ukryte (widoczność tylko dla zalogowanych użytkowników) @@ -1473,6 +1611,8 @@ members.invite_now=Zaproś teraz teams.join=Dołącz teams.leave=Opuść +teams.can_create_org_repo=Tworzenie repozytoriów +teams.can_create_org_repo_helper=Członkowie mogą tworzyć nowe repozytoria w organizacji. Twórca otrzyma uprawnienia administracyjne do nowego repozytorium. teams.read_access=Dostęp do odczytu teams.read_access_helper=Członkowie mogą wyświetlać i klonować repozytoria zespołów. teams.write_access=Dostęp do zapisu @@ -1492,12 +1632,24 @@ teams.delete_team_success=Zespół został usunięty. teams.read_permission_desc=Ten zespół udziela dostępu z odczytem: członkowie mogą wyświetlać i klonować repozytoria zespołu. teams.write_permission_desc=Ten zespół udziela dostępu z zapisem: członkowie mogą wyświetlać i wypychać zmiany do repozytoriów zespołu. teams.admin_permission_desc=Ten zespół udziela dostępu administratora: członkowie mogą wyświetlać i wypychać zmiany oraz dodawać współpracowników do repozytoriów zespołu. +teams.create_repo_permission_desc=Dodatkowo, ten zespół otrzyma uprawnienie Tworzenie repozytoriów: jego członkowie mogą tworzyć nowe repozytoria w organizacji. teams.repositories=Repozytoria zespołu teams.search_repo_placeholder=Szukaj repozytorium… +teams.remove_all_repos_title=Usuń wszystkie repozytoria zespołu +teams.remove_all_repos_desc=Usunie to wszystkie repozytoria przypisane do zespołu. +teams.add_all_repos_title=Dodaj wszystkie repozytoria +teams.add_all_repos_desc=Doda to wszystkie repozytoria organizacji do przypisanych repozytoriów zespołu. teams.add_nonexistent_repo=Repozytorium, które próbujesz dodać, nie istnieje. Proszę je najpierw utworzyć. teams.add_duplicate_users=Użytkownik jest już członkiem zespołu. teams.repos.none=Ten zespół nie ma dostępu do żadnego repozytorium. teams.members.none=Ten zespół nie ma żadnych członków. +teams.specific_repositories=Określone repozytoria +teams.specific_repositories_helper=Członkowie uzyskają dostęp wyłącznie do repozytoriów przypisanych do tego zespołu. Wybranie tej opcji nie usunie automatycznie repozytoriów dodanych przy pomocy Wszystkie repozytoria. +teams.all_repositories=Wszystkie repozytoria +teams.all_repositories_helper=Zespół ma dostęp do wszystkich repozytoriów. Wybranie tego doda wszystkie istniejące repozytoria do tego zespołu. +teams.all_repositories_read_permission_desc=Ten zespół nadaje uprawnienie Odczytu do wszystkich repozytoriów: jego członkowie mogą wyświetlać i klonować repozytoria. +teams.all_repositories_write_permission_desc=Ten zespół nadaje uprawnienie Zapisu do wszystkich repozytoriów: jego członkowie mogą odczytywać i przesyłać do repozytoriów. +teams.all_repositories_admin_permission_desc=Ten zespół nadaje uprawnienia Administratora do wszystkich repozytoriów: jego członkowie mogą odczytywać, przesyłać oraz dodawać innych współtwórców do repozytoriów. [admin] dashboard=Pulpit @@ -1590,6 +1742,7 @@ users.auth_login_name=Nazwa logowania uwierzytelnienia users.password_helper=Pozostaw hasło puste, aby go nie zmieniać. users.update_profile_success=Konto użytkownika zostało zaktualizowane. users.edit_account=Edytuj konto użytkownika +users.max_repo_creation=Maksymalna ilość repozytoriów users.max_repo_creation_desc=(Wpisz -1, aby użyć domyślnego globalnego limitu.) users.is_activated=Konto użytkownika jest aktywne users.prohibit_login=Wyłącz logowanie @@ -1671,6 +1824,15 @@ auths.oauth2_authURL=URL autoryzacji auths.oauth2_profileURL=URL profilu auths.oauth2_emailURL=URL adresu e-mail auths.enable_auto_register=Włącz automatyczną rejestrację +auths.sspi_auto_create_users=Automatycznie twórz użytkowników +auths.sspi_auto_create_users_helper=Zezwól metodzie uwierzytelniania SSPI na automatyczne tworzenie nowych kont dla użytkowników, którzy logują się po raz pierwszy +auths.sspi_auto_activate_users=Automatycznie aktywuj użytkowników +auths.sspi_auto_activate_users_helper=Zezwól metodzie uwierzytelnienia SSPI na automatyczne aktywowanie nowych kont użytkowników +auths.sspi_strip_domain_names=Usuwaj nazwy domen z nazw użytkowników +auths.sspi_strip_domain_names_helper=Gdy zaznaczone, nazwy domen będą usuwane z nazw logowania (np. zamiast "DOMENA\osoba", czy osoba@example.org" będą po prostu "osoba"). +auths.sspi_separator_replacement=Używany separator zamiast \, / oraz @ +auths.sspi_default_language=Domyślny język użytkownika +auths.sspi_default_language_helper=Domyślny język dla użytkowników automatycznie stworzonych przy pomocy metody uwierzytelnienia SSPI. Pozostaw puste, jeśli język ma zostać wykryty automatycznie. auths.tips=Wskazówki auths.tips.oauth2.general=Uwierzytelnianie OAuth2 auths.tips.oauth2.general.tip=Przy rejestracji nowego uwierzytelnienia OAuth2, URL zwrotny/przekierowań powinien mieć postać /user/oauth2//callback @@ -1684,6 +1846,7 @@ auths.tip.google_plus=Uzyskaj dane uwierzytelniające klienta OAuth2 z konsoli G auths.tip.openid_connect=Użyj adresu URL OpenID Connect Discovery (/.well-known/openid-configuration), aby określić punkty końcowe auths.tip.twitter=Przejdź na https://dev.twitter.com/apps, stwórz aplikację i upewnij się, że opcja “Allow this application to be used to Sign in with Twitter” jest włączona auths.tip.discord=Zarejestruj nową aplikację na https://discordapp.com/developers/applications/me +auths.tip.gitea=Zarejestruj nową aplikację OAuth2. Przewodnik można znaleźć na https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=Edytuj źródło uwierzytelniania auths.activated=To źródło uwierzytelniania jest aktywne auths.new_success=Uwierzytelnienie '%s' zostało dodane. @@ -1692,6 +1855,10 @@ auths.update=Zaktualizuj źródło uwierzytelniania auths.delete=Usuń źródło uwierzytelniania auths.delete_auth_title=Usuń źródło uwierzytelniania auths.delete_auth_desc=Usunięcie źródła uwierzytelniania uniemożliwi użytkownikom używania go do zalogowania się. Kontynuować? +auths.still_in_used=Źródło uwierzytelniania jest wciąż w użyciu. Przekonwertuj lub usuń użytkowników przed użyciem tego źródła uwierzytelniania. +auths.deletion_success=Źródło uwierzytelniania zostało usunięte. +auths.login_source_exist=Źródło uwierzytelniania '%s' już istnieje. +auths.login_source_of_type_exist=Źródło uwierzytelniania tego typu już istnieje. config.server_config=Konfiguracja serwera config.app_name=Tytuł strony @@ -1727,6 +1894,7 @@ config.ssh_minimum_key_sizes=Minimalne rozmiary kluczy config.lfs_config=Konfiguracja LFS config.lfs_enabled=Włączone config.lfs_content_path=Ścieżka zawartości LFS +config.lfs_http_auth_expiry=Wygasanie uwierzytelnienia LFS HTTP config.db_config=Konfiguracja bazy danych config.db_type=Typ @@ -1737,16 +1905,33 @@ config.db_ssl_mode=SSL config.db_path=Ścieżka config.service_config=Konfiguracja usługi +config.register_email_confirm=Wymagaj potwierdzenia adresu e-mail przy rejestracji +config.disable_register=Wyłącz samodzielną rejestrację +config.allow_only_external_registration=Zezwól na rejestrację wyłącznie za pomocą zewnętrznych usług +config.enable_openid_signup=Włącz samodzielną rejestrację za pomocą OpenID +config.enable_openid_signin=Włącz logowanie za pomocą OpenID config.show_registration_button=Pokazuj przycisk rejestracji +config.require_sign_in_view=Wymagaj zalogowania w celu wyświetlania stron +config.mail_notify=Włącz powiadomienia e-mail config.disable_key_size_check=Wyłącz sprawdzanie minimalnego rozmiaru klucza config.enable_captcha=Włącz CAPTCHA config.active_code_lives=Ważność kodów aktywacyjnych +config.reset_password_code_lives=Czas wygaśnięcia kodu przywracania konta +config.default_keep_email_private=Domyślne ukrywanie adresów e-mail +config.default_allow_create_organization=Domyślnie zezwalaj na tworzenie organizacji +config.enable_timetracking=Włącz śledzenie czasu +config.default_enable_timetracking=Domyślnie włącz śledzenie czasu +config.default_allow_only_contributors_to_track_time=Zezwalaj wyłącznie współpracownikom na śledzenie czasu +config.no_reply_address=Ukryta domena e-mail +config.default_visibility_organization=Domyślna widoczność dla nowych organizacji +config.default_enable_dependencies=Domyślne włączanie zależności zgłoszeń config.webhook_config=Konfiguracja webhooka config.queue_length=Długość kolejki config.deliver_timeout=Limit czasu doręczenia config.skip_tls_verify=Pomiń weryfikację TLS +config.mailer_config=Konfiguracja dostawcy SMTP config.mailer_enabled=Włączona config.mailer_disable_helo=Wyłącz HELO config.mailer_name=Nazwa @@ -1754,6 +1939,9 @@ config.mailer_host=Serwer config.mailer_user=Użytkownik config.mailer_use_sendmail=Używaj Sendmail config.mailer_sendmail_path=Ścieżka Sendmail +config.mailer_sendmail_args=Dodatkowe argumenty Sendmail +config.send_test_mail=Wyślij testową wiadomość e-mail +config.test_mail_failed=Nie udało się wysłać testowej wiadomości e-mail do '%s': %v config.test_mail_sent=Testowa wiadomość e-mail została wysłana do '%s'. config.oauth_config=Konfiguracja OAuth @@ -1763,6 +1951,7 @@ config.cache_config=Konfiguracja pamięci podręcznej config.cache_adapter=Adapter pamięci podręcznej config.cache_interval=Interwał pamięci podręcznej config.cache_conn=Połączenie z pamięcią podręczną +config.cache_item_ttl=TTL składnika pamięci podręcznej config.session_config=Konfiguracja sesji config.session_provider=Dostawca sesji @@ -1774,6 +1963,7 @@ config.session_life_time=Czas ważności sesji config.https_only=Tylko HTTPS config.cookie_life_time=Czas ważności ciasteczka +config.picture_config=Konfiguracja obrazu i awataru config.picture_service=Usługa obrazów config.disable_gravatar=Wyłącz Gravatar config.enable_federated_avatar=Włącz sfederowane awatary @@ -1792,6 +1982,9 @@ config.git_gc_timeout=Limit czasu usuwania śmieci config.log_config=Konfiguracja dziennika config.log_mode=Tryb dziennika +config.macaron_log_mode=Tryb dziennika Macaron +config.own_named_logger=Nazwany logger +config.routes_to_default_logger=Ścieżki do domyślnego loggera config.go_log=Używa dziennika Go (domyślne przekierowanie) config.router_log_mode=Tryb dziennika routera config.disabled_logger=Wyłączone @@ -1810,6 +2003,11 @@ monitor.process=Uruchomione procesy monitor.desc=Opis monitor.start=Czas rozpoczęcia monitor.execute_time=Czas wykonania +monitor.process.cancel=Anuluj proces +monitor.process.cancel_desc=Anulowanie procesu może spowodować utratę danych +monitor.process.cancel_notices=Anuluj: %s? + + notices.system_notice_list=Powiadomienia systemu notices.view_detail_header=Pokaż szczegóły powiadomienia @@ -1846,6 +2044,8 @@ compare_commits_general=Porównaj commity mirror_sync_push=synchronizuje commity do %[3]s w %[4]s z kopii lustrzanej mirror_sync_create=synchronizuje nowe odwołanie %[2]s do %[3]s z kopii lustrzanej mirror_sync_delete=synchronizuje i usuwa odwołanie %[2]s w %[3]s z kopii lustrzanej +approve_pull_request=`zatwierdził(-a) %s#%[2]s` +reject_pull_request=`zaproponował(-a) zmiany dla %s#%[2]s` [tool] ago=%s temu @@ -1887,12 +2087,15 @@ mark_as_unread=Oznacz jak nieprzeczytane mark_all_as_read=Oznacz wszystkie jako przeczytane [gpg] +default_key=Podpisano domyślnym kluczem error.extract_sign=Nie udało się wyłuskać podpisu error.generate_hash=Nie udało się wygenerować skrótu dla commitu error.no_committer_account=Brak konta powiązanego z adresem e-mail autora error.no_gpg_keys_found=Nie znaleziono w bazie danych klucza dla tego podpisu error.not_signed_commit=Commit nie podpisany error.failed_retrieval_gpg_keys=Nie udało się odzyskać żadnego klucza powiązanego z kontem autora +error.probable_bad_signature=OSTRZEŻENIE! Pomimo istnienia klucza z takim ID w bazie, nie weryfikuje on tego commita! Ten commit jest PODEJRZANY. +error.probable_bad_default_signature=OSTRZEŻENIE! Pomimo, że domyślny klucz posiada to ID, nie weryfikuje on tego commita! Ten commit jest PODEJRZANY. [units] error.no_unit_allowed_repo=Nie masz uprawnień do żadnej sekcji tego repozytorium. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 0c5ff32a6c53..bee2f38b8a15 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -10,6 +10,7 @@ link_account=Vincular conta register=Cadastrar website=Site version=Versão +powered_by=Desenvolvido por %s page=Página template=Template language=Idioma @@ -959,6 +960,7 @@ issues.add_time=Adicionar tempo manualmente issues.add_time_short=Adicionar tempo issues.add_time_cancel=Cancelar issues.add_time_history=`adicionou tempo gasto %s` +issues.del_time_history=`removeu tempo gasto %s` issues.add_time_hours=Horas issues.add_time_minutes=Minutos issues.add_time_sum_to_small=Nenhum tempo foi inserido. @@ -1016,7 +1018,7 @@ issues.review.content.empty=Você precisa deixar um comentário indicando as alt issues.review.reject=alterações solicitadas %s issues.review.pending=Pendente issues.review.review=Revisão -issues.review.reviewers=Revisões +issues.review.reviewers=Revisores issues.review.show_outdated=Mostrar desatualizado issues.review.hide_outdated=Ocultar desatualizado issues.assignee.error=Nem todos os responsáveis foram adicionados devido a um erro inesperado. @@ -1052,6 +1054,7 @@ pulls.is_checking=Verificação de conflitos do merge está em andamento. Tente pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas. pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request. pulls.blocked_by_approvals=Este pull request ainda não possui aprovações suficientes. %d de %d aprovações concedidas. +pulls.blocked_by_rejection=Este pull request possui alterações solicitadas por um revisor oficial. pulls.can_auto_merge_desc=O merge deste pull request pode ser aplicado automaticamente. pulls.cannot_auto_merge_desc=O merge deste pull request não pode ser aplicado automaticamente pois há conflitos. pulls.cannot_auto_merge_helper=Faça o merge manualmente para resolver os conflitos. @@ -1415,6 +1418,8 @@ settings.update_protect_branch_success=Proteção do branch '%s' foi atualizada. settings.remove_protected_branch_success=Proteção do branch '%s' foi desabilitada. settings.protected_branch_deletion=Desabilitar proteção de branch settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar? +settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas +settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente. settings.default_branch_desc=Selecione um branch padrão para pull requests e commits de código: settings.choose_branch=Escolha um branch... settings.no_protected_branch=Não há branches protegidos. @@ -2020,6 +2025,54 @@ monitor.execute_time=Tempo de execução monitor.process.cancel=Cancelar processo monitor.process.cancel_desc=Cancelar um processo pode causar perda de dados monitor.process.cancel_notices=Cancelar: %s? +monitor.queues=Filas +monitor.queue=Fila: %s +monitor.queue.name=Nome +monitor.queue.type=Tipo +monitor.queue.exemplar=Tipo de modelo +monitor.queue.numberworkers=Número de executores +monitor.queue.maxnumberworkers=Número máximo de executores +monitor.queue.review=Revisar configuração +monitor.queue.review_add=Revisar/Adicionar executores +monitor.queue.configuration=Configuração inicial +monitor.queue.nopool.title=Nenhum conjunto de executores +monitor.queue.nopool.desc=Essa fila agrupa outras filas e não possui um conjunto de executores. +monitor.queue.wrapped.desc=Uma fila agrupada envolve uma fila inicial lenta, armazenando as solicitações da fila em um canal. Ela não possui um conjunto de executores em si. +monitor.queue.persistable-channel.desc=Um canal persistente envolve duas filas, uma fila de canais que tem seu próprio conjunto de executores e uma fila de nível para solicitações persistentes de encerramentos anteriores. Ela não tem um conjunto de executores em si. +monitor.queue.pool.timeout=Tempo de espera +monitor.queue.pool.addworkers.title=Adicionar executores +monitor.queue.pool.addworkers.submit=Adicionar executores +monitor.queue.pool.addworkers.desc=Adicionar executores a este conjunto com ou sem tempo de espera. Se você definir um tempo de espera, estes executores serão removidos do conjunto depois que o tempo de espera expirar. +monitor.queue.pool.addworkers.numberworkers.placeholder=Número de executores +monitor.queue.pool.addworkers.timeout.placeholder=Defina 0 para não ter tempo de espera +monitor.queue.pool.addworkers.mustnumbergreaterzero=O número de executores à adicionar deve ser maior que zero +monitor.queue.pool.addworkers.musttimeoutduration=Tempo de espera deve ser uma duração em "golang", por exemplo, 5m ou 0 + +monitor.queue.settings.title=Configurações do conjunto +monitor.queue.settings.desc=Os conjuntos crescem dinamicamente com um aumento em resposta ao bloqueio da fila de executores. Essas alterações não afetarão os grupos de executores atuais. +monitor.queue.settings.timeout=Tempo de espera do impulso +monitor.queue.settings.timeout.placeholder=Atualmente %[1]v +monitor.queue.settings.timeout.error=Tempo de espera deve ser uma duração em "golang", por exemplo, 5m ou 0 +monitor.queue.settings.numberworkers=Número de executores a impulsionar +monitor.queue.settings.numberworkers.placeholder=Atualmente %[1]d +monitor.queue.settings.numberworkers.error=O número de executores à adicionar deve ser maior ou igual a zero +monitor.queue.settings.maxnumberworkers=Número máximo de executores +monitor.queue.settings.maxnumberworkers.placeholder=Atualmente %[1]d +monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve ser um número +monitor.queue.settings.submit=Atualizar configurações +monitor.queue.settings.changed=Configurações atualizadas +monitor.queue.settings.blocktimeout=Tempo de espera do bloqueio atual +monitor.queue.settings.blocktimeout.value=%[1]v + +monitor.queue.pool.none=Esta fila não tem um conjunto +monitor.queue.pool.added=Grupo de executores adicionado +monitor.queue.pool.max_changed=Número máximo de executores alterado +monitor.queue.pool.workers.title=Grupo de executores ativo +monitor.queue.pool.workers.none=Nenhum grupo de executores. +monitor.queue.pool.cancel=Encerrar grupo de executores +monitor.queue.pool.cancelling=Encerrando grupo de executores +monitor.queue.pool.cancel_notices=Encerrar este grupo de %s executores? +monitor.queue.pool.cancel_desc=Deixar uma fila sem grupos de executores pode fazer com que as solicitações sejam bloqueadas indefinidamente. notices.system_notice_list=Avisos do sistema notices.view_detail_header=Ver detalhes do aviso diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 470d5046a3af..d4acd0ad454d 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -66,14 +66,28 @@ forks=Форки activities=Активность pull_requests=Pull Request'ы issues=Задачи +milestones=Этапы cancel=Отмена +add=Добавить +add_all=Добавить все +remove=Удалить +remove_all=Удалить все write=Редактирование preview=Предпросмотр loading=Загрузка… [startpage] +app_desc=Удобный сервис собственного хостинга репозиториев Git +install=Простой в установке +install_desc=Просто запустите исполняемый файл для вашей платформы. Иcпользуйте Gitea с Docker или Vagrant, или загрузите пакет. +platform=Кроссплатформенный +platform_desc=Gitea работает на любой операционной системе, которая может компилировать Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! +lightweight=Легковесный +lightweight_desc=Gitea имеет низкие системные требования и может работать на недорогом Raspberry Pi. Экономьте энергию вашей машины! +license=Открытый исходный код +license_desc=Всё это на code.gitea.io/gitea! Присоединяйтесь к нам, внося вклад, чтобы сделать этот проект еще лучше. Не бойтесь помогать! [install] install=Установка @@ -236,6 +250,10 @@ twofa_scratch_token_incorrect=Неверный scratch-код. login_userpass=Вход login_openid=OpenID oauth_signup_tab=Зарегистрировать новый аккаунт +oauth_signup_title=Добавить адрес электронной почты и пароль (для восстановления учетной записи) +oauth_signup_submit=Полная учетная запись +oauth_signin_tab=Ссылка на существующую учетную запись +oauth_signin_title=Войдите, чтобы авторизовать связанную учетную запись oauth_signin_submit=Привязать учетную запись openid_connect_submit=Подключить openid_connect_title=Подключение к существующей учетной записи @@ -244,7 +262,9 @@ openid_register_title=Создать новый аккаунт openid_register_desc=Выбранный OpenID URI неизвестен. Свяжите с новой учетной записью здесь. openid_signin_desc=Введите свой OpenID URI. Например: https://anne.me, bob.openid.org.cn или gnusocial.net/carry. disable_forgot_password_mail=Восстановление аккаунта отключено. Пожалуйста, свяжитесь с администратором сайта. +email_domain_blacklisted=С данным email регистрация невозможна. authorize_application=Авторизация приложения +authorize_redirect_notice=Вы будете перенаправлены на %s, если вы авторизуете это приложение. authorize_application_created_by=Это приложение было создано %s. authorize_application_description=Если вы предоставите доступ, оно сможет получить доступ и редактировать любую информацию о вашей учетной записи, включая содержимое частных репозиториев и организаций. authorize_title=Разрешить «%s» доступ к вашей учетной записи? @@ -283,6 +303,8 @@ CommitChoice=Выбор коммита TreeName=Путь к файлу Content=Содержимое +SSPISeparatorReplacement=Разделитель +SSPIDefaultLanguage=Язык по умолчанию require_error=` не может быть пустым.` alpha_dash_error=` должен содержать только буквенно-цифровые символы, тире (' - ') и подчеркивания ('_').` @@ -297,6 +319,7 @@ include_error=` должен содержать '%s'.` unknown_error=Неизвестная ошибка: captcha_incorrect=Капча не пройдена. password_not_match=Пароли не совпадают. +lang_select_error=Выберите язык из списка. username_been_taken=Имя пользователя уже занято. repo_name_been_taken=Имя репозитория уже используется. @@ -308,10 +331,15 @@ team_no_units_error=Разрешите доступ хотя бы к одном email_been_used=Этот адрес электронной почты уже используется. openid_been_used=Адрес OpenID '%s' уже используется. username_password_incorrect=Неверное имя пользователя или пароль. +password_lowercase_one=Как минимум один строчный символ +password_uppercase_one=Как минимум один заглавный символ +password_digit_one=По крайней мере одна цифра +password_special_one=По крайней мере один специальный символ (знаки пунктуации, скобки, кавычки и т. д.) enterred_invalid_repo_name=Введенное вами имя репозитория неверно. enterred_invalid_owner_name=Имя нового владельца недоступно. enterred_invalid_password=Введенный пароль неверный. user_not_exist=Пользователь не существует. +team_not_exist=Команда не существует. last_org_owner=Вы не можете удалить последнего пользователя из команды 'владельцы'. В любой команде должен быть хотя бы один владелец. cannot_add_org_to_team=Организацию нельзя добавить в качестве члена команды. @@ -479,8 +507,12 @@ access_token_deletion_desc=Удаление токена отменит дост delete_token_success=Токен удалён. Приложения, использующие его, больше не имеют доступа к вашему аккаунту. 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 успешно применены. @@ -490,14 +522,20 @@ oauth2_type_web=Веб (например: Node.JS, Tomcat, Go) oauth2_type_native=Нативный (например: телефон, ПК, браузер) oauth2_redirect_uri=URI переадресации save_application=Сохранить +oauth2_client_id=ID клиента oauth2_client_secret=Клиентский ключ oauth2_regenerate_secret=Сгенерировать новый ключ oauth2_regenerate_secret_hint=Потеряли свой ключ? oauth2_client_secret_hint=Секретный ключ не будет показан, если вы повторно откроете эту страницу. Пожалуйста сохраните секретный ключ. +oauth2_application_edit=Изменить +oauth2_application_create_description=Приложения OAuth2 предоставляет стороннему приложению доступ к учетным записям пользователей данного сервиса. +oauth2_application_remove_description=Удаление приложения OAuth2 приведёт к отмене его доступа к авторизованным учетным записям пользователей в данном экземпляре. Продолжить? authorized_oauth2_applications=Авторизованные приложения OAuth2 +authorized_oauth2_applications_description=Вы предоставили доступ к вашему персональному аккаунту Gitea этим сторонним приложениям. Пожалуйста, отзовите доступ у приложений, которые больше не используются. revoke_key=Отозвать revoke_oauth2_grant=Отозвать доступ +revoke_oauth2_grant_description=Отзыв доступа у этого стороннего приложения не позволит ему получать доступ к вашим данным. Вы уверены? revoke_oauth2_grant_success=Вы успешно отозвали доступ. twofa_desc=Двухфакторная проверка подлинности повышает уровень безопасности вашей учётной записи. @@ -541,12 +579,22 @@ confirm_delete_account=Подтвердите удаление delete_account_title=Удалить аккаунт delete_account_desc=Вы уверены, что хотите навсегда удалить этот аккаунт? +email_notifications.enable=Включить почтовые уведомления +email_notifications.onmention=Только уведомлять по почте при упоминании +email_notifications.disable=Отключить почтовые уведомления +email_notifications.submit=Установить настройки электронной почты [repo] owner=Владелец repo_name=Имя репозитория repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов. +repo_size=Размер репозитория +template=Шаблон +template_select=Выбрать шаблон. +template_helper=Сделать репозиторий шаблоном +template_description=Шаблонные репозитории дают возможность пользователям создавать новые репозитории с той же структурой каталогов, файлами и дополнительными настройками. visibility=Видимость +visibility_description=Только владелец или члены организации, при наличии прав, смогут увидеть это. visibility_helper=Сделать репозиторий приватным visibility_helper_forced=Администратор сайта настроил параметр видимости новых репозиториев. Репозиторий приватный по умолчанию. visibility_fork_helper=(Изменение этого повлияет на все форки.) @@ -554,9 +602,12 @@ clone_helper=Нужна помощь в клонировании? Посетит fork_repo=Форкнуть репозиторий fork_from=Форк от fork_visibility_helper=Видимость форкнутого репозитория изменить нельзя. +use_template=Использовать этот шаблон +generate_repo=Создать репозиторий repo_desc=Описание repo_lang=Язык repo_gitignore_helper=Выберите шаблон .gitignore. +issue_labels=Метки задач license=Лицензия license_helper=Выберите файл лицензии. readme=README @@ -569,6 +620,8 @@ mirror_prune_desc=Удаление устаревших отслеживаемы mirror_interval=Интервал зеркалирования (допустимые единицы измерения 'h', 'm', 's'). Значение 0 отключает синхронизацию. mirror_interval_invalid=Недопустимый интервал зеркалирования. mirror_address=Клонировать по URL +mirror_address_url_invalid=Указанный url неверный. Вы должны правильно экранировать все компоненты url. +mirror_address_protocol_invalid=Указанный url неверный. Только http(s):// или git:// местоположения могут быть зеркалированы. mirror_last_synced=Последняя синхронизация watchers=Наблюдатели stargazers=Звездочеты @@ -576,6 +629,11 @@ forks=Форки pick_reaction=Оставьте свою оценку! reactions_more=и ещё %d +template.git_hooks=Git хуки +template.webhooks=Веб-хуки +template.topics=Темы +template.avatar=Аватар +template.issue_labels=Метки задач archive.title=Это архивный репозиторий. Вы можете его клонировать или просматривать файлы, но не вносить изменения или открывать задачи/запросы на слияние. archive.issue.nocomment=Этот репозиторий в архиве. Вы не можете комментировать задачи. @@ -588,6 +646,7 @@ form.name_pattern_not_allowed=Шаблон имени репозитория '%s need_auth=Требуется авторизация migrate_type=Тип миграции migrate_type_helper=Этот репозиторий будет зеркалом +migrate_items=Элементы миграции migrate_items_wiki=Вики migrate_items_milestones=Этапы migrate_items_labels=Метки @@ -602,6 +661,9 @@ migrate.permission_denied=У вас нет прав на импорт локал migrate.invalid_local_path=Недопустимый локальный путь. Возможно он не существует или не является папкой. migrate.failed=Миграция не удалась: %v migrate.lfs_mirror_unsupported=Зеркалирование LFS объектов не поддерживается - используйте 'git lfs fetch --all' и 'git lfs push --all' вручную. +migrate.migrate_items_options=При миграции из GitHub, укажите имя пользователя - и появятся параметры миграции. +migrated_from=Перенесено с %[2]s +migrated_from_fake=Перенесено с %[1]s mirror_from=зеркало из forked_from=форкнуто от @@ -648,6 +710,9 @@ video_not_supported_in_browser=Ваш браузер не поддерживае audio_not_supported_in_browser=Ваш браузер не поддерживает HTML5 'audio' тэг. stored_lfs=Хранится Git LFS commit_graph=Граф коммитов +normal_view=Обычный вид +line=строка +lines=строки editor.new_file=Новый файл editor.upload_file=Загрузить файл @@ -656,6 +721,7 @@ 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.delete_this_file=Удалить файл @@ -666,12 +732,15 @@ editor.filename_help=Чтобы добавить каталог, просто н editor.or=или editor.cancel_lower=Отменить editor.commit_changes=Сохранить правки +editor.add_tmpl=Добавить '' editor.add=Добавить '%s' editor.update=Изменить '%s' editor.delete=Удалить '%s' editor.commit_message_desc=Добавьте необязательное расширенное описание… editor.commit_directly_to_this_branch=Сделайте коммит прямо в ветку %s. editor.create_new_branch=Создайте новую ветку для этого коммита, и сделайте Pull Request. +editor.create_new_branch_np=Создать новую ветку для этого коммита. +editor.propose_file_change=Предложить изменение файла editor.new_branch_name_desc=Новое название ветки… editor.cancel=Отмена editor.filename_cannot_be_empty=Имя файла не может быть пустым. @@ -685,6 +754,7 @@ editor.file_editing_no_longer_exists=Редактируемый файл '%s' б editor.file_deleting_no_longer_exists=Удаляемый файл '%s' больше не существует в этом репозитории. editor.file_changed_while_editing=Содержимое файла изменилось с момента начала редактирования. Нажмите здесь, чтобы увидеть, что было изменено, или Зафиксировать изменения снова, чтобы заменить их. editor.file_already_exists=Файл с именем '%s' уже существует в репозитории. +editor.commit_empty_file_header=Закоммитить пустой файл editor.no_changes_to_show=Нет изменений. editor.fail_to_update_file=Не удалось обновить/создать файл «%s» из-за ошибки: %v editor.add_subdir=Добавить каталог… @@ -696,6 +766,7 @@ commits.desc=Просмотр истории изменений исходног commits.commits=коммитов commits.no_commits=Ничего общего в коммитах. '%s' и '%s' имеют совершенно разные истории. commits.search=Поиск коммитов… +commits.search.tooltip=Вы можете предварять ключевые слова словами "author:", "committer:", "after:", или "before:", например, "revert author:Alice before:2019-04-01". commits.find=Поиск commits.search_all=Все ветки commits.author=Автор @@ -711,6 +782,7 @@ ext_issues.desc=Ссылка на внешнюю систему отслежив issues.desc=Организация отчетов об ошибках, задач и этапов. issues.new=Новая задача +issues.new.title_empty=Заголовок не может быть пустым issues.new.labels=Метки issues.new.no_label=Нет меток issues.new.clear_labels=Отчистить метки @@ -742,6 +814,7 @@ issues.deleted_milestone=`(удалено)` issues.self_assign_at=`самоназначился %s` issues.add_assignee_at=`был назначен %s %s` issues.remove_assignee_at=`был снят с назначения %s %s` +issues.remove_self_assignment=`убрал их назначение %s` issues.delete_branch_at=`удалена ветка %s %s` issues.open_tab=%d открыто(ы) issues.close_tab=%d закрыто(ы) @@ -778,6 +851,7 @@ issues.action_assignee=Ответственный issues.action_assignee_no_select=Нет ответственного issues.opened_by=открыта %[1]s %[3]s pulls.merged_by=принят %[1]s %[3]s +pulls.merged_by_fake=%[1]s слита пользователем %[2]s issues.closed_by=закрыта %[1]s %[3]s issues.opened_by_fake=%[1]s открыта %[2]s issues.closed_by_fake=%[1]s закрыта пользователем %[2]s @@ -788,6 +862,9 @@ issues.closed_title=Закрыто issues.num_comments=комментариев: %d issues.commented_at=`прокомментировал %s` issues.delete_comment_confirm=Вы уверены, что хотите удалить этот комментарий? +issues.context.copy_link=Копировать ссылку +issues.context.edit=Редактировать +issues.context.delete=Удалить issues.no_content=Пока нет содержимого. issues.close_issue=Закрыть issues.close_comment_issue=Прокомментировать и закрыть @@ -829,6 +906,7 @@ issues.unlock=Снять ограничение issues.lock.unknown_reason=Для ограничения обсуждения необходимо указать причину. issues.lock_duplicate=Обсуждение задачи уже ограничено. issues.unlock_error=Невозможно снять несуществующее ограничение обсуждения. +issues.lock_with_reason=заблокировано как %s и ограничено обсуждение для соучастников %s issues.lock_no_reason=ограничил(а) обсуждение задачи кругом соавторов %s issues.unlock_comment=снял(а) ограничение %s issues.lock_confirm=Ограничить @@ -846,6 +924,7 @@ issues.tracker=Отслеживание времени issues.start_tracking_short=Начать issues.start_tracking=Начать отслеживание времени issues.start_tracking_history=`начал работать %s` +issues.tracker_auto_close=Таймер будет остановлен автоматически, когда эта проблема будет закрыта issues.tracking_already_started=`Вы уже начали отслеживать время для этой задачи!` issues.stop_tracking=Остановить issues.stop_tracking_history=`перестал работать %s` @@ -914,6 +993,7 @@ issues.review.reviewers=Рецензенты issues.review.show_outdated=Показать устаревшие issues.review.hide_outdated=Скрыть устаревшие +pulls.desc=Включить запросы на слияние и проверки кода. pulls.new=Новый Pull Request pulls.compare_changes=Новый Pull Request pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. @@ -924,11 +1004,13 @@ pulls.no_results=Результатов не найдено. pulls.nothing_to_compare=Нечего сравнивать, родительская и текущая ветка одинаковые. pulls.has_pull_request=`Уже существует запрос на слияние между двумя целями: %[2]s#%[3]d` pulls.create=Создать Pull Request +pulls.title_desc=хочет смерджить %[1]d коммит(ов) из %[2]s в %[3]s pulls.merged_title_desc=слито %[1]d коммит(ов) из %[2]s в %[3]s %[4]s pulls.tab_conversation=Обсуждение pulls.tab_commits=Коммиты pulls.tab_files=Измененные файлы pulls.reopen_to_merge=Пожалуйста, переоткройте этот Pull Request для выполнения слияния. +pulls.cant_reopen_deleted_branch=Этот запрос на слияние не может быть открыт заново, потому что ветка была удалена. pulls.merged=Слито pulls.has_merged=Слияние этого запроса успешно завершено. pulls.title_wip_desc=`Добавьте %s в начало заголовка для защиты от случайного досрочного принятия Pull Request'а.` @@ -949,6 +1031,9 @@ pulls.rebase_merge_commit_pull_request=Выполнить rebase и принят pulls.squash_merge_pull_request=Объединить и принять PR pulls.invalid_merge_option=Этот параметр слияния нельзя использовать для этого Pull Request'а. pulls.open_unmerged_pull_exists=`Вы не можете снова открыть, поскольку уже существует запрос на слияние (#%d) из того же репозитория с той же информацией о слиянии и ожидающий слияния.` +pulls.status_checking=Выполняются некоторые проверки +pulls.status_checks_success=Все проверки выполнены успешно +pulls.status_checks_error=Некоторые проверки не удались milestones.new=Новый этап milestones.open_tab=%d открыты @@ -997,6 +1082,8 @@ wiki.save_page=Сохранить страницу wiki.last_commit_info=%s редактировал эту страницу %s wiki.edit_page_button=Редактировать wiki.new_page_button=Новая страница +wiki.file_revision=Версия страницы +wiki.back_to_wiki=Вернуться на wiki страницу wiki.delete_page_button=Удалить страницу wiki.delete_page_notice_1=Удаление вики-страницы '%s' не может быть отменено. Продолжить? wiki.page_already_exists=Вики-страница с таким именем уже существует. @@ -1010,6 +1097,9 @@ 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.overview=Обзор activity.active_prs_count_1=%d Активный Pull Request activity.active_prs_count_n=%d Активных Pull Request'ов @@ -1046,6 +1136,26 @@ 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.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_and_deletions=и +activity.git_stats_deletion_1=%d удаление +activity.git_stats_deletion_n=%d удалений search=Поиск search.search_repo=Поиск по репозиторию @@ -1058,6 +1168,7 @@ settings.collaboration=Соавторы settings.collaboration.admin=Администратор settings.collaboration.write=Запись settings.collaboration.read=Просмотр +settings.collaboration.owner=Владелец settings.collaboration.undefined=Не определено settings.hooks=Автоматическое обновление settings.githooks=Git хуки @@ -1065,6 +1176,9 @@ settings.basic_settings=Основные параметры settings.mirror_settings=Настройки зеркалирования settings.sync_mirror=Синхронизировать settings.mirror_sync_in_progress=Синхронизируются репозитории-зеркала. Подождите минуту и обновите страницу. +settings.email_notifications.enable=Включить почтовые уведомления +settings.email_notifications.disable=Отключить почтовые уведомления +settings.email_notifications.submit=Установить настройки электронной почты settings.site=Сайт settings.update_settings=Обновить настройки settings.advanced_settings=Расширенные настройки @@ -1135,6 +1249,10 @@ settings.collaborator_deletion_desc=Этот пользователь больш settings.remove_collaborator_success=Соавтор удалён. settings.search_user_placeholder=Поиск пользователя… settings.org_not_allowed_to_be_collaborator=Организации не могут быть добавлены как соавторы. +settings.team_not_in_organization=Команда не в той же организации, что и репозиторий +settings.add_team_duplicate=Команда уже имеет репозиторий +settings.add_team_success=Команда теперь имеет доступ к репозиторию. +settings.remove_team_success=Доступ команды к репозиторию был удален. settings.add_webhook=Добавить Webhook settings.add_webhook.invalid_channel_name=Имя канала не может быть пустым или состоять только из символа #. settings.hooks_desc=Webhooks позволяют внешним службам получать уведомления при возникновении определенных событий на Gitea. При возникновении указанных событий мы отправим запрос POST на каждый заданный вами URL. Узнать больше можно в нашем руководстве по webhooks. @@ -1184,6 +1302,7 @@ settings.event_pull_request=Pull Request settings.event_pull_request_desc=Запрос слияния открыт, закрыт, переоткрыт, изменён, одобрен, отклонён, рецензирован, назначен, снят, метка обновлена, метка убрана, или синхронизирован. settings.event_push=Push settings.event_push_desc=Push в репозиторий. +settings.branch_filter=Фильтр веток settings.event_repository=Репозиторий settings.event_repository_desc=Репозиторий создан или удален. settings.active=Активный @@ -1223,6 +1342,8 @@ settings.protected_branch_can_push_yes=Вы можете выполнять push settings.protected_branch_can_push_no=Вы не можете выполнять push settings.branch_protection=Защита ветки %s settings.protect_this_branch=Защитить эту ветку +settings.protect_disable_push=Отключить Push +settings.protect_enable_push=Включить Push settings.protect_whitelist_users=Пользователи, которые могут делать push в эту ветку: settings.protect_whitelist_search_users=Поиск пользователей… settings.protect_whitelist_teams=Команды, члены которых могут делать push в эту ветку: @@ -1231,7 +1352,9 @@ settings.protect_merge_whitelist_committers=Ограничить право на settings.protect_merge_whitelist_committers_desc=Вы можете добавлять пользователей или целые команды в "белый" список этой ветки. Только присутствующие в списке смогут принимать Pull Request'ы. В противном случае любой с правами на запись в репозиторий будет обладать такой возможностью. settings.protect_merge_whitelist_users=Пользователи с правом на принятие Pull Request'ов в эту ветку: settings.protect_merge_whitelist_teams=Команды, члены которых обладают правом на принятие Pull Request'ов в эту ветку: +settings.protect_check_status_contexts=Включить проверку статуса settings.protect_required_approvals=Необходимые одобрения: +settings.protect_required_approvals_desc=Разрешить объединение Pull Request'а только с достаточным количеством положительных отзывов. settings.protect_approvals_whitelist_users=Рецензенты в белом списке: settings.protect_approvals_whitelist_teams=Команды в белом списке для рецензирования: settings.add_protected_branch=Включить защиту @@ -1251,16 +1374,31 @@ settings.archive.button=Архивировать репозиторий settings.archive.header=Архивировать этот репозиторий settings.archive.text=Архивация репозитория переведет его в режим read-only. Он будет скрыт из панели управления, создание задач, запросов на слияние, или создание коммитов будут запрещены. settings.archive.success=Репозиторий был успешно архивирован. +settings.archive.error=Ошибка при попытке архивировать репозиторий. Смотрите логи для получения подробностей. settings.archive.error_ismirror=Вы не можете поместить зеркалируемый репозиторий в архив. +settings.archive.branchsettings_unavailable=Настройки ветки недоступны, если репозиторий архивирован. settings.unarchive.button=Разархивировать settings.unarchive.header=Разархивировать этот репозиторий +settings.unarchive.text=Разархивация восстанавливает возможность совершать push в репозиторий, создавать новые коммиты, задачи и запросы на слияние. settings.unarchive.success=Репозиторий был успешно разархивирован. +settings.unarchive.error=Ошибка при попытке разархивировать репозиторий. Смотрите логи для получения подробностей. settings.update_avatar_success=Аватар репозитория обновлен. +settings.lfs=LFS +settings.lfs_findcommits=Найти коммиты +settings.lfs_force_unlock=Принудительная разблокировка +settings.lfs_pointers.sha=Blob SHA +settings.lfs_pointers.oid=OID +settings.lfs_pointers.inRepo=В репозитории diff.browse_source=Просмотр исходного кода diff.parent=Родитель diff.commit=Сommit +diff.git-notes=Заметки diff.data_not_available=Разница недоступна +diff.options_button=Опции Diff +diff.show_diff_stats=Показать статистику +diff.download_patch=Скачать Patch файл +diff.download_diff=Скачать Diff файл diff.show_split_view=Разделённый вид diff.show_unified_view=Единый вид diff.whitespace_button=Пробелы @@ -1271,6 +1409,11 @@ diff.whitespace_ignore_at_eol=Игнорировать изменения в п diff.stats_desc= %d измененных файлов: %d добавлений и %d удалений diff.bin=Двоичные данные diff.view_file=Просмотреть файл +diff.file_before=До +diff.file_after=После +diff.file_image_width=Ширина +diff.file_image_height=Высота +diff.file_byte_size=Размер diff.file_suppressed=Разница между файлами не показана из-за своего большого размера diff.too_many_files=Некоторые файлы не были показаны из-за большого количества измененных файлов diff.comment.placeholder=Оставить комментарий @@ -1284,6 +1427,7 @@ diff.review.header=Отправить рецензию diff.review.placeholder=Рецензионный комментарий diff.review.comment=Комментировать diff.review.approve=Утвердить +diff.review.reject=Запрос изменений releases.desc=Релизы позволяют организовать хранение готовых сборок проекта в строгом хронологически верном порядке. release.releases=Релизы @@ -1335,6 +1479,8 @@ branch.deleted_by=Удалён %s branch.restore_success=Ветка '%s' восстановлена. branch.restore_failed=Не удалось восстановить ветку '%s'. branch.protected_deletion_failed=Ветка '%s' защищена. Её нельзя удалить. +branch.restore=Восстановить ветку '%s' +branch.download=Скачать ветку '%s' topic.manage_topics=Редактировать тематические метки topic.done=Сохранить @@ -1358,6 +1504,7 @@ team_name=Название команды team_desc=Описание team_name_helper=Названия команд должны быть короткими и запоминающимися. team_desc_helper=Что это за команда? +team_access_desc=Доступ к репозиторию team_permission_desc=Разрешение team_unit_desc=Разрешить доступ к разделам репозитория @@ -1370,7 +1517,11 @@ settings.options=Организация settings.full_name=Полное имя settings.website=Сайт settings.location=Местоположение +settings.permission=Разрешения settings.visibility=Видимость +settings.visibility.public=Публичный +settings.visibility.limited=Ограничено (Видно только для авторизованных пользователей) +settings.visibility.private=Частный (Видимый только для участников организации) settings.update_settings=Обновить настройки settings.update_setting_success=Настройки организации обновлены. @@ -1399,6 +1550,7 @@ members.invite_now=Пригласите сейчас teams.join=Объединить teams.leave=Выйти +teams.can_create_org_repo=Создать репозитории teams.read_access=Доступ на чтение teams.read_access_helper=Участники могут просматривать и клонировать командные репозитории. teams.write_access=Доступ на запись @@ -1420,16 +1572,20 @@ teams.write_permission_desc=Эта команда предоставляет д teams.admin_permission_desc=Эта команда дает административный доступ: участники могут читать, пушить и добавлять соавторов к ее репозиториям. teams.repositories=Репозитории группы разработки teams.search_repo_placeholder=Поиск репозитория… +teams.remove_all_repos_title=Удалить все репозитории команды +teams.add_all_repos_title=Добавить все репозитории teams.add_nonexistent_repo=Вы добавляете в отсутствующий репозиторий, пожалуйста сначала его создайте. teams.add_duplicate_users=Пользователь уже состоит в команде. teams.repos.none=Для этой команды нет доступных репозиториев. teams.members.none=В этой команде нет участников. +teams.all_repositories=Все репозитории [admin] dashboard=Панель users=Пользователи organizations=Организации repositories=Репозитории +hooks=Стандартные Веб-хуки authentication=Авторизация config=Конфигурация notices=Системные уведомления @@ -1453,6 +1609,8 @@ dashboard.delete_repo_archives=Удаление всех архивов репо dashboard.delete_repo_archives_success=Все архивы репозиториев удалены. dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git dashboard.delete_missing_repos_success=Все записи о репозиториях с отсутствующими файлами Git удалены. +dashboard.delete_generated_repository_avatars=Удалить генерированные аватары репозитория +dashboard.delete_generated_repository_avatars_success=Генерированные аватары репозитория были удалены. dashboard.git_gc_repos=Выполнить сборку мусора для всех репозиториев dashboard.git_gc_repos_started=Сборка мусора выполнена для всех репозиториев. dashboard.resync_all_sshkeys=Перезаписать файл '.ssh/authorized_keys' для SSH ключей Gitea. Не требуется для встроенного SSH сервера. @@ -1513,6 +1671,7 @@ users.auth_login_name=Логин для авторизации users.password_helper=Оставьте пустым, чтобы оставить без изменений. users.update_profile_success=Профиль учетной записи обновлен успешно. users.edit_account=Изменение учетной записи +users.max_repo_creation=Максимальное количество репозиториев users.max_repo_creation_desc=(Установите -1 для использования стандартного глобального значения предела) users.is_activated=Эта учетная запись активирована users.prohibit_login=Этой учетной записи запрещен вход в систему @@ -1542,6 +1701,8 @@ repos.forks=Форки repos.issues=Задачи repos.size=Размер +hooks.add_webhook=Добавить стандартный Веб-хук +hooks.update_webhook=Обновить стандартный Веб-хук auths.auth_manage_panel=Управление аутентификацией auths.new=Добавить новый источник @@ -1591,6 +1752,14 @@ auths.oauth2_authURL=URL авторизации auths.oauth2_profileURL=URL аккаунта auths.oauth2_emailURL=URL-адрес электронной почты auths.enable_auto_register=Включить автоматическую регистрацию +auths.sspi_auto_create_users=Автоматически создавать пользователей +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_separator_replacement=Разделитель для использования вместо \, / и @ +auths.sspi_default_language=Язык пользователя по умолчанию +auths.sspi_default_language_helper=Язык по умолчанию для пользователей, автоматически создаваемый методом аутентификации SSPI. Оставьте пустым, если вы предпочитаете, чтобы язык определялся автоматически. auths.tips=Советы auths.tips.oauth2.general=OAuth2 аутентификация auths.tips.oauth2.general.tip=При добавлении нового OAuth2 провайдера, URL адрес переадресации по завершении аутентификации должен выглядеть так: /user/oauth2//callback @@ -1604,6 +1773,7 @@ auths.tip.google_plus=Получите учетные данные клиент auths.tip.openid_connect=Используйте OpenID Connect Discovery URL (/.well-known/openid-configuration) для автоматической настройки входа OAuth auths.tip.twitter=Перейдите на https://dev.twitter.com/apps, создайте приложение и убедитесь, что включена опция «Разрешить это приложение для входа в систему с помощью Twitter» auths.tip.discord=Добавьте новое приложение на https://discordapp.com/developers/applications/me +auths.tip.gitea=Зарегистрировать новое приложение OAuth2. Руководство можно найти на https://docs.gitea.io/ru-us/oauth2-provider/ auths.edit=Обновить параметры аутентификации auths.activated=Эта аутентификация активирована auths.new_success=Метод аутентификации '%s' был добавлен. @@ -1615,12 +1785,14 @@ 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=Конфигурация сервера config.app_name=Название сайта config.app_ver=Версия Gitea config.app_url=Базовый URL-адрес Gitea config.custom_conf=Путь к файлу конфигурации +config.custom_file_root_path=Пользовательский путь до папки с файлами config.domain=Домен SSH сервера config.offline_mode=Локальный режим config.disable_router_log=Отключение журнала маршрутизатора @@ -1646,6 +1818,10 @@ config.ssh_keygen_path=Путь к генератору ключей ('ssh-keyge config.ssh_minimum_key_size_check=Минимальный размер ключа проверки config.ssh_minimum_key_sizes=Минимальные размеры ключа +config.lfs_config=Конфигурация LFS +config.lfs_enabled=Включено +config.lfs_content_path=Путь к контенту LFS +config.lfs_http_auth_expiry=Устаревание HTTP-аутентификации LFS config.db_config=Конфигурация базы данных config.db_type=Тип @@ -1667,6 +1843,7 @@ config.mail_notify=Почтовые уведомления config.disable_key_size_check=Отключить проверку на минимальный размер ключа config.enable_captcha=Включить CAPTCHA config.active_code_lives=Время жизни кода для активации +config.reset_password_code_lives=Время действия кода восстановления аккаунта config.default_keep_email_private=Скрывать адреса электронной почты по умолчанию config.default_allow_create_organization=Разрешить создание организаций по умолчанию config.enable_timetracking=Включить отслеживание времени @@ -1701,6 +1878,7 @@ config.cache_config=Настройки кеша config.cache_adapter=Адаптер кэша config.cache_interval=Интервал кэширования config.cache_conn=Подключение кэша +config.cache_item_ttl=Время жизни данных в кеше config.session_config=Конфигурация сессии config.session_provider=Провайдер сессии @@ -1731,6 +1909,16 @@ config.git_gc_timeout=Лимит времени сборки мусора config.log_config=Конфигурация журнала config.log_mode=Режим журналирования +config.macaron_log_mode=Режим журнала Macaron +config.own_named_logger=Именованный журнал +config.routes_to_default_logger=Маршруты в стандартный журнал +config.go_log=Использует Go-журнал (перенаправлено в стандартный) +config.router_log_mode=Режим журнала маршрутизатора +config.disabled_logger=Отключен +config.access_log_mode=Режим доступа к журналу +config.access_log_template=Шаблон +config.xorm_log_mode=Режим журнала XORM +config.xorm_log_sql=Лог SQL monitor.cron=Задачи cron monitor.name=Название @@ -1742,6 +1930,11 @@ monitor.process=Запущенные процессы monitor.desc=Описание monitor.start=Время начала monitor.execute_time=Время выполнения +monitor.process.cancel=Отменить процесс +monitor.process.cancel_desc=Отмена процесса может привести к потере данных +monitor.process.cancel_notices=Отменить: %s? + + notices.system_notice_list=Уведомления системы notices.view_detail_header=Подробности уведомления @@ -1774,6 +1967,7 @@ push_tag=создал(а) тэг %[2]s в %[3]s delete_branch=удалил(а) ветку %[2]s из %[3]s compare_commits=Сравнить %d коммитов +compare_commits_general=Сравнить коммиты mirror_sync_push=синхронизированные коммиты с %[3]s на %[4]s из зеркала mirror_sync_create=синхронизированные новые ссылки %[2]s к %[3]s из зеркала mirror_sync_delete=синхронизированные и удаленные ссылки %[2]s на %[3]s из зеркала @@ -1818,6 +2012,7 @@ mark_as_unread=Пометить как непрочитанное mark_all_as_read=Пометить все как прочитанные [gpg] +default_key=Подписано ключом по умолчанию error.extract_sign=Не удалось извлечь подпись error.generate_hash=Не удается создать хэш коммита error.no_committer_account=Аккаунт пользователя с таким Email не найден diff --git a/options/locale/locale_sr-SP.ini b/options/locale/locale_sr-SP.ini index b4089c32e4f9..5cb79db65bd0 100644 --- a/options/locale/locale_sr-SP.ini +++ b/options/locale/locale_sr-SP.ini @@ -664,6 +664,8 @@ monitor.desc=Опис monitor.start=Почетно време monitor.execute_time=Време извршивања + + notices.system_notice_list=Системска обавештавања notices.actions=Акције notices.select_all=Изабери све diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 337b80115687..516d3296da14 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -1545,6 +1545,8 @@ monitor.desc=Beskrivning monitor.start=Starttid monitor.execute_time=Exekveringstid + + notices.system_notice_list=Systemnotiser notices.view_detail_header=Visa notisdetaljer notices.actions=Åtgärder diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index e975680d8aa8..5940524c71dc 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -25,6 +25,7 @@ password=Parola re_type=Parolayı yeniden yazın captcha=CAPTCHA twofa=İki Aşamalı Doğrulama +twofa_scratch=İki aşamalı kazınmış kod passcode=Şifre u2f_insert_key=Güvenlik anahtarınızı girin @@ -65,6 +66,7 @@ forks=Çatallar activities=Aktiviteler pull_requests=Değişiklik İstekleri issues=Konular +milestones=Kilometre Taşları cancel=İptal add=Ekle @@ -85,6 +87,7 @@ platform_desc=Gitea code.gitea.io/gitea'yı edinin! Bu projeyi daha da iyi yapmak için katkıda bulunarak bize katılın. Katkıda bulunmaktan çekinmeyin! [install] install=Kurulum @@ -122,9 +125,11 @@ run_user_helper=Gitea'nin çalışacağı, işletim sistemi kullanıcı adını domain=SSH Sunucusu Alan Adı domain_helper=SSH kopyalama URL'leri için alan adı veya sunucu adresi. ssh_port=SSH Sunucu Portu +ssh_port_helper=SSH sunucusunun dinleyeceği port numarası. Etkisizleştimek için boş bırakın. http_port=Gitea HTTP Dinleme Portu http_port_helper=Gitea'nın web sunucusunun dinleyeceği port numarası. app_url=Gitea Kök URL +app_url_helper=HTTP(S) kopyalama URL'leri ve e-posta bildirimleri için temel adres. log_root_path=Günlük Dosyaları Yolu log_root_path_helper=Günlük dosyaları bu dizine kaydedilecektir. @@ -139,7 +144,10 @@ register_confirm=Kayıt için E-posta Doğrulaması Gereksin mail_notify=E-Posta Bildirimlerini Etkinleştir server_service_title=Sunucu ve Diğer Servis Ayarları offline_mode=Yerel Kipi Etkinleştir +offline_mode_popup=Üçüncü parti içerik teslim ağlarını etkisizleştirin ve bütün kaynakları yerelden sunun. disable_gravatar=Gravatar'ı Devre Dışı Bırak +disable_gravatar_popup=Gravatar ve üçüncü parti avatar kaynaklarını iptal edin. Kullanıcı bir avatar yüklemediği zaman varsayılan bir avatar kullanılacaktır. +federated_avatar_lookup=Birleştirilmiş Avatarları Etkinleştir federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar. disable_registration=Kendi Kendine Kaydolmayı Devre Dışı Bırak disable_registration_popup=Kullanıcının kendi kendine kaydolmasını devre dışı bırak. Yalnızca yöneticiler yeni hesaplar oluşturabilecek. @@ -237,6 +245,7 @@ verify=Doğrula scratch_code=Çizgi kodu use_scratch_code=Bir çizgi kodu kullanınız twofa_scratch_used=Çizgi kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada cihaz kaydınızı kaldırabilir veya yeni bir çizgi kodu oluşturabilirsiniz. +twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın. twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir. login_userpass=Oturum Aç login_openid=Açık Kimlik @@ -251,6 +260,7 @@ openid_connect_title=Mevcut olan bir hesaba bağlan openid_connect_desc=Seçilen OpenID URI'si bilinmiyor. Burada yeni bir hesapla ilişkilendir. openid_register_title=Yeni hesap oluştur openid_register_desc=Seçilen OpenID URI'si bilinmiyor. Burada yeni bir hesapla ilişkilendir. +openid_signin_desc=OpenID URI'nızı girin. Örneğin: https://anne.me, bob.openid.org.cn veya gnusocial.net/carry. disable_forgot_password_mail=Hesap kurtarma devre dışı. Lütfen site yöneticinizle iletişime geçin. email_domain_blacklisted=Bu e-posta adresinizle kayıt olamazsınız. authorize_application=Uygulamayı Yetkilendir @@ -300,6 +310,7 @@ SSPIDefaultLanguage=Varsayılan Dil require_error=` boş olamaz.` alpha_dash_error=` yalnızca alfasayısal, çizgi ('-') ve alt çizgi ('_') karakterlerini içermelidir. ` alpha_dash_dot_error=` yalnızca alfasayısal, çizgi ('-'), alt çizgi ('_') ve nokta ('.') karakterlerini içermelidir. ` +git_ref_name_error=` git referans ismi iyi oluşturulmuş olmalıdır.` size_error=` uzunluk en fazla %s olmalıdır.` min_size_error=` en az %s karakter içermelidir.` max_size_error=` en fazla %s karakter içermelidir.` @@ -314,6 +325,8 @@ lang_select_error=Listeden bir dil seçin. username_been_taken=Bu kullanıcı adı daha önce alınmış. repo_name_been_taken=Depo adı zaten kullanılıyor. +visit_rate_limit=Uzaktan ziyarette oran sınırlaması ele alındı. +2fa_auth_required=Uzaktan ziyaret için iki faktörlü kimlik doğrulaması gerekli. org_name_been_taken=Organizasyon adı zaten kullanılıyor. team_name_been_taken=Takım adı zaten alınmış. team_no_units_error=En az bir depo bölümüne erişimine izin ver. @@ -394,6 +407,7 @@ cancel=İptal language=Dil ui=Tema +lookup_avatar_by_mail=Avatarı E-posta Adresine Göre Ara federated_avatar_lookup=Birleşmiş Avatar Araması enable_custom_avatar=Özel Avatarı Etkinleştir choose_new_avatar=Yeni Avatar Seç @@ -512,23 +526,45 @@ oauth2_type_native=Yerel (ör. Mobil, Masaüstü, Tarayıcı) oauth2_redirect_uri=Yönlendirme URI'si save_application=Kaydet oauth2_client_id=İstemci Kimliği +oauth2_client_secret=İstemci Gizliliği +oauth2_regenerate_secret=Gizliliği Yeniden Oluştur +oauth2_regenerate_secret_hint=Gizliliğini mi kaybettin? +oauth2_client_secret_hint=Bu sayfayı tekrar ziyaret ederseniz gizlilik görünmez. Lütfen gizliliğinizi kaydedin. oauth2_application_edit=Düzenle +oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar. +oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu durumda yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi? +authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları +authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin. revoke_key=İptal Et revoke_oauth2_grant=Erişimi İptal Et +revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz? revoke_oauth2_grant_success=Erişimi başarıyla iptal ettiniz. +twofa_desc=İki faktörlü kimlik doğrulama, hesabınızın güvenliğini artırır. twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmiş. twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş. +twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak +twofa_scratch_token_regenerate=Kazıma Belirtecini Yenile +twofa_scratch_token_regenerated=Kazıma belirteciniz şimdi %s. Güvenli bir yerde saklayın. +twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun +twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz. +twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi? +regenerate_scratch_token_desc=Karalama belirtecinizi yanlış yerleştirdiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz. twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı. scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın: or_enter_secret=Veya gizli şeyi girin: %s then_enter_passcode=Ve uygulamada gösterilen şifreyi girin: passcode_invalid=Şifre geçersiz. Tekrar deneyin. +twofa_enrolled=Hesabınız iki faktörlü kimlik doğrulamasına kaydedildi. Kazıma belirtecini (%s) yalnızca bir kez gösterdiği gibi güvenli bir yerde saklayın! +u2f_desc=Güvenlik anahtarları, şifreleme anahtarlarını içeren donanım aygıtlarıdır. İki faktörlü kimlik doğrulama için kullanılabilirler. Güvenlik anahtarları FIDO U2F standardını desteklemelidir. +u2f_require_twofa=Güvenlik anahtarlarını kullanmak için hesabınızın iki faktörlü kimlik doğrulamasına kaydedilmiş olması gerekir. u2f_register_key=Güvenlik Anahtarı Ekle u2f_nickname=Takma Ad +u2f_press_button=Kaydetmek için güvenlik anahtarınızdaki düğmeye basın. u2f_delete_key=Güvenlik Anahtarını Sil +u2f_delete_key_desc=Bir güvenlik anahtarını kaldırırsanız, artık onunla giriş yapamazsınız. Devam edilsin mi? manage_account_links=Bağlı Hesapları Yönet manage_account_links_desc=Bu harici hesaplar Gitea hesabınızla bağlantılı. @@ -559,6 +595,7 @@ repo_size=Depo Boyutu template=Şablon template_select=Bir şablon seçin. template_helper=Depoyu şablon yap +template_description=Şablon depoları, kullanıcıların aynı dizin yapısı, dosyaları ve isteğe bağlı ayarlarla yeni depoları oluşturmasına izin verir. visibility=Görünürlük visibility_description=Yalnızca sahibi veya haklara sahip organizasyon üyeleri onu görebilecek. visibility_helper=Depoyu Gizli Yap @@ -584,8 +621,12 @@ auto_init=Depoyu başlat (.gitignore, Lisans ve README dosyalarını ekler) create_repo=Depo Oluştur default_branch=Varsayılan Dal mirror_prune=Buda +mirror_prune_desc=Kullanılmayan uzak depoları izleyen referansları kaldır +mirror_interval=Yansı Aralığı (geçerli zaman birimleri 'h', 'm', 's'). 0 otomatik senkronizasyonu devre dışı bırakmak için. mirror_interval_invalid=Yansı süre aralığı geçerli değil. mirror_address=URL'den Klonla +mirror_address_desc=Gerekli kimlikleri Yetkilendirmeyi Klonla bölümüne girin. +mirror_address_url_invalid=Sağlanan Url geçersiz. Url'nin tüm bileşenlerinden doğru olarak kaçmalısınız. mirror_address_protocol_invalid=Sağlanan url geçersiz. Yalnızca http(s):// veya git:// konumları yansıtılabilir. mirror_last_synced=Son Senkronize Edilen watchers=İzleyenler @@ -597,7 +638,9 @@ reactions_more=ve %d daha fazla template.items=Şablon Öğeleri template.git_content=Git İçeriği (Varsayılan Dal) template.git_hooks=Git İstekleri +template.git_hooks_tooltip=Şu anda bir kez eklenen git isteklerini değiştirmek veya kaldırmak mümkün değildir. Bunu yalnızca şablon deposuna güveniyorsanız seçin. template.webhooks=Web İstekleri +template.topics=Konular template.avatar=Profil Resmi template.issue_labels=Konu Etiketleri template.one_item=En az bir şablon öğesi seçmelisiniz @@ -700,6 +743,7 @@ editor.delete_this_file=Dosyayı Sil editor.must_have_write_access=Bu dosyada değişiklikler yapmak veya önermek için yazma erişiminizin olması gerekir. editor.file_delete_success='%s' dosyası silindi. editor.name_your_file=Dosyanızı isimlendirin… +editor.filename_help=Bölü ('/') işaretiyle ismini yazarak bir dizin ekleyebilirsiniz. Dizini silmek için girdi sahasının başına backspace yazmalısınız. editor.or=veya editor.cancel_lower=İptal editor.commit_changes=Değişiklikleri Uygula @@ -739,6 +783,7 @@ commits.desc=Kaynak kodu değişiklik geçmişine göz atın. commits.commits=İşlemeler commits.no_commits=Ortak bir işleme yok. '%s' ve '%s' tamamen farklı geçmişlere sahip. commits.search=İşlemeleri ara… +commits.search.tooltip=Anahtar kelimeleri "yazar:", "yorumcu:", "sonra:" veya "önce:", örneğin; "eski haline yazan: Alice önce: 2019-04-01" önekleyebilirsiniz. commits.find=Ara commits.search_all=Tüm Dallar commits.author=Yazar @@ -837,6 +882,7 @@ issues.num_comments=%d yorum issues.commented_at=`%s yorum yaptı` issues.delete_comment_confirm=Bu yorumu silmek istediğinizden emin misiniz? issues.context.copy_link=Bağlantıyı Kopyala +issues.context.quote_reply=Alıntı Cevapla issues.context.edit=Düzenle issues.context.delete=Sil issues.no_content=Henüz bir içerik yok. @@ -848,6 +894,7 @@ issues.create_comment=Yorum yap issues.closed_at=`%[2]s kapattı` issues.reopened_at=`%[2]s yeniden açtı` issues.commit_ref_at=`%[2]s işlemesinde bu konuyu işaret etti` +issues.ref_from=`%[1]s'den` issues.poster=Poster issues.collaborator=Katkıcı issues.owner=Sahibi @@ -906,6 +953,7 @@ issues.add_time=El ile Zaman Ekle issues.add_time_short=Zaman Ekle issues.add_time_cancel=İptal issues.add_time_history=`%s harcanan zaman eklendi` +issues.del_time_history=`%s harcanan zaman silindi` issues.add_time_hours=Saat issues.add_time_minutes=Dakika issues.add_time_sum_to_small=Zaman girilmedi. @@ -972,13 +1020,16 @@ pulls.desc=Değişiklik isteklerini ve kod incelemelerini etkinleştir. pulls.new=Yeni Değişiklik İsteği pulls.compare_changes=Yeni Değişiklik İsteği pulls.compare_changes_desc=Birleştirmek için hedef ve kaynak dalı seçin. +pulls.compare_base=birleştir pulls.compare_compare=şuradan çek pulls.filter_branch=Dal filtrele pulls.no_results=Sonuç bulunamadı. pulls.nothing_to_compare=Bu dallar eşit. Değişiklik isteği oluşturmaya gerek yok. pulls.has_pull_request=`Bu dallar arasında bir değişiklik isteği zaten var: %[2]s#%[3]d` pulls.create=Değişiklik İsteği Oluştur +pulls.title_desc=%[3]s içindeki %[2]s işlemelerini %[1]d ile birleştirmek istiyor pulls.merged_title_desc=%[4]s %[2]s içindeki %[1]d işleme %[3]s ile birleştirdi +pulls.change_target_branch_at='hedef dal %s adresinden %s%s adresine değiştirildi' pulls.tab_conversation=Sohbet pulls.tab_commits=İşlemeler pulls.tab_files=Değiştirilen Dosyalar @@ -986,6 +1037,7 @@ pulls.reopen_to_merge=Bir birleşim uygulamak için lütfen bu çekme isteğini pulls.cant_reopen_deleted_branch=Dal silindiğinden bu değişiklik isteği yeniden açılamaz. pulls.merged=Birleştirildi pulls.merged_as=Değişiklik isteği %[2]s olarak birleştirildi. +pulls.is_closed=Değişiklik isteği kapatıldı. pulls.has_merged=Değişiklik isteği birleştirildi. pulls.title_wip_desc=`Değişiklik isteğinin yanlışlıkla birleştirilmesini önlemek için, başlığı %s ile başlatın` pulls.cannot_merge_work_in_progress=Bu değişiklik isteği devam eden bir çalışma olarak işaretlendi. Hazır olduğunda %s ön ekini başlıktan kaldırın @@ -995,6 +1047,7 @@ pulls.is_checking=Birleştirme çakışması denetimi devam ediyor. Birkaç daki pulls.required_status_check_failed=Bazı gerekli denetimler başarılı olmadı. pulls.required_status_check_administrator=Yönetici olarak, bu değişiklik isteğini yine de birleştirebilirsiniz. pulls.blocked_by_approvals=Bu değişiklik isteği henüz onaylanmadı. %[2]d isteğin %[1]d onayı verildi. +pulls.blocked_by_rejection=Bu Değişiklik İsteğinde, resmi bir inceleyeci tarafından istenen değişiklikler var. pulls.can_auto_merge_desc=Bu değişiklik isteği otomatik olarak birleştirilebilir. pulls.cannot_auto_merge_desc=Bu değişiklik isteği, çakışmalar nedeniyle otomatik olarak birleştirilemiyor. pulls.cannot_auto_merge_helper=Çakışmaları çözmek için el ile birleştirin. @@ -1007,6 +1060,10 @@ pulls.rebase_merge_pull_request=Rebase ve Merge pulls.rebase_merge_commit_pull_request=Rebase ve Merge (--no-ff) pulls.squash_merge_pull_request=Squash ve Merge pulls.invalid_merge_option=Bu değişiklik isteği için bu birleştirme seçeneğini kullanamazsınız. +pulls.merge_conflict=Birleştirme Başarısız: Birleşirken bir çakışma oluştu: %[1]s
%[2]s
İpucu: Farklı bir strateji deneyin +pulls.rebase_conflict=Birleştirme Başarısız: Yeniden işleme yaparken bir çakışma vardı: %[1]s
%[2]s
%[3]s
İpucu: Farklı bir strateji deneyin +pulls.unrelated_histories=Birleştirme Başarısız: Birleştirme başlığı ve tabanı ortak bir geçmişi paylaşmıyor. İpucu: Farklı bir strateji deneyin +pulls.merge_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, taban güncellendi. İpucu: Tekrar deneyin. pulls.open_unmerged_pull_exists=`Aynı özelliklere sahip bekleyen bir çekme isteği (#%d) olduğundan yeniden açma işlemini gerçekleştiremezsiniz.` pulls.status_checking=Bazı denetlemeler beklemede pulls.status_checks_success=Tüm denetlemeler başarılı oldu @@ -1167,18 +1224,26 @@ settings.use_external_wiki=Harici Wiki Kullan settings.external_wiki_url=Harici Wiki bağlantısı settings.external_wiki_url_error=Harici wiki URL'si geçerli bir URL değil. settings.external_wiki_url_desc=Ziyaretçiler, wiki sekmesine tıklandığında harici wiki URL'sine yönlendirilir. +settings.issues_desc=Depo Sorun İzleyicisini Etkinleştir +settings.use_internal_issue_tracker=Yerleşik Sorun İzleyiciyi Kullan +settings.use_external_issue_tracker=Harici Sorun İzleyici Kullan settings.external_tracker_url=Harici Konu İzleyici URLsi +settings.external_tracker_url_error=Harici sorun izleyici URL'si geçerli bir URL değil. settings.external_tracker_url_desc=Ziyaretçiler, konular sekmesine tıklandığında harici konu izleyici URL'sine yönlendirilir. settings.tracker_url_format=Harici Sorun Takipçisi Bağlantı Formatı settings.tracker_url_format_error=Harici konu izleyici URL biçimi geçerli bir URL değil. settings.tracker_issue_style=Harici Konu İzleyici Numara Biçimi settings.tracker_issue_style.numeric=Sayısal settings.tracker_issue_style.alphanumeric=Alfanumerik +settings.tracker_url_format_desc=Kullanıcı adı, depo adı ve yayın dizini için {user}, {repo} ve {index} yer tutucularını kullanın. settings.enable_timetracker=Zaman Takibini Etkinleştir settings.allow_only_contributors_to_track_time=Sadece Katkıcılar İçin Zaman Takibine İzin Ver settings.pulls_desc=Değişiklik İsteklerini Etkinleştir settings.pulls.ignore_whitespace=Çakışmalar için Boşlukları Gözardı Et settings.pulls.allow_merge_commits=İşleme Birleştirmeyi Etkinleştir +settings.pulls.allow_rebase_merge=İşlemeleri Birleştirmek için Yeniden Yapılandırmayı Etkinleştir +settings.pulls.allow_rebase_merge_commit=Açık birleştirme işlemeleri ile Yeniden Yapılandırmayı Etkinleştir (--no-ff) +settings.pulls.allow_squash_commits=İşlemeleri Birleştirmek için Sıkmayı Etkinleştir settings.admin_settings=Yönetici Ayarları settings.admin_enable_health_check=Depo Sağlık Kontrollerini Etkinleştir (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Varsayılan olmayan bir dalda yapılan bir işlemeyle konuyu kapat @@ -1192,6 +1257,7 @@ settings.convert_succeed=Yansı normal bir depoya dönüştürüldü. settings.transfer=Sahipliği Aktar settings.transfer_desc=Bu depoyu bir kullanıcıya veya yönetici haklarına sahip olduğunuz bir organizasyona aktarın. settings.transfer_notices_1=- Bireysel bir kullanıcıya aktarırsanız depoya erişiminizi kaybedersiniz. +settings.transfer_notices_2=- Sahip (-yardımcı) olduğunuz bir kuruluşa devrederseniz, depoya erişmeye devam edersiniz. settings.transfer_form_title=Onaylamak için depo adını girin: settings.wiki_delete=Wiki Verisini Sil settings.wiki_delete_desc=Depo wiki verilerini silmek kalıcıdır ve geri alınamaz. @@ -1201,6 +1267,7 @@ settings.wiki_deletion_success=Depo wiki verisi silindi. settings.delete=Bu Depoyu Sil settings.delete_desc=Bir depoyu silmek kalıcıdır ve geri alınamaz. settings.delete_notices_1=- Bu işlem geri ALINAMAZ. +settings.delete_notices_2=- Bu işlem, kod, sorunlar, yorumlar, wiki verileri ve katkıcı ayarları dahil olmak üzere %s deposunu kalıcı olarak siler. settings.delete_notices_fork_1=- Silme işleminden sonra bu deponun çatalları bağımsız hale gelecektir. settings.deletion_success=Depo silindi. settings.update_settings_success=Depo ayarları güncellendi. @@ -1273,6 +1340,7 @@ settings.event_pull_request_desc=Değişiklik isteği açıldığında, kapatıl settings.event_push=Çek settings.event_push_desc=Depo ittirildiğinde. settings.branch_filter=Dal filtresi +settings.branch_filter_desc=Glob deseni olarak belirtilen itme, dal oluşturma ve dal silme olayları için dal beyaz listesi. Boş veya * ise, tüm dallar için olaylar bildirilir. Sözdizimi için github.com/gobwas/glob belgelerine bakın. Örnekler: master, {master,release*}. settings.event_repository=Depo settings.event_repository_desc=Depo oluşturuldu veya silindi. settings.active=Etkin @@ -1312,6 +1380,14 @@ settings.protected_branch_can_push_yes=İtebilirsiniz settings.protected_branch_can_push_no=İtemezsiniz settings.branch_protection=%s dalı için Dal Koruması settings.protect_this_branch=Dal Korumayı Etkinleştir +settings.protect_this_branch_desc=Silmeyi önler ve dala Git itmesini ve birleştirmesini kısıtlar. +settings.protect_disable_push=İtmeyi Devre Dışı Bırak +settings.protect_disable_push_desc=Bu dala itme yapılmasına izin verilmeyecek. +settings.protect_enable_push=İtmeyi Etkinleştir +settings.protect_enable_push_desc=Yazma erişimi olan herkesin bu dala itmesine izin verilir (ancak zorla itmeyin). +settings.protect_whitelist_committers=Beyaz Liste Sınırlı İtme +settings.protect_whitelist_committers_desc=Sadece beyaz listeye alınmış kullanıcıların veya takımların bu dala itmesine izin verilir (ancak zorla itmeyin). +settings.protect_whitelist_deploy_keys=Beyaz liste itmek için yazma erişimli anahtarları dağıtır settings.protect_whitelist_users=İtme için beyaz listedeki kullanıcılar: settings.protect_whitelist_search_users=Kullanıcı ara… settings.protect_whitelist_teams=İtme için beyaz listedeki takımlar: @@ -1321,8 +1397,12 @@ settings.protect_merge_whitelist_committers_desc=Yalnızca beyaz listedeki kulla settings.protect_merge_whitelist_users=Birleştirme için beyaz listedeki kullanıcılar: settings.protect_merge_whitelist_teams=Birleştirme için beyaz listedeki takımlar: settings.protect_check_status_contexts=Durum Denetimini Etkinleştir +settings.protect_check_status_contexts_desc=Birleşmeden önce durum denetimlerinin geçmesini isteyin Dallar bu kuralla eşleşen bir dalda birleştirilmeden önce hangi durum denetimlerinin geçmesi gerektiğini seçin. Etkinleştirildiğinde, işlemelerin önce başka bir dala itilmesi, ardından durum kontrolleri geçtikten sonra doğrudan bu kuralla eşleşen bir dala birleştirilmesi veya itilmesi gerekir. Hiçbir bağlam seçilmezse, bağlam ne olursa olsun son işlem başarılı olmalıdır. settings.protect_check_status_contexts_list=Bu depo için geçen haftadaki durum denetimleri settings.protect_required_approvals=Gerekli onaylar: +settings.protect_required_approvals_desc=Değişiklik isteğini yalnızca yeterince olumlu yorumla birleştirmeye izin ver. +settings.protect_approvals_whitelist_enabled=Onayları beyaz listeye giren kullanıcılar veya takımlar için kısıtla +settings.protect_approvals_whitelist_enabled_desc=Yalnızca beyaz listedeki kullanıcıların veya ekiplerin incelemeleri gerekli onaylar için dikkate alınır. Onaylı beyaz liste olmadan, yazma erişimi olan herkesin incelemeleri gerekli onaylar için dikkate alınır. settings.protect_approvals_whitelist_users=Beyaz listedeki incelemeciler: settings.protect_approvals_whitelist_teams=Gözden geçirme için beyaz listedeki takımlar: settings.add_protected_branch=Korumayı etkinleştir @@ -1331,6 +1411,8 @@ settings.update_protect_branch_success='%s' dalı için dal koruması güncellen settings.remove_protected_branch_success='%s' dalı için dal koruması devre dışı bırakıldı. settings.protected_branch_deletion=Dal Korumasını Devre Dışı Bırak settings.protected_branch_deletion_desc=Dal korumasını devre dışı bırakmak, kullanıcıların dalı itmek için yazma izni olmasını sağlar. Devam edilsin mi? +settings.block_rejected_reviews=Reddedilen incelemelerde birleştirmeyi engelle +settings.block_rejected_reviews_desc=Yeterli onay olsa bile resmi inceleyiciler tarafından değişiklik istendiğinde birleşme mümkün olmayacaktır. settings.default_branch_desc=Değişiklik istekleri ve kod işlemeleri için varsayılan bir depo dalı seçin: settings.choose_branch=Bir dal seç… settings.no_protected_branch=Korumalı dal yok. @@ -1356,12 +1438,26 @@ settings.lfs_filelist=Bu depoda barındırılan LFS dosyaları settings.lfs_no_lfs_files=Bu depoda barındırılan herhangi bir LFS dosyası yok settings.lfs_findcommits=İşleme bul settings.lfs_lfs_file_no_commits=Bu LFS dosyası için hiçbir işleme bulunamadı +settings.lfs_noattribute=Bu yol, varsayılan dalda kilitlenebilir özelliğe sahip değil +settings.lfs_delete=OID %s ile LFS dosyasını sil +settings.lfs_delete_warning=Bir LFS dosyasını silmek, ödeme sırasında 'nesne yok' hatalarına neden olabilir. Emin misiniz? settings.lfs_findpointerfiles=İşaretçi dosyalarını bul +settings.lfs_locks=Kilitler +settings.lfs_invalid_locking_path=Geçersiz yol: %s +settings.lfs_invalid_lock_directory=Dizin kilitlenemiyor: %s +settings.lfs_lock_already_exists=Kilit zaten var: %s +settings.lfs_lock=Kilitle +settings.lfs_lock_path=Kilitlenecek dosya yolu... +settings.lfs_locks_no_locks=Kilit yok +settings.lfs_lock_file_no_exist=Kilitli dosya varsayılan dalda mevcut değil +settings.lfs_force_unlock=Kilidi Açmaya Zorla +settings.lfs_pointers.found=Bulunan %d blob işaretçi(leri) - %d ilişkili, %d ilişkilendirilmemiş (%d mağazadan eksik) settings.lfs_pointers.sha=Blob SHA settings.lfs_pointers.oid=OID settings.lfs_pointers.inRepo=Depoda settings.lfs_pointers.exists=Mağazada var settings.lfs_pointers.accessible=Kullanıcı tarafından erişilebilir +settings.lfs_pointers.associateAccessible=Erişilebilir %d OID ilişkilendirme diff.browse_source=Kaynağa Gözat diff.parent=ebeveyn @@ -1455,8 +1551,12 @@ branch.protected_deletion_failed='%s' dalı korunuyor. Silinemez. branch.restore='%s' Dalını Geri Yükle branch.download='%s' Dalını İndir branch.included_desc=Bu dal varsayılan dalın bir parçasıdır +branch.included=Dahil +topic.manage_topics=Konuları Yönet topic.done=Bitti +topic.count_prompt=25'ten fazla konu seçemezsiniz +topic.format_prompt=Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir. [org] org_name_holder=Organizasyon Adı @@ -1522,6 +1622,8 @@ members.invite_now=Şimdi Davet Et teams.join=Katıl teams.leave=Ayrıl +teams.can_create_org_repo=Depoları oluştur +teams.can_create_org_repo_helper=Üyeler organizasyonda yeni depolar oluşturabilirler. Oluşturan yeni depoya yönetici erişimi sağlayacak. teams.read_access=Okuma Erişimi teams.read_access_helper=Üyeler, takım depolarını görüntüleyebilir ve klonlayabilir. teams.write_access=Yazma Erişimi @@ -1541,6 +1643,7 @@ teams.delete_team_success=Takım silindi. teams.read_permission_desc=Bu takım Okuma erişimi veriyor. Üyeler takım depolarını görüntüleyebilir ve klonlayabilir. teams.write_permission_desc=Bu takım Yazma erişimi veriyor. Üyeler takım depolarından okuyabilir ve takım depolarına itme yapabilir. teams.admin_permission_desc=Bu takım Yönetici erişimi veriyor. Üyeler takım depolarını okuyabilir, itebilir ve katkıcı ekleyebilir. +teams.create_repo_permission_desc=Ayrıca, bu takım Depo oluşturma izni verir: üyeler organizasyonda yeni depolar oluşturabilir. teams.repositories=Ekip Depoları teams.search_repo_placeholder=Depo ara… teams.remove_all_repos_title=Tüm takım depolarını kaldır @@ -1552,7 +1655,12 @@ teams.add_duplicate_users=Kullanıcı zaten takımın üyesi. teams.repos.none=Bu takım tarafından hiçbir depoya erişilemedi. teams.members.none=Bu takımda üye yok. teams.specific_repositories=Belirli depolar +teams.specific_repositories_helper=Üyeler, yalnızca takıma açıkça eklenen depolara erişebilir. Bunu seçmek, Tüm depolarla zaten eklenmiş olan depoları otomatik olarak kaldırmaz. teams.all_repositories=Tüm depolar +teams.all_repositories_helper=Takımın tüm depolara erişimi vardır. Bunu seçmek, mevcut tüm depoları takıma ekleyecektir. +teams.all_repositories_read_permission_desc=Bu takım tüm depolara Okuma erişimi sağlar: üyeler depoları görüntüleyebilir ve kopyalayabilir. +teams.all_repositories_write_permission_desc=Bu takım tüm depolara Yazma erişimi sağlar: üyeler depolardan okuyabilir ve depolara itebilir. +teams.all_repositories_admin_permission_desc=Bu ekip tüm depolara Yönetici erişimi sağlar: üyeler depolardan okuyabilir, itebilir ve katkıcıları ekleyebilir. [admin] dashboard=Pano @@ -1560,6 +1668,7 @@ users=Kullanıcı Hesapları organizations=Organizasyonlar repositories=Depolar hooks=Varsayılan Web İstemcileri +authentication=Yetkilendirme Kaynakları config=Yapılandırma notices=Sistem Bildirimler monitor=İzleme @@ -1586,7 +1695,10 @@ dashboard.delete_generated_repository_avatars=Oluşturulan depo resimlerini sil dashboard.delete_generated_repository_avatars_success=Oluşturulan depo resimleri silindi. dashboard.git_gc_repos=Depolardaki çöpleri topla dashboard.git_gc_repos_success=Tüm depolardaki çöp toplama işlemi bitti. +dashboard.resync_all_sshkeys=Gitea SSH anahtarları ile '.ssh/authorized_keys' dosyasını güncelleyin. (Dahili SSH sunucusu için gerekli değildir.) dashboard.resync_all_sshkeys_success=Gitea tarafından kontrol edilen açık SSH anahtarları güncellendi. +dashboard.resync_all_hooks=Tüm depoların alma öncesi, güncelleme ve alma sonrası kancalarını yeniden senkronize edin. +dashboard.resync_all_hooks_success=Tüm depoların alım öncesi, güncelleme ve alım sonrası kancaları yeniden senkronize edildi. dashboard.reinit_missing_repos=Kayıtları bulunanlar için tüm eksik Git depolarını yeniden başlat dashboard.reinit_missing_repos_success=Kayıtları bulunanlar için tüm eksik Git depoları yeniden başlatıldı. dashboard.sync_external_users=Harici kullanıcı verisini senkronize et @@ -1637,6 +1749,7 @@ users.new_success='%s' kullanıcı hesabı oluşturuldu. users.edit=Düzenle users.auth_source=Yetkilendirme Kaynağı users.local=Yerel +users.auth_login_name=Oturum Açma Adı Doğrulaması users.password_helper=Şifreyi değiştirmemek için boş bırakın. users.update_profile_success=Kullanıcı hesabı güncellendi. users.edit_account=Kullanıcı Hesabını Düzenle @@ -1646,6 +1759,7 @@ users.is_activated=Kullanıcı Hesabı Etkinleştirildi users.prohibit_login=Oturum Açmayı Devre Dışı Bırak users.is_admin=Yöneticidir users.allow_git_hook=Git İstemcileri Oluşturabilir +users.allow_import_local=Yerel Depoları Alabilir users.allow_create_organization=Organizasyon Oluşturabilir users.update_profile=Kullanıcı Hesabını Güncelle users.delete_account=Kullanıcı Hesabını Sil @@ -1669,6 +1783,7 @@ repos.forks=Çatallar repos.issues=Konular repos.size=Boyut +hooks.desc=Web İstemcileri, belirli Gitea olayları tetiklendiğinde otomatik olarak HTTP POST isteklerini sunucuya yapar. Burada tanımlanan Web İstemcileri varsayılandır ve tüm yeni depolara kopyalanır. web istemcileri kılavuzunda daha fazla bilgi edinin. hooks.add_webhook=Varsayılan Web İstemcisi Ekle hooks.update_webhook=Varsayılan Web İstemcisini Güncelle @@ -1690,11 +1805,13 @@ auths.bind_password=Bağlama Parolası auths.bind_password_helper=Uyarı: Bu parola düz metin olarak saklanır. Mümkünse salt okunur bir hesap kullanın. auths.user_base=Kullanıcı Arama Tabanı auths.user_dn=Kullanıcı DN'i +auths.attribute_username=Kullanıcı Adı Özelliği auths.attribute_username_placeholder=Gitea'da girilen kullanıcı adını kullanmak için boş bırakın. auths.attribute_name=Ad Özelliği auths.attribute_surname=Soyad Özelliği auths.attribute_mail=E-posta Özelliği auths.attribute_ssh_public_key=Açık SSH Anahtarı Özelliği +auths.attributes_in_bind=Bağlı DN tabanındaki özellikleri çek auths.use_paged_search=Sayfalı Aramayı Kullan auths.search_page_size=Sayfa Boyutu auths.filter=Kullanıcı Filtresi @@ -1710,6 +1827,9 @@ auths.skip_tls_verify=TLS Doğrulamasını Atla auths.pam_service_name=PAM Servis Adı auths.oauth2_provider=OAuth2 Sağlayıcısı auths.oauth2_clientID=İstemci Kimliği (Anahtar) +auths.oauth2_clientSecret=İstemci Gizliliği +auths.openIdConnectAutoDiscoveryURL=OpenID Connect Otomatik Keşif URL’si +auths.oauth2_use_custom_url=Varsayılan URL'ler Yerine Özel URL'ler Kullanın auths.oauth2_tokenURL=Jeton URL auths.oauth2_authURL=Yetkilendirme URL'si auths.oauth2_profileURL=Profil URL’si @@ -1720,16 +1840,22 @@ auths.sspi_auto_create_users_helper=SSPI kimlik doğrulama yönteminin ilk kez o auths.sspi_auto_activate_users=Kullanıcıları otomatik olarak etkinleştir auths.sspi_auto_activate_users_helper=SSPI kimlik doğrulama yönteminin yeni kullanıcıları otomatik olarak etkinleştirmesine izin ver auths.sspi_strip_domain_names=Alan adlarını kullanıcı adlarından kaldır +auths.sspi_strip_domain_names_helper=İşaretlenirse, etki alanı adları oturum açma adlarından kaldırılacaktır (örn. "ETKİALANI\kullanıcı" ve " kullanıcı@örnek.org "her ikisi de sadece "kullanıcı" olacak). auths.sspi_separator_replacement=\, / ve @ yerine kullanılacak ayırıcı +auths.sspi_separator_replacement_helper=Alt seviye oturum açma adlarının (örneğin, "ETKİALANI\kullanıcı" içindeki \) ve kullanıcı asıl adlarının (örneğin, "kullanıcı@örnek.org" daki @) ayırıcılarını değiştirmek için kullanılacak karakter. auths.sspi_default_language=Varsayılan kullanıcı dili +auths.sspi_default_language_helper=SSPI kimlik doğrulama yöntemi tarafından otomatik olarak oluşturulan kullanıcılar için varsayılan dil. Dili otomatik olarak algılamayı tercih ederseniz boş bırakın. auths.tips=İpuçları auths.tips.oauth2.general=OAuth2 Kimlik Doğrulama +auths.tips.oauth2.general.tip=Yeni bir OAuth2 kimlik doğrulaması kaydederken, geri arama/yönlendirme URL'si şöyle olmalıdır: /kullanıcı/oauth2//callback auths.tip.oauth2_provider=OAuth2 Sağlayıcısı auths.tip.bitbucket=https://bitbucket.org/account/user//oauth-consumers/new adında yeni bir OAuth tüketicisi kaydedin ve 'Hesap' - 'Oku' iznini ekleyin auths.tip.dropbox=https://www.dropbox.com/developers/apps adresinde yeni bir uygulama oluştur auths.tip.facebook=https://developers.facebook.com/apps adresinde yeni bir uygulama kaydedin ve "Facebook Giriş" ürününü ekleyin auths.tip.github=https://github.com/settings/applications/new adresinde yeni bir OAuth uygulaması kaydedin auths.tip.gitlab=https://gitlab.com/profile/applications adresinde yeni bir uygulama kaydedin +auths.tip.google_plus=OAuth2 istemci kimlik bilgilerini https://console.developers.google.com/ adresindeki Google API konsolundan edinin +auths.tip.openid_connect=Bitiş noktalarını belirlemek için OpenID Connect Discovery URL'sini kullanın (/.well-known/openid-configuration) auths.tip.twitter=https://dev.twitter.com/apps adresine gidin, bir uygulama oluşturun ve “Bu uygulamanın Twitter ile oturum açmak için kullanılmasına izin ver” seçeneğinin etkin olduğundan emin olun auths.tip.discord=https://discordapp.com/developers/applications/me adresinde yeni bir uygulama kaydedin auths.tip.gitea=Yeni bir OAuth2 uygulaması kaydedin. Rehber https://docs.gitea.io/en-us/oauth2-provider/ adresinde bulunabilir @@ -1749,7 +1875,10 @@ auths.login_source_of_type_exist=Bu tür bir kimlik doğrulama kaynağı zaten v config.server_config=Sunucu Yapılandırması config.app_name=Site Başlığı config.app_ver=Gitea Sürümü +config.app_url=Gitea Taban URL'si config.custom_conf=Yapılandırma Dosyası Yolu +config.custom_file_root_path=Özel Dosya Kök Yolu +config.domain=SSH Sunucusu Etki Alanı config.offline_mode=Yerel Kip config.disable_router_log=Yönlendirici Log'larını Devre Dışı Bırak config.run_user=Şu Kullanıcı Olarak Çalıştır @@ -1777,6 +1906,7 @@ config.ssh_minimum_key_sizes=Minimum Anahtar Uzunlukları config.lfs_config=LFS Yapılandırması config.lfs_enabled=Etkin config.lfs_content_path=LFS İçerik Yolu +config.lfs_http_auth_expiry=LFS HTTP Yetkilendirme Süresi config.db_config=Veritabanı Yapılandırması config.db_type=Türü @@ -1790,13 +1920,21 @@ config.service_config=Servis Yapılandırması config.register_email_confirm=Kayıt Olmak İçin E-posta Onayı Gereksin config.disable_register=Kullanıcı Kaydını Devre Dışı Bırak config.allow_only_external_registration=Sadece Dış Hizmetler Aracılığıyla Kullanıcı Kaydına İzin Ver +config.enable_openid_signup=OpenID Kendinden Kaydı'nı Etkinleştir +config.enable_openid_signin=OpenID Oturum Açmayı Etkinleştiriniz config.show_registration_button=Kaydolma Tuşunu Göster config.require_sign_in_view=Sayfaları Görüntülemek için Giriş Yapmaya Zorla config.mail_notify=E-Posta Bildirimlerini Etkinleştir config.disable_key_size_check=Minimum Anahtar Uzunluğu Kontrolünü Devre Dışı Bırak config.enable_captcha=CAPTCHA'yı Etkinleştir config.active_code_lives=Kod Yaşamlarını Aktifleştir +config.reset_password_code_lives=Hesap Kodunun Sona Erme Zamanını Kurtar config.default_keep_email_private=E-posta Adreslerini Varsayılan Olarak Gizle +config.default_allow_create_organization=Varsayılan Olarak Kuruluşların Oluşturulmasına İzin Ver +config.enable_timetracking=Zaman İzlemeyi Etkinleştir +config.default_enable_timetracking=Varsayılan Olarak Zaman İzlemeyi Etkinleştir +config.default_allow_only_contributors_to_track_time=Yalnızca Katkıda Bulunanlar Zaman İzlesin +config.no_reply_address=Gizlenmiş E-Postalar için Alan Adı config.default_visibility_organization=Yeni organizasyonlar için varsayılan görünürlük config.default_enable_dependencies=Konu Bağımlılıklarını Varsayılan Olarak Etkinleştir @@ -1825,6 +1963,7 @@ config.cache_config=Önbellek Yapılandırması config.cache_adapter=Önbellek Uyarlayıcısı config.cache_interval=Önbellek Aralığı config.cache_conn=Önbellek Bağlantısı +config.cache_item_ttl=TTL Önbellek Öğesi config.session_config=Oturum Yapılandırması config.session_provider=Oturum Sağlayıcı @@ -1839,6 +1978,7 @@ config.cookie_life_time=Çerez Yaşam Zamanı config.picture_config=Resim ve Avatar Yapılandırması config.picture_service=Resim Servisi config.disable_gravatar=Gravatar Hizmet Dışı +config.enable_federated_avatar=Birleştirilmiş Avatarları Etkinleştir config.git_config=Git Yapılandırması config.git_disable_diff_highlight=Değişiklik Sözdizimi Vurgusunu Devre Dışı Bırak @@ -1879,6 +2019,8 @@ monitor.process.cancel=İşlemi iptal et monitor.process.cancel_desc=Bir işlemi iptal etmek veri kaybına neden olabilir monitor.process.cancel_notices=İptal et: %s? + + notices.system_notice_list=Sistem Bildirimleri notices.view_detail_header=Bildirim Ayrıntılarını Görüntüle notices.actions=İşlemler @@ -1904,6 +2046,7 @@ create_pull_request=`%s#%[2]s değişiklik isteğini o close_pull_request=`%s#%[2]s değişiklik isteğini kapattı` reopen_pull_request=`%s#%[2]s değişiklik isteğini tekrar açtı` comment_issue=`%s#%[2]s konusuna yorum yazdı` +comment_pull=`%s#%[2]s değişiklik isteği üzerinde yorum yapıldı` merge_pull_request=`%s#%[2]s değişim isteğini birleştirdi` transfer_repo=depo %s %s'a aktarıldı push_tag=%[3]s deposuna %[2]s etiketi itildi diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index bd055308987b..7ca21ed0904b 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -66,14 +66,28 @@ forks=Форки activities=Дії pull_requests=Запити на злиття issues=Проблеми +milestones=Етапи cancel=Відмінити +add=Додати +add_all=Додати все +remove=Видалити +remove_all=Видалити все write=Писати preview=Попередній перегляд loading=Завантаження… [startpage] +app_desc=Зручний власний сервіс хостингу репозиторіїв Git +install=Легко встановити +install_desc=Достатньо запустити виконуваний файл для вашої платформи. Або розмістіть Gitea в Docker чи Vagrant, чи просто встановіть пакунок. +platform=Платформонезалежність +platform_desc=Gitea виконується на платформі, для якої можливо скомпілювати Go: Windows, macOS, Linux, ARM, та інших. Оберіть ту, яка вам до вподоби! +lightweight=Невибагливість +lightweight_desc=Gitea має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Збережіть свою машину енергію! +license=Відкритий вихідний код +license_desc=Відвідайте code.gitea.io/gitea! Приєднайтесь до нас та зробіть свій внесок до проєкту, щоб зробити його ще краще. Не бійтеся долучитися! [install] install=Встановлення @@ -86,12 +100,18 @@ host=Хост user=Ім'я кристувача password=Пароль db_name=Ім'я бази даних +db_helper=Примітка для користувачів MySQL: будь-ласка використовуйте механізм зберігання InnoDB, і якщо ви використовуєте 'utf8mb4', ваша версія InnoDB повинна бути більше 5.6. ssl_mode=SSL +charset=Кодування символів path=Шлях sqlite_helper=Шлях до файлу для бази даних SQLite3.
Введіть абсолютний шлях, якщо ви запускаєте Gіtea як сервіс. err_empty_db_path=Шлях до файлу бази даних SQLite3 не може бути порожнім. 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=Неправильне ім'я користувача-адміністратора general_title=Загальні налаштування app_name=Назва сайту @@ -205,6 +225,7 @@ sign_up_successful=Обліковий запис було успішно ств confirmation_mail_sent_prompt=Новий лист для підтвердження було відправлено на %s, будь ласка, перевірте вашу поштову скриньку протягом %s для завершення реєстрації. must_change_password=Оновіть свій пароль allow_password_change=Вимагати в користувача змінити пароль (рекомендується) +reset_password_mail_sent_prompt=Електронний лист із підтвердженням надіслано %s. Перевірте папку 'Вхідні' в межах наступних %s, щоб завершити процес відновлення облікового запису. active_your_account=Активувати обліковий запис account_activated=Обліковий запис активовано prohibit_login=Вхід заборонений @@ -213,7 +234,11 @@ resent_limit_prompt=Вибачте, ви вже запросили актива has_unconfirmed_mail=Привіт %s, у вас є непідтверджена електронна адреса (%s ). Якщо ви не отримали електронний лист із підтвердженням або вам потрібно надіслати новий, натисніть на кнопку нижче. resend_mail=Натисніть тут, щоб вислати лист активації знову email_not_associate=Ця електронна пошта не пов'язана ні з одним обліковим записом. +send_reset_mail=Надіслати електронний лист для відновлення облікового запису +reset_password=Відновлення облікового запису invalid_code=Цей код підтвердження недійсний або закінчився. +reset_password_helper=Відновити обліковий запис +reset_password_wrong_user=Ви ввійшли як %s, але посилання для відновлення облікового запису для %s password_too_short=Довжина пароля не може бути меншою за %d символів. non_local_account=Нелокальні акаунти не можуть змінити пароль через Gitea. verify=Підтвердити @@ -224,16 +249,33 @@ twofa_passcode_incorrect=Ваш пароль є невірним. Якщо ви twofa_scratch_token_incorrect=Невірний одноразовий пароль. login_userpass=Увійти login_openid=OpenID +oauth_signup_tab=Зареєструвати обліковий запис +oauth_signup_title=Додати Email та пароль (для відновлення аккаунту) +oauth_signup_submit=Повний обліковий запис +oauth_signin_tab=Посилання на існуючий обліковий запис +oauth_signin_title=Увійдіть щоб авторизувати пов'язаний обліковий запис +oauth_signin_submit=Прив'язати обліковий запис openid_connect_submit=Під’єднатися openid_connect_title=Підключитися до існуючого облікового запису openid_connect_desc=Вибраний OpenID URI невідомий. Пов'яжіть його з новим обліковим записом тут. openid_register_title=Створити новий обліковий запис openid_register_desc=Вибраний OpenID URI невідомий. Пов'яжіть йогоз новим обліковим записом тут. openid_signin_desc=Введіть свій ідентифікатор OpenID. Наприклад: https://anne.me, bob.openid.org.cn або gnusocial.net/carry. +disable_forgot_password_mail=Відновлення облікового запису вимкнено. Зверніться до адміністратора сайту. +email_domain_blacklisted=З вказаним email реєстрація неможлива. +authorize_application=Авторизувати програму +authorize_redirect_notice=Вас буде переадресовано до %s, якщо ви авторизуєте цю програму. +authorize_application_created_by=Ця програма створена %s. +authorize_application_description=Якщо ви надасте цей доступ, то він матиме доступ до всіх ваших даних облікового запису, включаючи приватні репозиторії та організації. +authorize_title=Авторизуйвати "%s" для доступу до вашого облікового запису? +authorization_failed=Помилка авторизації +authorization_failed_desc=Авторизація не вдалася, оскільки ми виявили недійсний запит. Зверніться до супровідника програми, яку ви намагалися авторизувати. +sspi_auth_failed=Помилка SSPI-автентифікації [mail] activate_account=Будь ласка, активуйте ваш обліковий запис activate_email=Підтвердить вашу адресу електронної пошти +reset_password=Відновлення вашого облікового запису register_success=Реєстрація успішна register_notify=Ласкаво просимо у Gitea @@ -262,6 +304,8 @@ CommitChoice=Вибір коміта TreeName=Шлях до файлу Content=Зміст +SSPISeparatorReplacement=Розділювач +SSPIDefaultLanguage=Типова мова require_error=` не може бути пустим.` alpha_dash_error=` повинен містити тільки літерно-цифрові символи, дефіс ('-') та підкреслення ('_'). ` @@ -273,22 +317,32 @@ max_size_error=` повинен бути не більш як %s символі email_error=` не є адресою електронної пошти.` url_error=` не є припустимою URL-Адресою.` include_error=`повинен бути текст '%s'` +glob_pattern_error=` неприпустимий шаблон glob: %s.` unknown_error=Невідома помилка: captcha_incorrect=Код CAPTCHA неправильний. password_not_match=Паролі не співпадають. +lang_select_error=Оберіть мову з переліку. username_been_taken=Ім'я користувача вже зайнято. repo_name_been_taken=Ім'я репозіторію вже використовується. +visit_rate_limit=Обмеження швидкості віддаленого доступу. +2fa_auth_required=Для віддаленого доступу необхідна двуфакторна аутентифікація. org_name_been_taken=Назва організації вже зайнято. team_name_been_taken=Назва команди вже зайнято. team_no_units_error=Дозволити доступ до принаймні одного розділу репозитарію. email_been_used=Ця електронна адреса вже використовується. openid_been_used=OpenID адреса '%s' вже використовується. username_password_incorrect=Неправильне ім'я користувача або пароль. +password_complexity=Пароль не відповідає вимогам до складності: +password_lowercase_one=Принаймні одна буква в нижньому регістрі +password_uppercase_one=Принаймні одна буква в верхньому регістрі +password_digit_one=Принаймні одна цифра +password_special_one=Принаймні один спеціальний символ (пунктуація, дужки, лапки тощо) enterred_invalid_repo_name=Невірно введено ім'я репозиторію. enterred_invalid_owner_name=Ім'я нового власника не є дійсним. enterred_invalid_password=Введений вами пароль некоректний. user_not_exist=Даний користувач не існує. +team_not_exist=Команда не існує. last_org_owner=Ви не можете вилучити останнього користувача з команди 'власники'. У кожній команді має бути хоча б один власник. cannot_add_org_to_team=Організацію неможливо додати як учасника команди. @@ -313,6 +367,8 @@ starred=Обрані Репозиторії following=Читає follow=Підписатися unfollow=Відписатися +heatmap.loading=Завантаження карти активності… +user_bio=Біографія form.name_reserved=Ім'я користувача "%s" зарезервовано. form.name_pattern_not_allowed=Шаблон '%s' не дозволено в імені користувача. @@ -341,6 +397,7 @@ password_username_disabled=Нелокальним користувачам за full_name=Повне ім'я website=Веб-сайт location=Місцезнаходження +update_theme=Оновити тему update_profile=Оновити профіль update_profile_success=Профіль успішно оновлено. change_username=Ваше Ім'я кристувача було змінено. @@ -348,6 +405,7 @@ change_username_prompt=Примітка. Зміни в імені також з continue=Продовжити cancel=Відмінити language=Мова +ui=Тема lookup_avatar_by_mail=Знайти Аватар за адресою електронної пошти federated_avatar_lookup=Знайти зовнішній аватар @@ -356,6 +414,7 @@ choose_new_avatar=Оберіть новий аватар update_avatar=Оновити аватар delete_current_avatar=Видалити поточний аватар uploaded_avatar_not_a_image=Завантажений файл не є зображенням. +uploaded_avatar_is_too_big=Файл, що завантажувався, перевищив максимальний розмір. update_avatar_success=Ваш аватар був змінений. change_password=Оновити пароль @@ -368,14 +427,18 @@ password_change_disabled=Нелокальні акаунти не можуть emails=Адреса електронної пошти manage_emails=Керування адресами ел. пошти +manage_themes=Виберіть тему за замовчуванням manage_openid=Керування OpenID email_desc=Ваша основна адреса електронної пошти використовуватиметься для сповіщення та інших операцій. +theme_desc=Ця тема буде типовою для всього сайту. primary=Основний primary_email=Зробити основним delete_email=Видалити 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 була видалена. @@ -399,6 +462,7 @@ ssh_helper=Потрібна допомога? Дивіться gpg_helper= Потрібна допомога? Перегляньте посібник GitHub про GPG . add_new_key=Додати SSH ключ add_new_gpg_key=Додати GPG ключ +ssh_key_been_used=Цей SSH ключ вже був додано до сервера. ssh_key_name_used=Ключ SSH з таким самим ім'ям вже додано до вашого облікового запису. gpg_key_id_used=Публічний ключ GPG з таким самим ідентифікатором вже існує. gpg_no_key_email_found=Цей ключ GPG непридатний для використання з будь-якою електронною адресою, що пов'язана з вашим обліковим записом. @@ -445,7 +509,37 @@ access_token_deletion=Видалити токен доступу access_token_deletion_desc=Видалення токена скасовує доступ до вашого облікового запису для програм, що використовують його. Продовжити? delete_token_success=Токен був знищений. Програми, що використовують його, більше не мають доступу до вашого облікового запису. - +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_select_type=Який тип програми підходить? +oauth2_type_web=Веб (напр. Node.JS, Tomcat, Go) +oauth2_type_native=Рідний (напр. мобільний, робочий стіл, веб-переглядач) +oauth2_redirect_uri=URI перенаправлення +save_application=Зберегти +oauth2_client_id=ID Клієнта +oauth2_client_secret=Ключ клієнта +oauth2_regenerate_secret=Відновити ключ +oauth2_regenerate_secret_hint=Ви втратили свій ключ? +oauth2_client_secret_hint=Ключ не буде видимим, якщо ви знову зайдете на цю сторінку. Збережіть свій ключ. +oauth2_application_edit=Редагувати +oauth2_application_create_description=Програми OAuth2 надають вашим стороннім програмам доступ до облікових записів користувачів у цьому екземплярі. +oauth2_application_remove_description=Видалення програми OAuth2 скасує доступ до авторизованих облікових записів користувачів у цьому екземплярі. Продовжити? + +authorized_oauth2_applications=Авторизовані програми OAuth2 +authorized_oauth2_applications_description=Ви надали цим програмам третіх сторін доступ до вашого облікового запису Gitea. Будь ласка, скасуйте доступ для програм, які більше не потрібні. +revoke_key=Відкликати +revoke_oauth2_grant=Скасувати доступ +revoke_oauth2_grant_description=Скасування доступу для цієї програми третьої сторони не дозволить їй отримувати доступ до ваших даних. Ви впевнені? +revoke_oauth2_grant_success=Ви успішно скасували доступ. twofa_desc=Двофакторна автентифікація підвищує безпеку вашого облікового запису. twofa_is_enrolled=Ваш обліковий запис на даний час використовує двофакторну автентифікацію. @@ -488,12 +582,22 @@ 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 [repo] owner=Власник repo_name=Назва репозиторію repo_name_helper=Хороші назви репозиторіїв використовують короткі, унікальні ключові слова що легко запам'ятати. +repo_size=Розмір репозиторію +template=Шаблон +template_select=Оберіть шаблон. +template_helper=Зробити репозиторій шаблоном +template_description=Шаблонні репозиторії дозволяють користувачам генерувати нові репозиторії із такою ж структурою директорій, файлами та додатковими налаштуваннями. visibility=Видимість +visibility_description=Тільки власник або члени організації які мають віповідні права, зможуть побачити. visibility_helper=Створити приватний репозиторій visibility_helper_forced=Адміністратор вашого сайту налаштував параметри: всі нові репозиторії будуть приватними. visibility_fork_helper=(Ці зміни вплинуть на всі форки.) @@ -501,9 +605,14 @@ clone_helper=Потрібна допомога у клонуванні? Відв fork_repo=Форкнути репозиторій fork_from=Форк з fork_visibility_helper=Неможливо змінити видимість форкнутого репозиторію. +use_template=Застосувати цей шаблон +generate_repo=Згенерувати репозиторій +generate_from=Генерувати з repo_desc=Опис repo_lang=Мова repo_gitignore_helper=Виберіть шаблон .gitignore. +issue_labels=Мітки проблем +issue_labels_helper=Вибрати мітку для проблеми. license=Ліцензія license_helper=Виберіть ліцензійний файл. readme=README @@ -513,8 +622,12 @@ create_repo=Створити репозиторій default_branch=Головна гілка mirror_prune=Очистити mirror_prune_desc=Видалення застарілих посилань які ви відслідковуєте +mirror_interval=Інтервал дзеркалювання (допустимі значення 'h', 'm', 's'). 0 - щоб вимкнути автоматичну синхронізацію. mirror_interval_invalid=Інтервал дзеркалювання є неприпустимим. mirror_address=Клонування з URL-адреси +mirror_address_desc=Покласти будь-які необхідні облікові дані у розділі клонування авторизації. +mirror_address_url_invalid=Надана URL-адреса є неприпустимою. Ви повинні екранувати всі компоненти URL-адреси правильно. +mirror_address_protocol_invalid=Надана URL-адреса є неприпустимою. Тільки http(s):// або git:// можливо використовувати при дзеркальні. mirror_last_synced=Остання синхронізація watchers=Спостерігачі stargazers=Зацікавлені @@ -522,7 +635,20 @@ forks=Форки pick_reaction=Залиште свою оцінку reactions_more=додати %d більше - +template.items=Елементи шаблону +template.git_content=Вміст Git (типова гілка) +template.git_hooks=Перехоплювачі Git +template.git_hooks_tooltip=Наразі ви не зможете змінити чи видалити перехоплювачі git після додавання. Обирайте цей варіант лише тоді, коли довіряєте шаблонному репозиторію. +template.webhooks=Webhook'и +template.topics=Теми +template.avatar=Аватар +template.issue_labels=Мітки проблем +template.one_item=Слід обрати хоча б один елемент шаблону +template.invalid=Слід обрати шаблонний репозиторій + +archive.title=Це архівний репозитарій. Ви можете переглядати і клонувати файли, але не можете робити пуш або відкривати питання/запити. +archive.issue.nocomment=Це архівний репозитарій. Ви не можете коментувати запити. +archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквести. form.reach_limit_of_creation=Ви досягли максимальної кількості %d створених репозиторіїв. form.name_reserved=Назву репозиторію '%s' зарезервовано. @@ -531,6 +657,13 @@ form.name_pattern_not_allowed=Шаблон '%s' не дозволено в на need_auth=Клонувати з авторизацією migrate_type=Тип міграції migrate_type_helper=Даний репозиторій буде дзеркалом +migrate_items=Деталі міграції +migrate_items_wiki=Вікі +migrate_items_milestones=Етапи +migrate_items_labels=Мітки +migrate_items_issues=Проблеми +migrate_items_pullrequests=Запити на злиття +migrate_items_releases=Релізи migrate_repo=Перенести репозиторій migrate.clone_address=Міграція / клонувати з URL-адреси migrate.clone_address_desc=URL-адреса HTTP(S) або Git "clone" існуючого репозиторія @@ -539,10 +672,17 @@ migrate.permission_denied=Вам не дозволено імпортувати migrate.invalid_local_path=Локальний шлях недійсний. Він не існує або не є каталогом. migrate.failed=Міграція не вдалася: %v migrate.lfs_mirror_unsupported=Дзеркалювання LFS об'єктів не підтримується - використовуйте 'git lfs fetch --all' і 'git lfs push --all' вручну. +migrate.migrate_items_options=Під час міграції з github введіть ім'я користувача і будуть показані параметри міграції. +migrated_from=Перенесено з %[2]s +migrated_from_fake=Перенесено з %[1]s +migrate.migrating=Міграція із %s... +migrate.migrating_failed=Міграція із %s не вдалася. mirror_from=дзеркало forked_from=форк від +generated_from=згенеровано з fork_from_self=Ви не можете форкнути репозиторій, так як ви його власник. +fork_guest_user=Увійдіть, щоб зробити форк репозитарію. copy_link=Копіювати copy_link_success=Посилання було скопійоване copy_link_error=Натисніть ⌘-C або Ctrl-C, щоб скопіювати @@ -559,6 +699,7 @@ quick_guide=Короткий посібник clone_this_repo=Кнонувати цей репозиторій create_new_repo_command=Створити новий репозиторій з командного рядка push_exist_repo=Опублікувати існуючий репозиторій з командного рядка +empty_message=Цей репозиторій порожній. code=Код code.desc=Доступ до коду, файлів, комітів та гілок. @@ -580,15 +721,22 @@ file_view_raw=Перегляд Raw file_permalink=Постійне посилання file_too_large=Цей файл завеликий щоб бути показаним. video_not_supported_in_browser=Ваш браузер не підтримує тег 'video' HTML5. +audio_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 'audio'. stored_lfs=Збережено з Git LFS commit_graph=Графік комітів +blame=Звинувачення +normal_view=Звичайний вигляд +line=рядок +lines=рядки editor.new_file=Новий файл editor.upload_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.delete_this_file=Видалити файл @@ -599,32 +747,43 @@ editor.filename_help=Щоб додати каталог, наберіть йог editor.or=або editor.cancel_lower=Скасувати editor.commit_changes=Закомітити зміни +editor.add_tmpl=Додати '' editor.add=Додати '%s' editor.update=Оновити '%s' editor.delete=Видалити '%s' editor.commit_message_desc=Додати необов'язковий розширений опис… editor.commit_directly_to_this_branch=Зробіть коміт прямо в гілку %s. editor.create_new_branch=Створити нову гілку для цього коміту та відкрити запит на злиття. +editor.create_new_branch_np=Створити нову гілку для цього коміту. +editor.propose_file_change=Запропонувати зміну файлу editor.new_branch_name_desc=Ім'я нової гілки… editor.cancel=Відмінити editor.filename_cannot_be_empty=Ім'я файлу не може бути порожнім. +editor.filename_is_invalid=Ім'я файлу неприпустиме: "%s". +editor.branch_does_not_exist=Гілка '%s' відсутня в цьому репозиторії. editor.branch_already_exists=Гілка '%s' вже присутня в репозиторії. editor.directory_is_a_file=Ім'я каталогу "%s" уже використовується як ім'я файлу в цьому репозиторії. editor.file_is_a_symlink='%s' є символічним посиланням. Символічні посилання не можливо редагувати в веб-редакторі 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=Зміст файлу змінився з моменту початку редагування. Натисніть тут , щоб переглянути що було змінено, або закомітьте зміни ще раз, щоб переписати їх. editor.file_already_exists=Файл з назвою "%s" уже існує у цьому репозиторію. +editor.commit_empty_file_header=Закомітити порожній файл +editor.commit_empty_file_text=Файл, який ви збираєтеся закомітити, порожній. Продовжити? editor.no_changes_to_show=Нема змін для показу. editor.fail_to_update_file=Не вдалося оновити/створити файл '%s' через помилку: %v 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'. commits.desc=Переглянути історію зміни коду. commits.commits=Коміти +commits.no_commits=Немає спільних комітів. '%s' та '%s' мають різну історію. commits.search=Знайти коміт… +commits.search.tooltip=Можна вказати префікс ключових слів із "автор:", "'комітер:", "до:" або "опісля:", напр. "повернути автору: Аліса до: 2019-04-01". commits.find=Пошук commits.search_all=Усі гілки commits.author=Автор @@ -640,6 +799,7 @@ ext_issues.desc=Посилання на зовнішню систему відс issues.desc=Організація звітів про помилки, завдань та етапів. issues.new=Нова проблема +issues.new.title_empty=Заголовок не може бути пустим issues.new.labels=Мітки issues.new.no_label=Без мітки issues.new.clear_labels=Очистити мітки @@ -662,16 +822,22 @@ issues.label_templates.info=Ще немає міток. Натисніть 'Но issues.label_templates.helper=Оберіть набір міток issues.label_templates.use=Використовувати набір міток issues.label_templates.fail_to_load_file=Не вдалося завантажити файл шаблона мітки '%s': %v +issues.add_label_at=додано
%s
мітку %s +issues.remove_label_at=видалено
%s
мітку %s issues.add_milestone_at=`додав(ла) до %s етапу %s` issues.change_milestone_at=`змінено цільової етап з %s на %s %s` issues.remove_milestone_at=`видалено з етапу%s %s` issues.deleted_milestone=`(видалено)` issues.self_assign_at=`самостійно призначений %s` issues.add_assignee_at=`був призначений %s %s` +issues.remove_assignee_at=`був знятий з призначення %s %s` +issues.remove_self_assignment=`видалено призначення %s` +issues.change_title_at=`змінився заголовок з %s на %s %s` issues.delete_branch_at=`видалена гілка %s %s` issues.open_tab=%d відкрито issues.close_tab=%d закрито issues.filter_label=Мітка +issues.filter_label_exclude=`Використовуйте Alt + клік/Enter для виключення міток` issues.filter_label_no_select=Всі мітки issues.filter_milestone=Етап issues.filter_milestone_no_select=Всі етапи @@ -689,6 +855,8 @@ 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.mostforks=Найбільше форків @@ -701,7 +869,11 @@ issues.action_milestone_no_select=Етап відсутній issues.action_assignee=Виконавець issues.action_assignee_no_select=Немає виконавеця issues.opened_by=%[1]s відкрито %[3]s +pulls.merged_by=злито %[1]s з %[3]s +pulls.merged_by_fake=об'єднано %[1]s згідно з %[2]s +issues.closed_by=закрито %[1]s з %[3]s issues.opened_by_fake=%[1]s відкрито %[2]s +issues.closed_by_fake=закрито %[1]s згідно з %[2]s issues.previous=Попередній issues.next=Далі issues.open_title=Відкрито @@ -709,6 +881,10 @@ issues.closed_title=Закрито issues.num_comments=%d коментарів issues.commented_at=`прокоментував(ла) %s` issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар? +issues.context.copy_link=Скопіювати посилання +issues.context.quote_reply=Цитувати відповідь +issues.context.edit=Редагувати +issues.context.delete=Видалити issues.no_content=Тут ще немає жодного змісту. issues.close_issue=Закрити issues.close_comment_issue=Прокоментувати і закрити @@ -718,6 +894,13 @@ issues.create_comment=Коментар issues.closed_at=`закрито %[2]s` issues.reopened_at=`повторно відкрито %[2]s` issues.commit_ref_at=`згадано цю проблему в коміті %[2]s` +issues.ref_issue_from=`послався на цю проблему %[4]s %[2]s` +issues.ref_pull_from=`послався на цей запит злиття %[4]s %[2]s` +issues.ref_closing_from=`послався на запит злиття %[4]s, який закриває цю проблему %[2]s` +issues.ref_reopening_from=`послався на запит злиття %[4]s, який повторно відкриває цю проблему %[2]s` +issues.ref_closed_from=`закрив цю проблему %[4]s %[2]s` +issues.ref_reopened_from=`повторно відкрив цю проблему %[4]s %[2]s` +issues.ref_from=`із %[1]s` issues.poster=Автор issues.collaborator=Співавтор issues.owner=Власник @@ -745,10 +928,30 @@ issues.attachment.open_tab=`Натисніть щоб побачити "%s" у issues.attachment.download=`Натисніть щоб завантажити "%s"` issues.subscribe=Підписатися issues.unsubscribe=Відписатися +issues.lock=Блокування обговорення +issues.unlock=Розблокування обговорення +issues.lock.unknown_reason=Неможливо заблокувати проблему з невідомою причиною. +issues.lock_duplicate=Проблема не може бути заблокованим двічі. +issues.unlock_error=Не можливо розблокувати проблему, яка не заблокована. +issues.lock_with_reason=заблоковано як %s та обмежене обговорення для співавторів %s +issues.lock_no_reason=заблоковано та обмежене обговорення для співавторів %s +issues.unlock_comment=розблоковане обговорення %s +issues.lock_confirm=Заблокувати +issues.unlock_confirm=Розблокувати +issues.lock.notice_1=- Інші користувачі не можуть додавати нові коментарі до цієї проблеми. +issues.lock.notice_2=- Ви й інші співавтори, які мають доступ до цього репозиторію, можете залишати коментарі, які інші можуть бачити. +issues.lock.notice_3=- Ви завжди зможете розблокувати цю проблему в майбутньому. +issues.unlock.notice_1=- Кожен зможе прокоментувати це питання ще раз. +issues.unlock.notice_2=- Ви завжди зможете заблокувати цю проблему в майбутньому. +issues.lock.reason=Причина блокування +issues.lock.title=Заблокувати обговорення цієї проблеми. +issues.unlock.title=Розблокувати обговорення цієї проблеми. +issues.comment_on_locked=Ви не можете коментувати заблоковану проблему. issues.tracker=Відстеження часу issues.start_tracking_short=Запустити issues.start_tracking=Почати відстеження часу issues.start_tracking_history=`почав працювати %s` +issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця проблема буде закрита issues.tracking_already_started=`Ви вже почали відстежувати час для цієї проблеми!` issues.stop_tracking=Стоп issues.stop_tracking_history=`перестав(-ла) працювати %s` @@ -810,11 +1013,15 @@ issues.review.self.rejection=Ви не можете надіслати запи issues.review.approve=зміни затверджено %s issues.review.comment=рецензовано %s issues.review.content.empty=Запрошуючи зміни, ви зобов'язані залишити коментар з поясненнями своїх побажань відносно Pull Request'а. +issues.review.reject=зробив запит змін %s issues.review.pending=Очікування issues.review.review=Рецензії +issues.review.reviewers=Рецензенти issues.review.show_outdated=Показати застарілі issues.review.hide_outdated=Приховати застарілі +issues.assignee.error=Додано не всіх виконавців через непередбачену помилку. +pulls.desc=Увімкнути запити на злиття та огляд коду. pulls.new=Новий запит на злиття pulls.compare_changes=Новий запит на злиття pulls.compare_changes_desc=Порівняти дві гілки і створити запит на злиття для змін. @@ -825,28 +1032,46 @@ pulls.no_results=Результатів не знайдено. pulls.nothing_to_compare=Ці гілки однакові. Немає необхідності створювати запитів на злиття. pulls.has_pull_request=`Вже існує запит на злиття між двома цілями: %[2]s#%[3]d` pulls.create=Створити запит на злиття +pulls.title_desc=хоче злити %[1]d комітів з %[2]s в %[3]s pulls.merged_title_desc=злито %[1]d комітів з %[2]s до %[3]s %[4]s +pulls.change_target_branch_at=`змінена цільова гілка з %s на %s %s` pulls.tab_conversation=Обговорення pulls.tab_commits=Коміти pulls.tab_files=Змінені файли pulls.reopen_to_merge=Будь ласка перевідкрийте цей запит щоб здіснити операцію злиття. +pulls.cant_reopen_deleted_branch=Цей запит не можна повторно відкрити, оскільки гілку видалено. pulls.merged=Злито +pulls.merged_as=Запит на злиття був влитиий як %[2]s. +pulls.is_closed=Запит на злиття було закрито. pulls.has_merged=Запит на злиття було об'єднано. pulls.title_wip_desc=`Почніть заголовок з %s щоб запобігти випадковому злиттю запитів.` pulls.cannot_merge_work_in_progress=Цей пулл-реквест вже в стадії виконання. Видаліть префікс %s з заголовку після того як роботи будуть завершені pulls.data_broken=Зміст цього запиту було порушено внаслідок видалення інформації Форком. Цей запит тягнеться через відсутність інформації про вилучення. +pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою. pulls.is_checking=Триває перевірка конфліктів, будь ласка обновіть сторінку дещо пізніше. +pulls.required_status_check_failed=Деякі необхідні перевірки виконані з помилками. +pulls.required_status_check_administrator=Як адміністратор ви все одно можете об'єднати цей запит на злиття. +pulls.blocked_by_approvals=Цей pull-запит ще не має достатньо схвалень. %d від %d схвалень надано. pulls.can_auto_merge_desc=Цей запит можна об'єднати автоматично. pulls.cannot_auto_merge_desc=Цей запит на злиття не може бути злитий автоматично через конфлікти. pulls.cannot_auto_merge_helper=Злийте вручну для вирішення конфліктів. pulls.no_merge_desc=Цей запити на злиття неможливо злити, оскільки всі параметри об'єднання репозиторія вимкнено. pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну. pulls.no_merge_wip=Цей пулл-реквест не можливо об'єднати, тому-що він вже виконується. +pulls.no_merge_status_check=Не вдалося об'єднати цей запит на злиття, оскільки не всі обов'язкові перевірки були успішними. pulls.merge_pull_request=Об'єднати запит на злиття pulls.rebase_merge_pull_request=Зробити Rebase і злити +pulls.rebase_merge_commit_pull_request=Rebase та злитя (--no-ff) pulls.squash_merge_pull_request=Об'єднати (Squash) і злити pulls.invalid_merge_option=Цей параметр злиття не можна використовувати для цього Pull Request'а. +pulls.merge_conflict=Помилка злиття: при злитті виник конфлікт: %[1]s
%[2]s
Підказка: спробуйте іншу стратегію +pulls.rebase_conflict=Помилка злиття: виник конфлікт при виконанні rebase коміту: %[1]s
%[2]s
%[3]s
Підказка: спробуйте іншу стратегію +pulls.unrelated_histories=Помилка злиття: head та base злиття не мають спільної історії. Підказка: спробуйте іншу стратегію +pulls.merge_out_of_date=Помилка злиття: base було оновлено, поки відбувалося злиття. Підказка: спробуйте знову. pulls.open_unmerged_pull_exists=`Ви не можете знову відкрити, оскільки вже існує запит на злиття (%d) з того ж репозиторія з тією ж інформацією про злиття і в очікуванні.` +pulls.status_checking=Деякі перевірки знаходяться на розгляді +pulls.status_checks_success=Всі перевірки були успішними +pulls.status_checks_error=Деякі перевірки не вдалися milestones.new=Новий етап milestones.open_tab=%d відкрито @@ -856,6 +1081,7 @@ milestones.no_due_date=Немає дати завершення milestones.open=Відкрити milestones.close=Закрити milestones.new_subheader=Створюйте етапи для організації ваших завдань. +milestones.completeness=%d%% завершено milestones.create=Створити етап milestones.title=Заголовок milestones.desc=Опис @@ -894,6 +1120,9 @@ wiki.save_page=Зберегти сторінку wiki.last_commit_info=%s редагував цю сторінку %s wiki.edit_page_button=Редагувати wiki.new_page_button=Нова сторінка +wiki.file_revision=Ревізії сторінки +wiki.wiki_page_revisions=Ревізії вікі сторінок +wiki.back_to_wiki=Повернутись на сторінку Вікі wiki.delete_page_button=Видалити сторінку wiki.delete_page_notice_1=Видалення сторінки вікі '%s' не може бути скасовано. Продовжити? wiki.page_already_exists=Вікі-сторінка з таким самим ім'ям вже існує. @@ -907,6 +1136,9 @@ 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.overview=Огляд activity.active_prs_count_1=%d Активний запити на злиття activity.active_prs_count_n=%d Активні запити на злиття @@ -942,6 +1174,27 @@ 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.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_and_deletions=та +activity.git_stats_deletion_1=%d видалений +activity.git_stats_deletion_n=%d видалені search=Пошук search.search_repo=Пошук репозиторію @@ -954,6 +1207,7 @@ settings.collaboration=Співавтори settings.collaboration.admin=Адміністратор settings.collaboration.write=Запис settings.collaboration.read=Читати +settings.collaboration.owner=Власник settings.collaboration.undefined=Не визначено settings.hooks=Веб-хуки settings.githooks=Git хуки @@ -961,6 +1215,10 @@ settings.basic_settings=Базові налаштування settings.mirror_settings=Налаштування дзеркала settings.sync_mirror=Синхронізувати зараз settings.mirror_sync_in_progress=Синхронізуються репозиторії-дзеркала. Зачекайте хвилину і обновіть сторінку. +settings.email_notifications.enable=Увімкнути сповіщення email +settings.email_notifications.onmention=Повідомнення email тільки при згадуванні +settings.email_notifications.disable=Вимкнути email сповіщення +settings.email_notifications.submit=Налаштувати параметри email settings.site=Веб-сайт settings.update_settings=Оновити налаштування settings.advanced_settings=Додаткові налаштування @@ -988,9 +1246,11 @@ settings.pulls_desc=Увімкнути запити на злиття в реп settings.pulls.ignore_whitespace=Ігнорувати пробіл у конфліктах settings.pulls.allow_merge_commits=Дозволити коміти злиття settings.pulls.allow_rebase_merge=Увімкнути Rebasing коміти перед злиттям +settings.pulls.allow_rebase_merge_commit=Ввімкнути Rebase з явним злиттям (--no-ff) settings.pulls.allow_squash_commits=Увімкнути об'єднувати коміти перед злиттям settings.admin_settings=Налаштування адміністратора settings.admin_enable_health_check=Включити перевірки працездатності репозиторію (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити проблему за допомогою коміта, зробленого не головній гілці settings.danger_zone=Небезпечна зона settings.new_owner_has_same_repo=Новий власник вже має репозиторій з такою назвою. Будь ласка, виберіть інше ім'я. settings.convert=Перетворити на звичайний репозиторій @@ -1029,6 +1289,11 @@ settings.collaborator_deletion_desc=Цей користувач більше н settings.remove_collaborator_success=Співавтор видалений. settings.search_user_placeholder=Пошук користувача… settings.org_not_allowed_to_be_collaborator=Організації не можуть бути додані як співавтори. +settings.change_team_access_not_allowed=Зміна доступу команди до репозитарію обмежена власником організації +settings.team_not_in_organization=Команда та репозитарій мають привязки до різних організацій +settings.add_team_duplicate=Команда вже має привязку до репозитарію +settings.add_team_success=Команда отримала доступ до репозиторію. +settings.remove_team_success=Доступ команди до репозиторію видалений. settings.add_webhook=Додати веб-хук settings.add_webhook.invalid_channel_name=Назва каналу Webhook не може бути порожньою і не може містити лише символ #. settings.hooks_desc=Веб-хуки автоматично робить HTTP POST-запити на сервер, коли відбуваються певні події Gitea. Дізнайтеся більше в інструкції по використанню web-хуків . @@ -1050,6 +1315,7 @@ settings.githook_content=Зміст хука settings.update_githook=Оновити хук settings.add_webhook_desc=Gitea буде відправляти POST запити на вказану URL адресу, з інформацією про події, що відбуваються. Подробиці на сторінці інструкції по використанню web-хуків . settings.payload_url=Цільова URL-адреса +settings.http_method=Метод HTTP settings.content_type=Тип змісту settings.secret=Секрет settings.slack_username=Ім'я кристувача @@ -1074,8 +1340,11 @@ settings.event_issue_comment_desc=Коментар проблеми створе settings.event_release=Реліз settings.event_release_desc=Реліз опублікований, оновлений або видалений з репозиторія. settings.event_pull_request=Запити до злиття +settings.event_pull_request_desc=Запит до злиття відкритий, закритий, перевідкритий, змінений, назначений, відхилений, рецінзійований коментар, призначений, мітка оновлена, мітка прибрана або синхронізована. settings.event_push=Push settings.event_push_desc=Git push до репозиторію. +settings.branch_filter=Фільтр гілок +settings.branch_filter_desc=Білий список повідомлень push гілок, створення гілок та видалення гілок, визначається як glob шаблон. Якщо пустий або *, повідомлення для вісіх гілок ввімкнено. Дівіться github.com/gobwas/glob документацію на синтаксис. Приклад: master, {master,release*}. settings.event_repository=Репозиторій settings.event_repository_desc=Репозиторій створений або видалено. settings.active=Активний @@ -1092,6 +1361,8 @@ settings.slack_domain=Домен settings.slack_channel=Канал settings.add_discord_hook_desc=Інтеграція Discord у ваш репозиторії. settings.add_dingtalk_hook_desc=Інтеграція Dingtalk у ваш репозиторії. +settings.add_telegram_hook_desc=Інтегруйте Telegram у своє сховище. +settings.add_msteams_hook_desc=Інтегруйте Microsoft Teams у своє сховище. settings.deploy_keys=Ключі для розгортування settings.add_deploy_key=Додати ключ для розгортування settings.deploy_key_desc=Ключі розгортання доступні тільки для читання. Це не те ж саме що і SSH-ключі аккаунта. @@ -1113,6 +1384,14 @@ settings.protected_branch_can_push_yes=Ви можете виконувати pu settings.protected_branch_can_push_no=Ви не можете виконувати push settings.branch_protection=Захист гілки %s settings.protect_this_branch=Захистити цю гілку +settings.protect_this_branch_desc=Запобігає видаленню гілки та обмежує виконання в ній push та злиття. +settings.protect_disable_push=Заборонити Push +settings.protect_disable_push_desc=Для цієї гілки буде заборонено виконання push. +settings.protect_enable_push=Дозволити Push +settings.protect_enable_push_desc=Будь-хто із правом запису зможе виконувати push для цієї гілки (за виключенням force push). +settings.protect_whitelist_committers=Білий список обмеження Push +settings.protect_whitelist_committers_desc=Лише користувачі та команди з білого списку зможуть виконувати push в цій гілці (за виключеням force push). +settings.protect_whitelist_deploy_keys=Білий список ключів розгортання з правом на запис settings.protect_whitelist_users=Користувачі, які можуть робити push в цю гілку: settings.protect_whitelist_search_users=Пошук користувачів… settings.protect_whitelist_teams=Команди, учасники яких можуть робити push в цю гілку: @@ -1121,6 +1400,15 @@ settings.protect_merge_whitelist_committers=Обмежити право на п settings.protect_merge_whitelist_committers_desc=Ви можете додавати користувачів або цілі команди в 'білий' список цієї гілки. Тільки присутні в списку зможуть приймати запити на злиття. В іншому випадку будь-хто з правами запису до головного репозиторію буде володіти такою можливістю. settings.protect_merge_whitelist_users=Користувачі з правом на прийняття Pull Request'ів в цю гілку: settings.protect_merge_whitelist_teams=Команди, яким дозволено злиття: +settings.protect_check_status_contexts=Увімкнути перевірку стану +settings.protect_check_status_contexts_desc=Необхідно перевірити статус, перш ніж об'єднати. Виберіть, які перевірки статусу слід провести, перш ніж гілку можна об'єднати в гілку, яка відповідає цим правилам. Коли цей пункт увімкнено, комміт спочатку відбувається до іншої гілки, яка потім об'єднується або відправляється безпосередньо до гілки, яка пройшла перевірку на відповідність цим правилам. Якщо жодний контекст не вибраний, то останній коміт буде успішним, не беручи до уваги контекст. +settings.protect_check_status_contexts_list=Перевірки статусу знайдено для репозитарію за минулий тиждень +settings.protect_required_approvals=Необхідно схвалення: +settings.protect_required_approvals_desc=Дозволити об'єднання запитів на злиття лише із достатньою кількістю позитивних рецензій. +settings.protect_approvals_whitelist_enabled=Обмежити схвалення користувачами та командами з білого списку +settings.protect_approvals_whitelist_enabled_desc=Лише рецензії користувачів та команд в білому списку впливають на число отриманих затверджень. Без білого списку будуть враховані рецензії будь-кого із доступом на запис. +settings.protect_approvals_whitelist_users=Білий список рецензентів: +settings.protect_approvals_whitelist_teams=Білий список команд рецензентів: settings.add_protected_branch=Увімкнути захист settings.delete_protected_branch=Вимкнути захист settings.update_protect_branch_success=Налаштування захисту гілки '%s' були успішно змінені. @@ -1131,11 +1419,57 @@ settings.default_branch_desc=Головна гілка є 'базовою' дл settings.choose_branch=Оберіть гілку… settings.no_protected_branch=Немає захищених гілок. settings.edit_protected_branch=Редагувати +settings.protected_branch_required_approvals_min=Число необхідних схвалень не може бути від'ємним. +settings.bot_token=Токен для бота +settings.chat_id=Чат ID +settings.archive.button=Архівний репозиторій +settings.archive.header=Відправити репозиторій в архів +settings.archive.text=Архівування репозиторія зробить його доступним лише для читання. Він не відображається на панелі, в нього не можуть вноситись зміни і не можна створювати запити з проблем та пулл-реквести. +settings.archive.success=Репозиторію успішно присвоєно статус архівного. +settings.archive.error=Сталася помилка при спробі архівувати репозиторій. Докладнішу інформацію див. у журналі. +settings.archive.error_ismirror=Неможливо архівувати дзеркальний репозиротрій. +settings.archive.branchsettings_unavailable=Параметри гілки не доступні, якщо репозиторій архівний. +settings.unarchive.button=Зняти архівний статус +settings.unarchive.header=Зняти архівний статус для репозиторія +settings.unarchive.text=Зняття статусу архівного відновить запис в репозиторій, а також відкриє можливість створювати запити з нових проблем та пулл-запити. +settings.unarchive.success=Статус архівний успішно знято. +settings.unarchive.error=Сталася помилка при спробі скасувати архівний статус репозиторія. Докладнішу інформацію див. у журналі. +settings.update_avatar_success=Аватар репозиторію оновлений. +settings.lfs=LFS +settings.lfs_filelist=Файли LFS, які зберігаються в цьому репозиторії +settings.lfs_no_lfs_files=В цьому репозиторії відсутні файли LFS +settings.lfs_findcommits=Знайти коміти +settings.lfs_lfs_file_no_commits=Не знайдено комітів для цього файлу LFS +settings.lfs_noattribute=Цей шлях не має атрибуту блокування в гілці за замовчуванням +settings.lfs_delete=Видалити файл LFS з OID %s +settings.lfs_delete_warning=Видалення файлу LFS може спричинити помилки "Об'єкт не існує" під час перевірки. Ви впевнені? +settings.lfs_findpointerfiles=Знайти файли-посилання +settings.lfs_locks=Блокування +settings.lfs_invalid_locking_path=Неприпустимий шлях: %s +settings.lfs_invalid_lock_directory=Не можливо заблокувати каталог: %s +settings.lfs_lock_already_exists=Блокування вже використовується: %s +settings.lfs_lock=Блокувати +settings.lfs_lock_path=Шлях до файлу для блокування... +settings.lfs_locks_no_locks=Відсутнє блокування +settings.lfs_lock_file_no_exist=Заблокований файл не існує у гілці за замовчуванням +settings.lfs_force_unlock=Примусове розблокування +settings.lfs_pointers.found=Знайдено %d посилань на blob - %d пов'язаних, %d непов'язаних (%d відсутні у сховищі) +settings.lfs_pointers.sha=Blob SHA +settings.lfs_pointers.oid=OID +settings.lfs_pointers.inRepo=В репозиторії +settings.lfs_pointers.exists=Наявний у сховищі +settings.lfs_pointers.accessible=Доступний для користувача +settings.lfs_pointers.associateAccessible=Пов'язати доступні %d OID diff.browse_source=Переглянути джерело diff.parent=джерело diff.commit=коміт +diff.git-notes=Примітки diff.data_not_available=Різниця недоступна +diff.options_button=Параметри порівняння +diff.show_diff_stats=Показати статистику +diff.download_patch=Завантажити патч +diff.download_diff=Завантажити файл різниці diff.show_split_view=Розділений перегляд diff.show_unified_view=Об'єднаний перегляд diff.whitespace_button=Пробіли @@ -1146,6 +1480,11 @@ diff.whitespace_ignore_at_eol=Ігнорувати зміни у пробіла diff.stats_desc= %d змінених файлів з %d додано та %d видалено diff.bin=BIN diff.view_file=Переглянути файл +diff.file_before=Перед +diff.file_after=Після +diff.file_image_width=Ширина +diff.file_image_height=Висота +diff.file_byte_size=Розмір diff.file_suppressed=Різницю між файлами не показано, бо вона завелика diff.too_many_files=Деякі файли не було показано, через те що забагато файлів було змінено diff.comment.placeholder=Залишити коментар @@ -1159,6 +1498,7 @@ diff.review.header=Надіслати рецензію diff.review.placeholder=Рецензійований коментарій diff.review.comment=Коментар diff.review.approve=Затвердити +diff.review.reject=Запит змін releases.desc=Відслідковувати версії проекту (релізи) та завантаження. release.releases=Релізи @@ -1210,6 +1550,10 @@ branch.deleted_by=Видалено %s branch.restore_success=Гілку "%s" відновлено. branch.restore_failed=Не вдалося відновити гілку '%s'. branch.protected_deletion_failed=Гілка '%s' захищена. Її не можна видалити. +branch.restore=Відновити гілку '%s' +branch.download=Завантажити гілку '%s' +branch.included_desc=Ця гілка є частиною типової гілки +branch.included=Включено topic.manage_topics=Керувати тематичними мітками topic.done=Готово @@ -1233,6 +1577,7 @@ team_name=Назва команди team_desc=Опис team_name_helper=Назва команди має бути простою та зрозумілою. team_desc_helper=Опишіть мету або роль команди. +team_access_desc=Доступ до репозиторія team_permission_desc=Права доступу team_unit_desc=Дозволити доступ до розділів репозиторію @@ -1245,6 +1590,12 @@ settings.options=Організація settings.full_name=Повне ім'я settings.website=Веб-сайт settings.location=Розташування +settings.permission=Дозволи +settings.repoadminchangeteam=Адміністратор репозитарію може додавати та видаляти доступ для команд +settings.visibility=Видимість +settings.visibility.public=Публічний +settings.visibility.limited=Обмежений (Видимий лише для користувачів, що ввійшли в систему) +settings.visibility.private=Приватний (Видимий лише членам організації) settings.update_settings=Оновити налаштування settings.update_setting_success=Налаштування організації оновлені. @@ -1273,6 +1624,8 @@ members.invite_now=Запросити зараз teams.join=Приєднатися teams.leave=Покинути +teams.can_create_org_repo=Створити репозиторії +teams.can_create_org_repo_helper=Учасники можуть створювати нові репозиторії в організації. Автор отримає доступ адміністратора до нового репозиторію. teams.read_access=Доступ для читання teams.read_access_helper=Учасники можуть переглядати та клонувати репозиторії команд. teams.write_access=Доступ на запис @@ -1292,16 +1645,31 @@ teams.delete_team_success=Команду було видалено. teams.read_permission_desc=Ця команда має доступ для читання: учасники можуть переглядати та клонувати репозиторії. teams.write_permission_desc=Ця команда надає доступ на запис: учасники можуть отримувати й виконувати push команди до репозитрію. teams.admin_permission_desc=Ця команда надає адміністраторський доступ: учасники можуть читати, виконувати push команди та додавати співробітників до репозиторію. +teams.create_repo_permission_desc=Крім того, ця команда надає дозвіл Створити репозиторій: учасники можуть створювати нові репозиторії в організації. teams.repositories=Репозиторії команди teams.search_repo_placeholder=Пошук репозиторію… +teams.remove_all_repos_title=Видалити всі репозиторії команди +teams.remove_all_repos_desc=Це видалить усі репозиторії команди. +teams.add_all_repos_title=Додати всі репозиторії +teams.add_all_repos_desc=Це додасть всі репозиторії організації до команди. teams.add_nonexistent_repo=Ви намагаєтеся додати у репозиторій якого не існує. Будь ласка, спочатку створіть його. teams.add_duplicate_users=Користувач уже є членом команди. +teams.repos.none=Для команди немає доступних репозиторіїв. +teams.members.none=Немає членів в цій команді. +teams.specific_repositories=Конкретні репозиторії +teams.specific_repositories_helper=Учасники матимуть доступ лише до репозиторіїв, які були явно додані до команди. Вибір цього пункту не призводить до автоматичного видалення репозиторіїв, доданих з Всі репозиторії. +teams.all_repositories=Всі репозиторії +teams.all_repositories_helper=Команда має доступ до всіх репозиторіїв. Вибір цього пункту додасть всі наявні репозиторії до команди. +teams.all_repositories_read_permission_desc=Ця команда надає дозвіл Перегляд для всіх репозиторіїв: учасники можуть переглядати та клонувати їх. +teams.all_repositories_write_permission_desc=Ця команда надає дозвіл Запис для всіх репозиторіїв: учасники можуть переглядати та виконувати push в репозиторіях. +teams.all_repositories_admin_permission_desc=Ця команда надає дозвіл Адміністрування для всіх репозиторіїв: учасники можуть переглядати, виконувати push та додавати співробітників. [admin] dashboard=Панель управління users=Облікові записи користувачів organizations=Організації repositories=Репозиторії +hooks=Типові веб-хуки authentication=Джерела автентифікації config=Конфігурація notices=Сповіщення системи @@ -1325,6 +1693,8 @@ dashboard.delete_repo_archives=Видалити всі архіви репози dashboard.delete_repo_archives_success=Всі архіви репозиторіїв були видалені. dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git dashboard.delete_missing_repos_success=Всі записи про репозиторії з відсутніми файлами Git видалені. +dashboard.delete_generated_repository_avatars=Видалити репозиторій з згенерованими аватарами +dashboard.delete_generated_repository_avatars_success=Репозиторій з згенерованими аватарами видалено. dashboard.git_gc_repos=Виконати очистку сміття для всіх репозиторіїв dashboard.git_gc_repos_started=Всі репозиторії завершили збирання сміття. dashboard.resync_all_sshkeys=Перезаписати файл '.ssh/authorized_keys' для SSH ключів Gitea. (Не потрібно для вбудованого SSH сервера.) @@ -1385,6 +1755,7 @@ users.auth_login_name=Логін для авторизації users.password_helper=Залиште пароль порожнім, щоб не змінювати його. users.update_profile_success=Обліковий запис користувача було оновлено. users.edit_account=Редагувати обліковий запис +users.max_repo_creation=Максимальне число репозиторіїв users.max_repo_creation_desc=(Введіть -1, щоб використовувати глобальний ліміт за замовчуванням.) users.is_activated=Обліковий запис користувача увімкнено users.prohibit_login=Вимкнути вхід @@ -1414,6 +1785,9 @@ repos.forks=Форки repos.issues=Проблеми repos.size=Розмір +hooks.desc=Веб-хуки автоматично створюють HTTP POST-запити до сервера, коли виконуються певні події Gitea. Визначені тут веб-хуки є типовими і копіюються у всі нові сховища. Детальніше читайте в інструкції по використанню web-хуків. +hooks.add_webhook=Додати типовий веб-хук +hooks.update_webhook=Змінити типовий веб-хук auths.auth_manage_panel=Керування джерелом автентифікації auths.new=Додати джерело автентифікації @@ -1463,6 +1837,16 @@ auths.oauth2_authURL=URL авторизації auths.oauth2_profileURL=URL профілю auths.oauth2_emailURL=URL електронної пошти auths.enable_auto_register=Увімкнути автоматичну реєстрацію +auths.sspi_auto_create_users=Автоматично створювати користувачів +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" стануть "user"). +auths.sspi_separator_replacement=Використовувати замість \, / та @ роздільник +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 адреса переадресації по завершенні автентифікації повинена виглядати так:/user/oauth2//callback @@ -1475,6 +1859,8 @@ auths.tip.gitlab=Додайте новий додаток на https://gitlab.co auths.tip.google_plus=Отримайте облікові дані клієнта OAuth2 в консолі Google API на сторінці https://console.developers.google.com/ auths.tip.openid_connect=Використовуйте OpenID Connect Discovery URL (/.well-known/openid-configuration) для автоматичної настройки входу OAuth auths.tip.twitter=Перейдіть на https://dev.twitter.com/apps, створіть програму і переконайтеся, що включена опція «Дозволити цю програму для входу в систему за допомогою Twitter» +auths.tip.discord=Зареєструйте новий додаток на https://discordapp.com/developers/applications/me +auths.tip.gitea=Зареєструйте новий додаток OAuth2. Керівництво можна знайти на https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=Редагувати джерело автентифікації auths.activated=Ця аутентифікація активована auths.new_success=Метод аутентифікації '%s' був доданий. @@ -1486,12 +1872,14 @@ 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=Конфігурація сервера config.app_name=Назва сайту config.app_ver=Версія Gitea config.app_url=Базова URL-адреса Gitea config.custom_conf=Шлях до файлу конфігурації +config.custom_file_root_path=Шлях до файлу користувача config.domain=Домен SSH сервера config.offline_mode=Локальний режим config.disable_router_log=Вимкнути логування роутеру @@ -1517,6 +1905,10 @@ config.ssh_keygen_path=Шлях до генератора ключів ('ssh-key config.ssh_minimum_key_size_check=Мінімальний розмір ключа перевірки config.ssh_minimum_key_sizes=Мінімальні розміри ключів +config.lfs_config=Конфігурація LFS +config.lfs_enabled=Увімкнено +config.lfs_content_path=Шлях до контенту LFS +config.lfs_http_auth_expiry=Застаріла LFS HTTP аунтифікація config.db_config=Конфігурація бази даних config.db_type=Тип @@ -1538,12 +1930,14 @@ config.mail_notify=Увімкнути сповіщення електронно config.disable_key_size_check=Вимкнути перевірку мінімального розміру ключа config.enable_captcha=Увімкнути CAPTCHA config.active_code_lives=Час актуальності кода підтвердження +config.reset_password_code_lives=Відновлення часу закінчення терміну дії коду облікового запису 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.default_visibility_organization=Видимість за замовчуванням для нових організацій config.default_enable_dependencies=Увімкнути залежності проблем за замовчуванням config.webhook_config=Конфігурація web-хуків @@ -1571,6 +1965,7 @@ config.cache_config=Конфігурація кешу config.cache_adapter=Адаптер кешу config.cache_interval=Інтервал кешування config.cache_conn=Підключення до кешу +config.cache_item_ttl=Час зберігання даних кешу config.session_config=Конфігурація сесії config.session_provider=Провайдер сесії @@ -1601,6 +1996,16 @@ config.git_gc_timeout=Тайм-аут операції збирача смітт config.log_config=Конфігурація журналу config.log_mode=Режим журналювання +config.macaron_log_mode=Macaron-режим запису журналу +config.own_named_logger=Іменований журнал +config.routes_to_default_logger=Перенаправити в стандартний журнал +config.go_log=Використовувати Go-журнал (перенаправлення на стандартний) +config.router_log_mode=Маршрутизований запис журналу +config.disabled_logger=Вимкнено +config.access_log_mode=Режим доступу до журналу +config.access_log_template=Шаблон +config.xorm_log_mode=XORM-режим запису журналу +config.xorm_log_sql=Журнал SQL monitor.cron=Завдання cron monitor.name=Ім'я @@ -1612,6 +2017,11 @@ monitor.process=Запущені процеси monitor.desc=Опис monitor.start=Час початку monitor.execute_time=Час виконання +monitor.process.cancel=Зупинити процес +monitor.process.cancel_desc=Зупинка процесу може призвести до втрати даних +monitor.process.cancel_notices=Зупинити: %s? + + notices.system_notice_list=Сповіщення системи notices.view_detail_header=Переглянути деталі повідомлення @@ -1638,15 +2048,19 @@ create_pull_request=`створив(ла) запити на злиття %s#%[2]s` reopen_pull_request=`повторно відкрито запит на злиття %s#%[2]s` comment_issue=`прокоментував(ла) проблему %s#%[2]s` +comment_pull=`прокоментував запит на злиття %s#%[2]s` merge_pull_request=`злив(ла) запит на злиття %s#%[2]s` transfer_repo=перенесено репозиторій %s у %s push_tag=створено тег %[2]s в %[3]s delete_tag=видалено мітку %[2]s з %[3]s delete_branch=видалено гілку %[2]s з %[3]s compare_commits=Порівняти %d комітів +compare_commits_general=Порівняти коміти mirror_sync_push=синхронізовано коміти %[3]s в %[4]s із дзеркала mirror_sync_create=синхронізовано нове посилання %[2]s на %[3]s із дзеркала mirror_sync_delete=синхронізовано й видалено посилання %[2]s на %[3]s із дзеркала +approve_pull_request=`схвалив %s#%[2]s` +reject_pull_request=`запропонував зміни до %s#%[2]s` [tool] ago=%s тому @@ -1688,12 +2102,15 @@ mark_as_unread=Позначити як непрочитане mark_all_as_read=Позначити всі як прочитані [gpg] +default_key=Підписано типовим ключем error.extract_sign=Не вдалося витягти підпис error.generate_hash=Не вдалося згенерувати хеш коміту error.no_committer_account=Аккаунт користувача з таким Email не знайдено error.no_gpg_keys_found=Не вдалося знайти GPG ключ що відповідає даному підпису error.not_signed_commit=Непідписаний коміт error.failed_retrieval_gpg_keys=Не вдалося отримати відповідний GPG ключ користувача +error.probable_bad_signature=УВАГА! Хоча ключ з таким ID і є в базі, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ. +error.probable_bad_default_signature=УВАГА! Хоча типовий ключ має цей ID, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ. [units] error.no_unit_allowed_repo=У вас немає доступу до жодного розділу цього репозитория. diff --git a/options/locale/locale_vi-VN.ini b/options/locale/locale_vi-VN.ini index 6706b9ca2b07..d1eaa8e979aa 100644 --- a/options/locale/locale_vi-VN.ini +++ b/options/locale/locale_vi-VN.ini @@ -103,6 +103,8 @@ + + diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3b715a0c7ad1..e84701b68d9c 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -10,6 +10,7 @@ link_account=链接帐户 register=注册 website=官方网站 version=当前版本 +powered_by=Powered by %s page=页面 template=模板 language=语言选项 @@ -959,6 +960,7 @@ issues.add_time=手动添加时间 issues.add_time_short=添加时间 issues.add_time_cancel=取消 issues.add_time_history=`添加耗时 %s` +issues.del_time_history=`已删除时间 %s` issues.add_time_hours=小时 issues.add_time_minutes=分钟 issues.add_time_sum_to_small=没有输入时间。 @@ -1052,6 +1054,7 @@ pulls.is_checking=正在进行合并冲突检测,请稍后再试。 pulls.required_status_check_failed=一些必要的检查没有成功。 pulls.required_status_check_administrator=作为管理员,您仍可合并此合并请求。 pulls.blocked_by_approvals=此合并请求没有通过审批。已获取审批数%d个,共需要审批数%d个。 +pulls.blocked_by_rejection=此合并请求有官方审核员请求的更改。 pulls.can_auto_merge_desc=该合并请求可以进行自动合并操作。 pulls.cannot_auto_merge_desc=该合并请求存在冲突,无法进行自动合并操作。 pulls.cannot_auto_merge_helper=手动合并解决此冲突 @@ -1415,6 +1418,8 @@ settings.update_protect_branch_success=分支 "%s" 的分支保护已更新。 settings.remove_protected_branch_success=分支 "%s" 的分支保护已被禁用。 settings.protected_branch_deletion=禁用分支保护 settings.protected_branch_deletion_desc=禁用分支保护允许具有写入权限的用户推送提交到此分支。继续? +settings.block_rejected_reviews=拒绝审核阻止了此合并 +settings.block_rejected_reviews_desc=如果官方审查人员要求作出改动,即使有足够的批准,合并也不允许。 settings.default_branch_desc=请选择一个默认的分支用于合并请求和提交: settings.choose_branch=选择一个分支... settings.no_protected_branch=没有受保护的分支 @@ -2020,6 +2025,54 @@ monitor.execute_time=执行时长 monitor.process.cancel=中止进程 monitor.process.cancel_desc=中止一个进程可能导致数据丢失 monitor.process.cancel_notices=中止:%s ? +monitor.queues=队列 +monitor.queue=队列: %s +monitor.queue.name=名称 +monitor.queue.type=类型 +monitor.queue.exemplar=数据类型 +monitor.queue.numberworkers=工作者数量 +monitor.queue.maxnumberworkers=最大工作者数量 +monitor.queue.review=查看配置 +monitor.queue.review_add=查看/添加工作者 +monitor.queue.configuration=初始配置 +monitor.queue.nopool.title=没有工作者池 +monitor.queue.nopool.desc=此队列包装其它队列,本身没有工作者池。 +monitor.queue.wrapped.desc=一个包装队列包装一个启动缓慢队列,缓存队列请求到 channel 中。它本身没有一个工作者池。 +monitor.queue.persistable-channel.desc=一个 persistable-channel 队列包装2个队列,一个 channel 队列拥有自己的工作者池,一个 level 队列用于永久存储。它没有自己的工作者池。 +monitor.queue.pool.timeout=超时 +monitor.queue.pool.addworkers.title=新增工作者 +monitor.queue.pool.addworkers.submit=新增工作者 +monitor.queue.pool.addworkers.desc=添加工作者到此池中。如果你设置了超时,这些工作者将会在超时结束后从池中移除。 +monitor.queue.pool.addworkers.numberworkers.placeholder=工作者数量 +monitor.queue.pool.addworkers.timeout.placeholder=设置为0则无超时 +monitor.queue.pool.addworkers.mustnumbergreaterzero=要添加的工作者数量必须大于等于0 +monitor.queue.pool.addworkers.musttimeoutduration=超时时间必须为Go语言时间间隔。例如 5m 或者 0 + +monitor.queue.settings.title=池设置 +monitor.queue.settings.desc=池动态增长以应对队列阻塞。这些变更将不会影响当前的工作者组。 +monitor.queue.settings.timeout=提高超时时间 +monitor.queue.settings.timeout.placeholder=当前 %[1]v +monitor.queue.settings.timeout.error=超时时间必须为Go语言时间间隔。例如 5m 或者 0 +monitor.queue.settings.numberworkers=提高工作者数量 +monitor.queue.settings.numberworkers.placeholder=当前 %[1]d +monitor.queue.settings.numberworkers.error=要添加的工作者数量必须大于等于0 +monitor.queue.settings.maxnumberworkers=最大工作者数量 +monitor.queue.settings.maxnumberworkers.placeholder=当前 %[1]d +monitor.queue.settings.maxnumberworkers.error=最大工作者数必须是数字 +monitor.queue.settings.submit=更新设置 +monitor.queue.settings.changed=设置已更新 +monitor.queue.settings.blocktimeout=当前阻塞超时时间 +monitor.queue.settings.blocktimeout.value=%[1]v + +monitor.queue.pool.none=此队列没有工作者池 +monitor.queue.pool.added=工作者组添加成功 +monitor.queue.pool.max_changed=最大工作者数量已更改 +monitor.queue.pool.workers.title=活跃的工作者组 +monitor.queue.pool.workers.none=没有工作者组。 +monitor.queue.pool.cancel=停止工作者组 +monitor.queue.pool.cancelling=工作者组正在关闭 +monitor.queue.pool.cancel_notices=关掉这组 %s 工作者吗? +monitor.queue.pool.cancel_desc=没有工作者组的队列将会引起请求永久阻塞。 notices.system_notice_list=系统提示管理 notices.view_detail_header=查看提示详情 @@ -2046,6 +2099,7 @@ create_pull_request=`创建了合并请求 %s#%[2]s` close_pull_request=`关闭了合并请求 %s#%[2]s` reopen_pull_request=`重新开启了合并请求 %s#%[2]s` comment_issue=`评论了工单 %s#%[2]s` +comment_pull=`评论了合并请求 %s#%[2]s` merge_pull_request=`合并了合并请求 %s#%[2]s` transfer_repo=将仓库 %s 转移至 %s push_tag=推送了标签 %[2]s%[3]s diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 6d1bb734e371..662bab934d60 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -795,6 +795,8 @@ monitor.desc=進程描述 monitor.start=開始時間 monitor.execute_time=已執行時間 + + notices.system_notice_list=系統提示管理 notices.view_detail_header=查看提示細節 notices.actions=操作 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 1d91f1e5b655..0e9430a09e0a 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1329,6 +1329,8 @@ monitor.desc=進程描述 monitor.start=開始時間 monitor.execute_time=已執行時間 + + notices.system_notice_list=系統提示管理 notices.view_detail_header=查看提示細節 notices.actions=操作 diff --git a/routers/admin/admin.go b/routers/admin/admin.go index a1078ff1ede7..be18d013deff 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "runtime" + "strconv" "strings" "time" @@ -22,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/services/mailer" @@ -35,6 +37,7 @@ const ( tplDashboard base.TplName = "admin/dashboard" tplConfig base.TplName = "admin/config" tplMonitor base.TplName = "admin/monitor" + tplQueue base.TplName = "admin/queue" ) var ( @@ -355,6 +358,7 @@ func Monitor(ctx *context.Context) { ctx.Data["PageIsAdminMonitor"] = true ctx.Data["Processes"] = process.GetManager().Processes() ctx.Data["Entries"] = cron.ListTasks() + ctx.Data["Queues"] = queue.GetManager().ManagedQueues() ctx.HTML(200, tplMonitor) } @@ -366,3 +370,126 @@ func MonitorCancel(ctx *context.Context) { "redirect": ctx.Repo.RepoLink + "/admin/monitor", }) } + +// Queue shows details for a specific queue +func Queue(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + ctx.Data["Title"] = ctx.Tr("admin.monitor.queue", mq.Name) + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminMonitor"] = true + ctx.Data["Queue"] = mq + ctx.HTML(200, tplQueue) +} + +// WorkerCancel cancels a worker group +func WorkerCancel(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + pid := ctx.ParamsInt64("pid") + mq.CancelWorkers(pid) + ctx.Flash.Info(ctx.Tr("admin.monitor.queue.pool.cancelling")) + ctx.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid), + }) +} + +// AddWorkers adds workers to a worker group +func AddWorkers(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + number := ctx.QueryInt("number") + if number < 1 { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.addworkers.mustnumbergreaterzero")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + timeout, err := time.ParseDuration(ctx.Query("timeout")) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.addworkers.musttimeoutduration")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + if mq.Pool == nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.none")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + mq.AddWorkers(number, timeout) + ctx.Flash.Success(ctx.Tr("admin.monitor.queue.pool.added")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) +} + +// SetQueueSettings sets the maximum number of workers and other settings for this queue +func SetQueueSettings(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + if mq.Pool == nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.none")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + + maxNumberStr := ctx.Query("max-number") + numberStr := ctx.Query("number") + timeoutStr := ctx.Query("timeout") + + var err error + var maxNumber, number int + var timeout time.Duration + if len(maxNumberStr) > 0 { + maxNumber, err = strconv.Atoi(maxNumberStr) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.maxnumberworkers.error")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + if maxNumber < -1 { + maxNumber = -1 + } + } else { + maxNumber = mq.MaxNumberOfWorkers() + } + + if len(numberStr) > 0 { + number, err = strconv.Atoi(numberStr) + if err != nil || number < 0 { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.numberworkers.error")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + } else { + number = mq.BoostWorkers() + } + + if len(timeoutStr) > 0 { + timeout, err = time.ParseDuration(timeoutStr) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.timeout.error")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) + return + } + } else { + timeout = mq.Pool.BoostTimeout() + } + + mq.SetSettings(maxNumber, number, timeout) + ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.changed")) + ctx.Redirect(setting.AppSubURL + fmt.Sprintf("/admin/monitor/queue/%d", qid)) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0bb5320b1602..e4288f40f650 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -664,10 +664,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("", reqToken()). Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment). Delete(repo.DeleteIssueComment) - m.Combo("/reactions", reqToken()). + m.Combo("/reactions"). Get(repo.GetIssueCommentReactions). - Post(bind(api.EditReactionOption{}), repo.PostIssueCommentReaction). - Delete(bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction) + Post(bind(api.EditReactionOption{}), reqToken(), repo.PostIssueCommentReaction). + Delete(bind(api.EditReactionOption{}), reqToken(), repo.DeleteIssueCommentReaction) }) }) m.Group("/:index", func() { @@ -704,10 +704,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Put("/:user", reqToken(), repo.AddIssueSubscription) m.Delete("/:user", reqToken(), repo.DelIssueSubscription) }) - m.Combo("/reactions", reqToken()). + m.Combo("/reactions"). Get(repo.GetIssueReactions). - Post(bind(api.EditReactionOption{}), repo.PostIssueReaction). - Delete(bind(api.EditReactionOption{}), repo.DeleteIssueReaction) + Post(bind(api.EditReactionOption{}), reqToken(), repo.PostIssueReaction). + Delete(bind(api.EditReactionOption{}), reqToken(), repo.DeleteIssueReaction) }) }, mustEnableIssuesOrPulls) m.Group("/labels", func() { diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 4396e6faaebe..ad82d53e7a19 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -524,8 +524,8 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { } } - if err = models.UpdateIssue(issue); err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateIssue", err) + if err = models.UpdateIssueByAPI(issue); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) return } if form.State != nil { @@ -542,7 +542,11 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { // Refetch from database to assign some automatic values issue, err = models.GetIssueByID(issue.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetIssueByID", err) + ctx.InternalServerError(err) + return + } + if err = issue.LoadMilestone(); err != nil { + ctx.InternalServerError(err) return } ctx.JSON(http.StatusCreated, issue.APIFormat()) diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index bbc767cc99ad..b943ea6980ab 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -13,11 +13,11 @@ import ( api "code.gitea.io/gitea/modules/structs" ) -// GetIssueCommentReactions list reactions of a issue comment +// GetIssueCommentReactions list reactions of a comment from an issue func GetIssueCommentReactions(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions // --- - // summary: Get a list reactions of a issue comment + // summary: Get a list of reactions from a comment of an issue // consumes: // - application/json // produces: @@ -55,7 +55,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) { return } - if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if !ctx.Repo.CanRead(models.UnitTypeIssues) { ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions")) return } @@ -83,11 +83,11 @@ func GetIssueCommentReactions(ctx *context.APIContext) { ctx.JSON(http.StatusOK, result) } -// PostIssueCommentReaction add a reaction to a comment of a issue +// PostIssueCommentReaction add a reaction to a comment of an issue func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) { // swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction // --- - // summary: Add a reaction to a comment of a issue comment + // summary: Add a reaction to a comment of an issue // consumes: // - application/json // produces: @@ -124,11 +124,11 @@ func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOpti changeIssueCommentReaction(ctx, form, true) } -// DeleteIssueCommentReaction list reactions of a issue comment +// DeleteIssueCommentReaction remove a reaction from a comment of an issue func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) { // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction // --- - // summary: Remove a reaction from a comment of a issue comment + // summary: Remove a reaction from a comment of an issue // consumes: // - application/json // produces: @@ -179,7 +179,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err) } - if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) { ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction")) return } @@ -219,11 +219,11 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp } } -// GetIssueReactions list reactions of a issue comment +// GetIssueReactions list reactions of an issue func GetIssueReactions(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions // --- - // summary: Get a list reactions of a issue + // summary: Get a list reactions of an issue // consumes: // - application/json // produces: @@ -261,7 +261,7 @@ func GetIssueReactions(ctx *context.APIContext) { return } - if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if !ctx.Repo.CanRead(models.UnitTypeIssues) { ctx.Error(http.StatusForbidden, "GetIssueReactions", errors.New("no permission to get reactions")) return } @@ -289,11 +289,11 @@ func GetIssueReactions(ctx *context.APIContext) { ctx.JSON(http.StatusOK, result) } -// PostIssueReaction add a reaction to a comment of a issue +// PostIssueReaction add a reaction to an issue func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) { // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction // --- - // summary: Add a reaction to a comment of a issue + // summary: Add a reaction to an issue // consumes: // - application/json // produces: @@ -330,11 +330,11 @@ func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) { changeIssueReaction(ctx, form, true) } -// DeleteIssueReaction list reactions of a issue comment +// DeleteIssueReaction remove a reaction from an issue func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) { // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction // --- - // summary: Remove a reaction from a comment of a issue + // summary: Remove a reaction from an issue // consumes: // - application/json // produces: @@ -380,7 +380,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i return } - if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { + if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) { ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction")) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 0392eb8e8c48..d0551320fdbc 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -450,8 +450,8 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { } } - if err = models.UpdateIssue(issue); err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateIssue", err) + if err = models.UpdateIssueByAPI(issue); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) return } if form.State != nil { diff --git a/routers/home.go b/routers/home.go index d223054f4ce3..773e0f3d6beb 100644 --- a/routers/home.go +++ b/routers/home.go @@ -142,6 +142,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { Keyword: keyword, OwnerID: opts.OwnerID, AllPublic: true, + AllLimited: true, TopicOnly: topicOnly, IncludeDescription: setting.UI.SearchRepoDescription, }) diff --git a/routers/private/hook.go b/routers/private/hook.go index dc5001ad4e5a..c1e283f357ec 100644 --- a/routers/private/hook.go +++ b/routers/private/hook.go @@ -113,6 +113,13 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { }) return } + if protectBranch.MergeBlockedByRejectedReview(pr) { + log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d has requested changes", opts.UserID, branchName, repo, pr.Index) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d has requested changes", branchName, opts.ProtectedBranchID), + }) + return + } } else if !canPush { log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", opts.UserID, branchName, repo) ctx.JSON(http.StatusForbidden, map[string]interface{}{ diff --git a/routers/private/serv.go b/routers/private/serv.go index 64fd671309ff..c769f30d07ca 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" repo_service "code.gitea.io/gitea/services/repository" + wiki_service "code.gitea.io/gitea/services/wiki" "gitea.com/macaron/macaron" ) @@ -320,7 +321,7 @@ func ServCommand(ctx *macaron.Context) { // Finally if we're trying to touch the wiki we should init it if results.IsWiki { - if err = repo.InitWiki(); err != nil { + if err = wiki_service.InitWiki(repo); err != nil { log.Error("Failed to initialize the wiki in %-v Error: %v", repo, err) ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "results": results, diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index 0d496230e16a..96dc28a23a9d 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -6,6 +6,8 @@ package repo import ( "fmt" + "net/http" + "os" "strings" "code.gitea.io/gitea/models" @@ -85,3 +87,57 @@ func DeleteAttachment(ctx *context.Context) { "uuid": attach.UUID, }) } + +// GetAttachment serve attachements +func GetAttachment(ctx *context.Context) { + attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid")) + if err != nil { + if models.IsErrAttachmentNotExist(err) { + ctx.Error(404) + } else { + ctx.ServerError("GetAttachmentByUUID", err) + } + return + } + + repository, unitType, err := attach.LinkedRepository() + if err != nil { + ctx.ServerError("LinkedRepository", err) + return + } + + if repository == nil { //If not linked + if !(ctx.IsSigned && attach.UploaderID == ctx.User.ID) { //We block if not the uploader + ctx.Error(http.StatusNotFound) + return + } + } else { //If we have the repository we check access + perm, err := models.GetUserRepoPermission(repository, ctx.User) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) + return + } + if !perm.CanRead(unitType) { + ctx.Error(http.StatusNotFound) + return + } + } + + //If we have matched and access to release or issue + fr, err := os.Open(attach.LocalPath()) + if err != nil { + ctx.ServerError("Open", err) + return + } + defer fr.Close() + + if err := attach.IncreaseDownloadCount(); err != nil { + ctx.ServerError("Update", err) + return + } + + if err = ServeData(ctx, attach.Name, fr); err != nil { + ctx.ServerError("ServeData", err) + return + } +} diff --git a/routers/repo/branch.go b/routers/repo/branch.go index b0a1efc5b92b..df6e0bf21f6c 100644 --- a/routers/repo/branch.go +++ b/routers/repo/branch.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/util" + "gopkg.in/src-d/go-git.v4/plumbing" ) const ( @@ -33,6 +34,7 @@ type Branch struct { CommitsAhead int CommitsBehind int LatestPullRequest *models.PullRequest + MergeMovedOn bool } // Branches render repository branch page @@ -185,6 +187,12 @@ func loadBranches(ctx *context.Context) []*Branch { return nil } + repoIDToRepo := map[int64]*models.Repository{} + repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository + + repoIDToGitRepo := map[int64]*git.Repository{} + repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo + branches := make([]*Branch, len(rawBranches)) for i := range rawBranches { commit, err := rawBranches[i].GetCommit() @@ -213,11 +221,46 @@ func loadBranches(ctx *context.Context) []*Branch { ctx.ServerError("GetLatestPullRequestByHeadInfo", err) return nil } + headCommit := commit.ID.String() + + mergeMovedOn := false if pr != nil { + pr.HeadRepo = ctx.Repo.Repository if err := pr.LoadIssue(); err != nil { ctx.ServerError("pr.LoadIssue", err) return nil } + if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok { + pr.BaseRepo = repo + } else if err := pr.LoadBaseRepo(); err != nil { + ctx.ServerError("pr.LoadBaseRepo", err) + return nil + } else { + repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo + } + + if pr.HasMerged { + baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID] + if !ok { + baseGitRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil + } + defer baseGitRepo.Close() + repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo + } + pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil && err != plumbing.ErrReferenceNotFound { + ctx.ServerError("GetBranchCommitID", err) + return nil + } + if err == nil && headCommit != pullCommit { + // the head has moved on from the merge - we shouldn't delete + mergeMovedOn = true + } + } + } isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName @@ -230,6 +273,7 @@ func loadBranches(ctx *context.Context) []*Branch { CommitsAhead: divergence.Ahead, CommitsBehind: divergence.Behind, LatestPullRequest: pr, + MergeMovedOn: mergeMovedOn, } } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 67c4ee378819..0a78e06b41ac 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" - "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -963,10 +962,14 @@ func ViewIssue(ctx *context.Context) { } if pull.ProtectedBranch != nil { cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull) - ctx.Data["IsBlockedByApprovals"] = pull.ProtectedBranch.RequiredApprovals > 0 && cnt < pull.ProtectedBranch.RequiredApprovals + ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull) + ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull) ctx.Data["GrantedApprovals"] = cnt } - ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) + ctx.Data["IsPullBranchDeletable"] = canDelete && + pull.HeadRepo != nil && + git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) && + (!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"]) ctx.Data["PullReviewers"], err = models.GetReviewersByIssueID(issue.ID) if err != nil { @@ -1177,13 +1180,11 @@ func UpdateIssueAssignee(ctx *context.Context) { return } - removed, comment, err := issue_service.ToggleAssignee(issue, ctx.User, assigneeID) + _, _, err = issue_service.ToggleAssignee(issue, ctx.User, assigneeID) if err != nil { ctx.ServerError("ToggleAssignee", err) return } - - notification.NotifyIssueChangeAssignee(ctx.User, issue, assignee, removed, comment) } } ctx.JSON(200, map[string]interface{}{ diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 418c2e9438bd..1a5c4a036f24 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -330,25 +330,37 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare repo := ctx.Repo.Repository pull := issue.PullRequest - var err error - if err = pull.GetHeadRepo(); err != nil { + if err := pull.GetHeadRepo(); err != nil { ctx.ServerError("GetHeadRepo", err) return nil } + if err := pull.GetBaseRepo(); err != nil { + ctx.ServerError("GetBaseRepo", err) + return nil + } + setMergeTarget(ctx, pull) - if err = pull.LoadProtectedBranch(); err != nil { + if err := pull.LoadProtectedBranch(); err != nil { ctx.ServerError("GetLatestCommitStatus", err) return nil } ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck - var headGitRepo *git.Repository + baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil + } + defer baseGitRepo.Close() var headBranchExist bool + var headBranchSha string // HeadRepo may be missing if pull.HeadRepo != nil { - headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath()) + var err error + + headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath()) if err != nil { ctx.ServerError("OpenRepository", err) return nil @@ -358,46 +370,53 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) if headBranchExist { - sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch) + headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch) if err != nil { ctx.ServerError("GetBranchCommitID", err) return nil } + } + } - commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0) - if err != nil { - ctx.ServerError("GetLatestCommitStatus", err) - return nil - } - if len(commitStatuses) > 0 { - ctx.Data["LatestCommitStatuses"] = commitStatuses - ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) - } + sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName()) + if err != nil { + ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) + return nil + } - if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { - ctx.Data["is_context_required"] = func(context string) bool { - for _, c := range pull.ProtectedBranch.StatusCheckContexts { - if c == context { - return true - } - } - return false + commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0) + if err != nil { + ctx.ServerError("GetLatestCommitStatus", err) + return nil + } + if len(commitStatuses) > 0 { + ctx.Data["LatestCommitStatuses"] = commitStatuses + ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses) + } + + if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck { + ctx.Data["is_context_required"] = func(context string) bool { + for _, c := range pull.ProtectedBranch.StatusCheckContexts { + if c == context { + return true } - ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) } + return false } + ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts) } - if pull.HeadRepo == nil || !headBranchExist { + ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha + ctx.Data["HeadBranchCommitID"] = headBranchSha + ctx.Data["PullHeadCommitID"] = sha + + if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha { ctx.Data["IsPullRequestBroken"] = true ctx.Data["HeadTarget"] = "deleted" - ctx.Data["NumCommits"] = 0 - ctx.Data["NumFiles"] = 0 - return nil } - compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name), - pull.BaseBranch, pull.HeadBranch) + compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), + pull.BaseBranch, pull.GetGitRefName()) if err != nil { if strings.Contains(err.Error(), "fatal: Not a valid object name") { ctx.Data["IsPullRequestBroken"] = true diff --git a/routers/repo/repo.go b/routers/repo/repo.go index d44812729fe4..d6d91f084341 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -180,6 +180,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { return } + var repo *models.Repository var err error if form.RepoTemplate > 0 { opts := models.GenerateRepoOptions{ @@ -209,14 +210,14 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { return } - repo, err := repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts) + repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts) if err == nil { log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) return } } else { - repo, err := repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ Name: form.RepoName, Description: form.Description, Gitignores: form.Gitignores, diff --git a/routers/repo/setting_protected_branch.go b/routers/repo/setting_protected_branch.go index c279c94b1b64..8872c7471fac 100644 --- a/routers/repo/setting_protected_branch.go +++ b/routers/repo/setting_protected_branch.go @@ -244,6 +244,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ",")) } } + protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ UserIDs: whitelistUsers, diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index 6cf194365891..b0bd8047b8b3 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + wiki_service "code.gitea.io/gitea/services/wiki" ) const ( @@ -124,7 +125,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) { var entry *git.TreeEntry var err error - pageFilename := models.WikiNameToFilename(wikiName) + pageFilename := wiki_service.NameToFilename(wikiName) if entry, err = findEntryForFile(commit, pageFilename); err != nil { ctx.ServerError("findEntryForFile", err) return nil, nil, "", false @@ -157,7 +158,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if !entry.IsRegular() { continue } - wikiName, err := models.WikiFilenameToName(entry.Name()) + wikiName, err := wiki_service.FilenameToName(entry.Name()) if err != nil { if models.IsErrWikiInvalidFileName(err) { continue @@ -172,17 +173,17 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { } pages = append(pages, PageMeta{ Name: wikiName, - SubURL: models.WikiNameToSubURL(wikiName), + SubURL: wiki_service.NameToSubURL(wikiName), }) } ctx.Data["Pages"] = pages // get requested pagename - pageName := models.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) if len(pageName) == 0 { pageName = "Home" } - ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName) + ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName) ctx.Data["old_title"] = pageName ctx.Data["Title"] = pageName ctx.Data["title"] = pageName @@ -243,11 +244,11 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } // get requested pagename - pageName := models.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) if len(pageName) == 0 { pageName = "Home" } - ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName) + ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName) ctx.Data["old_title"] = pageName ctx.Data["Title"] = pageName ctx.Data["title"] = pageName @@ -320,11 +321,11 @@ func renderEditPage(ctx *context.Context) { }() // get requested pagename - pageName := models.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) if len(pageName) == 0 { pageName = "Home" } - ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName) + ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName) ctx.Data["old_title"] = pageName ctx.Data["Title"] = pageName ctx.Data["title"] = pageName @@ -474,7 +475,7 @@ func WikiPages(ctx *context.Context) { ctx.ServerError("GetCommit", err) return } - wikiName, err := models.WikiFilenameToName(entry.Name()) + wikiName, err := wiki_service.FilenameToName(entry.Name()) if err != nil { if models.IsErrWikiInvalidFileName(err) { continue @@ -488,7 +489,7 @@ func WikiPages(ctx *context.Context) { } pages = append(pages, PageMeta{ Name: wikiName, - SubURL: models.WikiNameToSubURL(wikiName), + SubURL: wiki_service.NameToSubURL(wikiName), UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()), }) } @@ -528,7 +529,7 @@ func WikiRaw(ctx *context.Context) { providedPath = providedPath[:len(providedPath)-3] } - wikiPath := models.WikiNameToFilename(providedPath) + wikiPath := wiki_service.NameToFilename(providedPath) entry, err = findEntryForFile(commit, wikiPath) if err != nil { ctx.ServerError("findFile", err) @@ -576,8 +577,8 @@ func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) { return } - wikiName := models.NormalizeWikiName(form.Title) - if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil { + wikiName := wiki_service.NormalizeWikiName(form.Title) + if err := wiki_service.AddWikiPage(ctx.User, ctx.Repo.Repository, wikiName, form.Content, form.Message); err != nil { if models.IsErrWikiReservedName(err) { ctx.Data["Err_Title"] = true ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form) @@ -590,7 +591,7 @@ func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) { return } - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName)) + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(wikiName)) } // EditWiki render wiki modify page @@ -623,25 +624,25 @@ func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) { return } - oldWikiName := models.NormalizeWikiName(ctx.Params(":page")) - newWikiName := models.NormalizeWikiName(form.Title) + oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + newWikiName := wiki_service.NormalizeWikiName(form.Title) - if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil { + if err := wiki_service.EditWikiPage(ctx.User, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil { ctx.ServerError("EditWikiPage", err) return } - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName)) + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(newWikiName)) } // DeleteWikiPagePost delete wiki page func DeleteWikiPagePost(ctx *context.Context) { - wikiName := models.NormalizeWikiName(ctx.Params(":page")) + wikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) if len(wikiName) == 0 { wikiName = "Home" } - if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil { + if err := wiki_service.DeleteWikiPage(ctx.User, ctx.Repo.Repository, wikiName); err != nil { ctx.ServerError("DeleteWikiPage", err) return } diff --git a/routers/repo/wiki_test.go b/routers/repo/wiki_test.go index 44fcd02035fe..843a8ddf2bb4 100644 --- a/routers/repo/wiki_test.go +++ b/routers/repo/wiki_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/test" + wiki_service "code.gitea.io/gitea/services/wiki" "github.com/stretchr/testify/assert" ) @@ -29,7 +30,7 @@ func wikiEntry(t *testing.T, repo *models.Repository, wikiName string) *git.Tree entries, err := commit.ListEntries() assert.NoError(t, err) for _, entry := range entries { - if entry.Name() == models.WikiNameToFilename(wikiName) { + if entry.Name() == wiki_service.NameToFilename(wikiName) { return entry } } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index b67d1909b490..8f9449f992bb 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -8,7 +8,6 @@ import ( "bytes" "encoding/gob" "net/http" - "os" "path" "text/template" "time" @@ -411,8 +410,16 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", adminReq, admin.Dashboard) m.Get("/config", admin.Config) m.Post("/config/test_mail", admin.SendTestMail) - m.Get("/monitor", admin.Monitor) - m.Post("/monitor/cancel/:pid", admin.MonitorCancel) + m.Group("/monitor", func() { + m.Get("", admin.Monitor) + m.Post("/cancel/:pid", admin.MonitorCancel) + m.Group("/queue/:qid", func() { + m.Get("", admin.Queue) + m.Post("/set", admin.SetQueueSettings) + m.Post("/add", admin.AddWorkers) + m.Post("/cancel/:pid", admin.WorkerCancel) + }) + }) m.Group("/users", func() { m.Get("", admin.Users) @@ -474,34 +481,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/following", user.Following) }) - m.Get("/attachments/:uuid", func(ctx *context.Context) { - attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid")) - if err != nil { - if models.IsErrAttachmentNotExist(err) { - ctx.Error(404) - } else { - ctx.ServerError("GetAttachmentByUUID", err) - } - return - } - - fr, err := os.Open(attach.LocalPath()) - if err != nil { - ctx.ServerError("Open", err) - return - } - defer fr.Close() - - if err := attach.IncreaseDownloadCount(); err != nil { - ctx.ServerError("Update", err) - return - } - - if err = repo.ServeData(ctx, attach.Name, fr); err != nil { - ctx.ServerError("ServeData", err) - return - } - }) + m.Get("/attachments/:uuid", repo.GetAttachment) }, ignSignIn) m.Group("/attachments", func() { diff --git a/routers/user/home.go b/routers/user/home.go index f57a1c90c33e..a219341850a2 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -193,9 +193,13 @@ func Milestones(ctx *context.Context) { ctx.ServerError("env.RepoIDs", err) return } + userRepoIDs, err = models.FilterOutRepoIdsWithoutUnitAccess(ctx.User, userRepoIDs, models.UnitTypeIssues, models.UnitTypePullRequests) + if err != nil { + ctx.ServerError("FilterOutRepoIdsWithoutUnitAccess", err) + return + } } else { - unitType := models.UnitTypeIssues - userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType) + userRepoIDs, err = ctxUser.GetAccessRepoIDs(models.UnitTypeIssues, models.UnitTypePullRequests) if err != nil { ctx.ServerError("ctxUser.GetAccessRepoIDs", err) return @@ -206,27 +210,30 @@ func Milestones(ctx *context.Context) { } var repoIDs []int64 - if issueReposQueryPattern.MatchString(reposQuery) { - // remove "[" and "]" from string - reposQuery = reposQuery[1 : len(reposQuery)-1] - //for each ID (delimiter ",") add to int to repoIDs - reposSet := false - for _, rID := range strings.Split(reposQuery, ",") { - // Ensure nonempty string entries - if rID != "" && rID != "0" { - reposSet = true - rIDint64, err := strconv.ParseInt(rID, 10, 64) - if err == nil && com.IsSliceContainsInt64(userRepoIDs, rIDint64) { - repoIDs = append(repoIDs, rIDint64) + if len(reposQuery) != 0 { + if issueReposQueryPattern.MatchString(reposQuery) { + // remove "[" and "]" from string + reposQuery = reposQuery[1 : len(reposQuery)-1] + //for each ID (delimiter ",") add to int to repoIDs + reposSet := false + for _, rID := range strings.Split(reposQuery, ",") { + // Ensure nonempty string entries + if rID != "" && rID != "0" { + reposSet = true + rIDint64, err := strconv.ParseInt(rID, 10, 64) + // If the repo id specified by query is not parseable or not accessible by user, just ignore it. + if err == nil && com.IsSliceContainsInt64(userRepoIDs, rIDint64) { + repoIDs = append(repoIDs, rIDint64) + } } } + if reposSet && len(repoIDs) == 0 { + // force an empty result + repoIDs = []int64{-1} + } + } else { + log.Warn("issueReposQueryPattern not match with query") } - if reposSet && len(repoIDs) == 0 { - // force an empty result - repoIDs = []int64{-1} - } - } else { - log.Error("issueReposQueryPattern not match with query") } if len(repoIDs) == 0 { @@ -261,26 +268,6 @@ func Milestones(ctx *context.Context) { } } showReposMap[rID] = repo - - // Check if user has access to given repository. - perm, err := models.GetUserRepoPermission(repo, ctxUser) - if err != nil { - ctx.ServerError("GetUserRepoPermission", fmt.Errorf("[%d]%v", rID, err)) - return - } - - if !perm.CanRead(models.UnitTypeIssues) { - if log.IsTrace() { - log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+ - "User in repo has Permissions: %-+v", - ctxUser, - models.UnitTypeIssues, - repo, - perm) - } - ctx.Status(404) - return - } } showRepos := models.RepositoryListOfMap(showReposMap) @@ -350,9 +337,11 @@ var issueReposQueryPattern = regexp.MustCompile(`^\[\d+(,\d+)*,?\]$`) // Issues render the user issues page func Issues(ctx *context.Context) { isPullList := ctx.Params(":type") == "pulls" + unitType := models.UnitTypeIssues if isPullList { ctx.Data["Title"] = ctx.Tr("pull_requests") ctx.Data["PageIsPulls"] = true + unitType = models.UnitTypePullRequests } else { ctx.Data["Title"] = ctx.Tr("issues") ctx.Data["PageIsIssues"] = true @@ -394,21 +383,23 @@ func Issues(ctx *context.Context) { reposQuery := ctx.Query("repos") var repoIDs []int64 - if issueReposQueryPattern.MatchString(reposQuery) { - // remove "[" and "]" from string - reposQuery = reposQuery[1 : len(reposQuery)-1] - //for each ID (delimiter ",") add to int to repoIDs - for _, rID := range strings.Split(reposQuery, ",") { - // Ensure nonempty string entries - if rID != "" && rID != "0" { - rIDint64, err := strconv.ParseInt(rID, 10, 64) - if err == nil { - repoIDs = append(repoIDs, rIDint64) + if len(reposQuery) != 0 { + if issueReposQueryPattern.MatchString(reposQuery) { + // remove "[" and "]" from string + reposQuery = reposQuery[1 : len(reposQuery)-1] + //for each ID (delimiter ",") add to int to repoIDs + for _, rID := range strings.Split(reposQuery, ",") { + // Ensure nonempty string entries + if rID != "" && rID != "0" { + rIDint64, err := strconv.ParseInt(rID, 10, 64) + if err == nil { + repoIDs = append(repoIDs, rIDint64) + } } } + } else { + log.Warn("issueReposQueryPattern not match with query") } - } else { - log.Error("issueReposQueryPattern not match with query") } isShowClosed := ctx.Query("state") == "closed" @@ -427,11 +418,12 @@ func Issues(ctx *context.Context) { ctx.ServerError("env.RepoIDs", err) return } - } else { - unitType := models.UnitTypeIssues - if isPullList { - unitType = models.UnitTypePullRequests + userRepoIDs, err = models.FilterOutRepoIdsWithoutUnitAccess(ctx.User, userRepoIDs, unitType) + if err != nil { + ctx.ServerError("FilterOutRepoIdsWithoutUnitAccess", err) + return } + } else { userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType) if err != nil { ctx.ServerError("ctxUser.GetAccessRepoIDs", err) diff --git a/routers/user/home_test.go b/routers/user/home_test.go index e5bbd0e98e64..39186d93eeaa 100644 --- a/routers/user/home_test.go +++ b/routers/user/home_test.go @@ -26,10 +26,10 @@ func TestIssues(t *testing.T) { Issues(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) + assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"]) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.Len(t, ctx.Data["Issues"], 1) - assert.Len(t, ctx.Data["Repos"], 1) + assert.Len(t, ctx.Data["Repos"], 2) } func TestMilestones(t *testing.T) { diff --git a/services/mailer/mail.go b/services/mailer/mail.go index a8768de6cdbd..fa40170d464e 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -177,7 +177,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent commentType := models.CommentTypeComment if ctx.Comment != nil { - prefix = "Re: " commentType = ctx.Comment.Type link = ctx.Issue.HTMLURL() + "#" + ctx.Comment.HashTag() } else { @@ -189,13 +188,16 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent reviewType = ctx.Comment.Review.Type } - fallback = prefix + fallbackMailSubject(ctx.Issue) - // This is the body of the new issue or comment, not the mail body body := string(markup.RenderByType(markdown.MarkupName, []byte(ctx.Content), ctx.Issue.Repo.HTMLURL(), ctx.Issue.Repo.ComposeMetas())) actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType) + if actName != "new" { + prefix = "Re: " + } + fallback = prefix + fallbackMailSubject(ctx.Issue) + if ctx.Comment != nil && ctx.Comment.Review != nil { reviewComments = make([]*models.Comment, 0, 10) for _, lines := range ctx.Comment.Review.CodeComments { @@ -247,7 +249,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, tos []string, fromMent msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) // Set Message-ID on first message so replies know what to reference - if ctx.Comment == nil { + if actName == "new" { msg.SetHeader("Message-ID", "<"+ctx.Issue.ReplyReference()+">") } else { msg.SetHeader("In-Reply-To", "<"+ctx.Issue.ReplyReference()+">") diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 7fc6e97b463a..28b2e2a12728 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -410,7 +410,7 @@ func syncMirror(repoID string) { theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID) - notification.NotifySyncPushCommits(m.Repo.MustOwner(), m.Repo, result.refName, oldCommitID, newCommitID, models.ListToPushCommits(commits)) + notification.NotifySyncPushCommits(m.Repo.MustOwner(), m.Repo, result.refName, oldCommitID, newCommitID, theCommits) } // Get latest commit date and update to current repository updated time diff --git a/services/pull/patch.go b/services/pull/patch.go index cb8d0144868d..57a2997b36ee 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -171,7 +171,6 @@ func TestPatch(pr *models.PullRequest) error { scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { line := scanner.Text() - fmt.Printf("%s\n", line) if strings.HasPrefix(line, prefix) { conflict = true filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go new file mode 100644 index 000000000000..58af203cfdc0 --- /dev/null +++ b/services/wiki/wiki.go @@ -0,0 +1,322 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package wiki + +import ( + "fmt" + "net/url" + "os" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" + + "github.com/unknwon/com" +) + +var ( + reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"} + wikiWorkingPool = sync.NewExclusivePool() +) + +func nameAllowed(name string) error { + if util.IsStringInSlice(name, reservedWikiNames) { + return models.ErrWikiReservedName{ + Title: name, + } + } + return nil +} + +// NameToSubURL converts a wiki name to its corresponding sub-URL. +func NameToSubURL(name string) string { + return url.QueryEscape(strings.Replace(name, " ", "-", -1)) +} + +// NormalizeWikiName normalizes a wiki name +func NormalizeWikiName(name string) string { + return strings.Replace(name, "-", " ", -1) +} + +// NameToFilename converts a wiki name to its corresponding filename. +func NameToFilename(name string) string { + name = strings.Replace(name, " ", "-", -1) + return url.QueryEscape(name) + ".md" +} + +// FilenameToName converts a wiki filename to its corresponding page name. +func FilenameToName(filename string) (string, error) { + if !strings.HasSuffix(filename, ".md") { + return "", models.ErrWikiInvalidFileName{ + FileName: filename, + } + } + basename := filename[:len(filename)-3] + unescaped, err := url.QueryUnescape(basename) + if err != nil { + return "", err + } + return NormalizeWikiName(unescaped), nil +} + +// InitWiki initializes a wiki for repository, +// it does nothing when repository already has wiki. +func InitWiki(repo *models.Repository) error { + if repo.HasWiki() { + return nil + } + + if err := git.InitRepository(repo.WikiPath(), true); err != nil { + return fmt.Errorf("InitRepository: %v", err) + } else if err = models.CreateDelegateHooks(repo.WikiPath()); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + return nil +} + +// updateWikiPage adds a new page to the repository wiki. +func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, newWikiName, content, message string, isNew bool) (err error) { + if err = nameAllowed(newWikiName); err != nil { + return err + } + wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) + defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) + + if err = InitWiki(repo); err != nil { + return fmt.Errorf("InitWiki: %v", err) + } + + hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") + + basePath, err := models.CreateTemporaryPath("update-wiki") + if err != nil { + return err + } + defer func() { + if err := models.RemoveTemporaryPath(basePath); err != nil { + log.Error("Merge: RemoveTemporaryPath: %s", err) + } + }() + + cloneOpts := git.CloneRepoOptions{ + Bare: true, + Shared: true, + } + + if hasMasterBranch { + cloneOpts.Branch = "master" + } + + if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } + + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } + defer gitRepo.Close() + + if hasMasterBranch { + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + } + + newWikiPath := NameToFilename(newWikiName) + if isNew { + filesInIndex, err := gitRepo.LsFiles(newWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + if util.IsStringInSlice(newWikiPath, filesInIndex) { + return models.ErrWikiAlreadyExist{ + Title: newWikiPath, + } + } + } else { + oldWikiPath := NameToFilename(oldWikiName) + filesInIndex, err := gitRepo.LsFiles(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + + if util.IsStringInSlice(oldWikiPath, filesInIndex) { + err := gitRepo.RemoveFilesFromIndex(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + } + } + + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + objectHash, err := gitRepo.HashObject(strings.NewReader(content)) + if err != nil { + log.Error("%v", err) + return err + } + + if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { + log.Error("%v", err) + return err + } + + tree, err := gitRepo.WriteTree() + if err != nil { + log.Error("%v", err) + return err + } + + commitTreeOpts := git.CommitTreeOpts{ + Message: message, + } + + sign, signingKey := repo.SignWikiCommit(doer) + if sign { + commitTreeOpts.KeyID = signingKey + } else { + commitTreeOpts.NoGPGSign = true + } + if hasMasterBranch { + commitTreeOpts.Parents = []string{"HEAD"} + } + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + if err != nil { + log.Error("%v", err) + return err + } + + if err := git.Push(basePath, git.PushOptions{ + Remote: "origin", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: models.FullPushingEnvironment( + doer, + doer, + repo, + repo.Name+".wiki", + 0, + ), + }); err != nil { + log.Error("%v", err) + return fmt.Errorf("Push: %v", err) + } + + return nil +} + +// AddWikiPage adds a new wiki page with a given wikiPath. +func AddWikiPage(doer *models.User, repo *models.Repository, wikiName, content, message string) error { + return updateWikiPage(doer, repo, "", wikiName, content, message, true) +} + +// EditWikiPage updates a wiki page identified by its wikiPath, +// optionally also changing wikiPath. +func EditWikiPage(doer *models.User, repo *models.Repository, oldWikiName, newWikiName, content, message string) error { + return updateWikiPage(doer, repo, oldWikiName, newWikiName, content, message, false) +} + +// DeleteWikiPage deletes a wiki page identified by its path. +func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string) (err error) { + wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) + defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) + + if err = InitWiki(repo); err != nil { + return fmt.Errorf("InitWiki: %v", err) + } + + basePath, err := models.CreateTemporaryPath("update-wiki") + if err != nil { + return err + } + defer func() { + if err := models.RemoveTemporaryPath(basePath); err != nil { + log.Error("Merge: RemoveTemporaryPath: %s", err) + } + }() + + if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, + Branch: "master", + }); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } + + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } + defer gitRepo.Close() + + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + + wikiPath := NameToFilename(wikiName) + filesInIndex, err := gitRepo.LsFiles(wikiPath) + found := false + for _, file := range filesInIndex { + if file == wikiPath { + found = true + break + } + } + if found { + err := gitRepo.RemoveFilesFromIndex(wikiPath) + if err != nil { + return err + } + } else { + return os.ErrNotExist + } + + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + tree, err := gitRepo.WriteTree() + if err != nil { + return err + } + message := "Delete page '" + wikiName + "'" + commitTreeOpts := git.CommitTreeOpts{ + Message: message, + Parents: []string{"HEAD"}, + } + + sign, signingKey := repo.SignWikiCommit(doer) + if sign { + commitTreeOpts.KeyID = signingKey + } else { + commitTreeOpts.NoGPGSign = true + } + + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + if err != nil { + return err + } + + if err := git.Push(basePath, git.PushOptions{ + Remote: "origin", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: models.PushingEnvironment(doer, repo), + }); err != nil { + return fmt.Errorf("Push: %v", err) + } + + return nil +} diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go new file mode 100644 index 000000000000..0e1d460a2040 --- /dev/null +++ b/services/wiki/wiki_test.go @@ -0,0 +1,210 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package wiki + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + models.MainTest(m, filepath.Join("..", "..")) +} + +func TestWikiNameToSubURL(t *testing.T) { + type test struct { + Expected string + WikiName string + } + for _, test := range []test{ + {"wiki-name", "wiki name"}, + {"wiki-name", "wiki-name"}, + {"name-with%2Fslash", "name with/slash"}, + {"name-with%25percent", "name with%percent"}, + } { + assert.Equal(t, test.Expected, NameToSubURL(test.WikiName)) + } +} + +func TestNormalizeWikiName(t *testing.T) { + type test struct { + Expected string + WikiName string + } + for _, test := range []test{ + {"wiki name", "wiki name"}, + {"wiki name", "wiki-name"}, + {"name with/slash", "name with/slash"}, + {"name with%percent", "name-with%percent"}, + {"%2F", "%2F"}, + } { + assert.Equal(t, test.Expected, NormalizeWikiName(test.WikiName)) + } +} + +func TestWikiNameToFilename(t *testing.T) { + type test struct { + Expected string + WikiName string + } + for _, test := range []test{ + {"wiki-name.md", "wiki name"}, + {"wiki-name.md", "wiki-name"}, + {"name-with%2Fslash.md", "name with/slash"}, + {"name-with%25percent.md", "name with%percent"}, + } { + assert.Equal(t, test.Expected, NameToFilename(test.WikiName)) + } +} + +func TestWikiFilenameToName(t *testing.T) { + type test struct { + Expected string + Filename string + } + for _, test := range []test{ + {"hello world", "hello-world.md"}, + {"symbols/?*", "symbols%2F%3F%2A.md"}, + } { + name, err := FilenameToName(test.Filename) + assert.NoError(t, err) + assert.Equal(t, test.Expected, name) + } + for _, badFilename := range []string{ + "nofileextension", + "wrongfileextension.txt", + } { + _, err := FilenameToName(badFilename) + assert.Error(t, err) + assert.True(t, models.IsErrWikiInvalidFileName(err)) + } + _, err := FilenameToName("badescaping%%.md") + assert.Error(t, err) + assert.False(t, models.IsErrWikiInvalidFileName(err)) +} + +func TestWikiNameToFilenameToName(t *testing.T) { + // converting from wiki name to filename, then back to wiki name should + // return the original (normalized) name + for _, name := range []string{ + "wiki-name", + "wiki name", + "wiki name with/slash", + "$$$%%%^^&&!@#$(),.<>", + } { + filename := NameToFilename(name) + resultName, err := FilenameToName(filename) + assert.NoError(t, err) + assert.Equal(t, NormalizeWikiName(name), resultName) + } +} + +func TestRepository_InitWiki(t *testing.T) { + models.PrepareTestEnv(t) + // repo1 already has a wiki + repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + assert.NoError(t, InitWiki(repo1)) + + // repo2 does not already have a wiki + repo2 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + assert.NoError(t, InitWiki(repo2)) + assert.True(t, repo2.HasWiki()) +} + +func TestRepository_AddWikiPage(t *testing.T) { + assert.NoError(t, models.PrepareTestDatabase()) + const wikiContent = "This is the wiki content" + const commitMsg = "Commit message" + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + for _, wikiName := range []string{ + "Another page", + "Here's a and a/slash", + } { + wikiName := wikiName + t.Run("test wiki exist: "+wikiName, func(t *testing.T) { + t.Parallel() + assert.NoError(t, AddWikiPage(doer, repo, wikiName, wikiContent, commitMsg)) + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + defer gitRepo.Close() + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := NameToFilename(wikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName) + }) + } + + t.Run("check wiki already exist", func(t *testing.T) { + t.Parallel() + // test for already-existing wiki name + err := AddWikiPage(doer, repo, "Home", wikiContent, commitMsg) + assert.Error(t, err) + assert.True(t, models.IsErrWikiAlreadyExist(err)) + }) + + t.Run("check wiki reserved name", func(t *testing.T) { + t.Parallel() + // test for reserved wiki name + err := AddWikiPage(doer, repo, "_edit", wikiContent, commitMsg) + assert.Error(t, err) + assert.True(t, models.IsErrWikiReservedName(err)) + }) +} + +func TestRepository_EditWikiPage(t *testing.T) { + const newWikiContent = "This is the new content" + const commitMsg = "Commit message" + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + for _, newWikiName := range []string{ + "Home", // same name as before + "New home", + "New/name/with/slashes", + } { + models.PrepareTestEnv(t) + assert.NoError(t, EditWikiPage(doer, repo, "Home", newWikiName, newWikiContent, commitMsg)) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := NameToFilename(newWikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName) + + if newWikiName != "Home" { + _, err := masterTree.GetTreeEntryByPath("Home.md") + assert.Error(t, err) + } + gitRepo.Close() + } +} + +func TestRepository_DeleteWikiPage(t *testing.T) { + models.PrepareTestEnv(t) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + assert.NoError(t, DeleteWikiPage(doer, repo, "Home")) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + defer gitRepo.Close() + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := NameToFilename("Home") + _, err = masterTree.GetTreeEntryByPath(wikiPath) + assert.Error(t, err) +} diff --git a/templates/admin/monitor.tmpl b/templates/admin/monitor.tmpl index 38402fece2be..0f9c2150b647 100644 --- a/templates/admin/monitor.tmpl +++ b/templates/admin/monitor.tmpl @@ -31,6 +31,34 @@ +

+ {{.i18n.Tr "admin.monitor.queues"}} +

+
+ + + + + + + + + + + + {{range .Queues}} + + + + + + + {{end}} + +
{{.i18n.Tr "admin.monitor.queue.name"}}{{.i18n.Tr "admin.monitor.queue.type"}}{{.i18n.Tr "admin.monitor.queue.exemplar"}}{{.i18n.Tr "admin.monitor.queue.numberworkers"}}
{{.Name}}{{.Type}}{{.ExemplarType}}{{$sum := .NumberOfWorkers}}{{if lt $sum 0}}-{{else}}{{$sum}}{{end}}{{if lt $sum 0}}{{$.i18n.Tr "admin.monitor.queue.review"}}{{else}}{{$.i18n.Tr "admin.monitor.queue.review_add"}}{{end}} +
+
+

{{.i18n.Tr "admin.monitor.process"}}

diff --git a/templates/admin/queue.tmpl b/templates/admin/queue.tmpl new file mode 100644 index 000000000000..4f422210e756 --- /dev/null +++ b/templates/admin/queue.tmpl @@ -0,0 +1,147 @@ +{{template "base/head" .}} +
+ {{template "admin/navbar" .}} +
+ {{template "base/alert" .}} +

+ {{.i18n.Tr "admin.monitor.queue" .Queue.Name}} +

+
+ + + + + + + + + + + + + + + + + + + +
{{.i18n.Tr "admin.monitor.queue.name"}}{{.i18n.Tr "admin.monitor.queue.type"}}{{.i18n.Tr "admin.monitor.queue.exemplar"}}{{.i18n.Tr "admin.monitor.queue.numberworkers"}}{{.i18n.Tr "admin.monitor.queue.maxnumberworkers"}}
{{.Queue.Name}}{{.Queue.Type}}{{.Queue.ExemplarType}}{{$sum := .Queue.NumberOfWorkers}}{{if lt $sum 0}}-{{else}}{{$sum}}{{end}}{{if lt $sum 0}}-{{else}}{{.Queue.MaxNumberOfWorkers}}{{end}}
+
+ {{if lt $sum 0 }} +

+ {{.i18n.Tr "admin.monitor.queue.nopool.title"}} +

+
+ {{if eq .Queue.Type "wrapped" }} +

{{.i18n.Tr "admin.monitor.queue.wrapped.desc"}}

+ {{else if eq .Queue.Type "persistable-channel"}} +

{{.i18n.Tr "admin.monitor.queue.persistable-channel.desc"}}

+ {{else}} +

{{.i18n.Tr "admin.monitor.queue.nopool.desc"}}

+ {{end}} +
+ {{else}} +

+ {{.i18n.Tr "admin.monitor.queue.settings.title"}} +

+
+

{{.i18n.Tr "admin.monitor.queue.settings.desc"}}

+
+ {{$.CsrfTokenHtml}} +
+
+ + +
+
+ + +
+
+ + +
+
+ + {{.i18n.Tr "admin.monitor.queue.settings.blocktimeout.value" .Queue.BlockTimeout}} +
+ +
+
+
+

+ {{.i18n.Tr "admin.monitor.queue.pool.addworkers.title"}} +

+
+

{{.i18n.Tr "admin.monitor.queue.pool.addworkers.desc"}}

+
+ {{$.CsrfTokenHtml}} +
+
+
+ + +
+
+ + +
+
+ +
+
+
+

+ {{.i18n.Tr "admin.monitor.queue.pool.workers.title"}} +

+
+ + + + + + + + + + + {{range .Queue.Workers}} + + + + + + + {{else}} + + + {{end}} + +
{{.i18n.Tr "admin.monitor.queue.numberworkers"}}{{.i18n.Tr "admin.monitor.start"}}{{.i18n.Tr "admin.monitor.queue.pool.timeout"}}
{{.Workers}}{{DateFmtLong .Start}}{{if .HasTimeout}}{{DateFmtLong .Timeout}}{{else}}-{{end}} + +
{{.i18n.Tr "admin.monitor.queue.pool.workers.none" }} +
+
+ {{end}} +

+ {{.i18n.Tr "admin.monitor.queue.configuration"}} +

+
+
{{.Queue.Configuration | JsonPrettyPrint}}
+		
+
+
+ + +{{template "base/footer" .}} diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 364e58a3d03d..db4c19c156bb 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,7 +1,7 @@