diff --git a/.changelog.yml b/.changelog.yml index c5a9078e03ad..942138327875 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -22,6 +22,10 @@ groups: name: SECURITY labels: - kind/security + - + name: API + labels: + - kind/api - name: BUGFIXES labels: diff --git a/.drone.yml b/.drone.yml index 0bb731d18d01..872dcf750d8f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -57,7 +57,7 @@ steps: - name: build-backend-no-gcc pull: always - image: golang:1.12 # this step is kept as the lowest version of golang that we support + image: golang:1.13 # this step is kept as the lowest version of golang that we support environment: GO111MODULE: on GOPROXY: off @@ -113,18 +113,6 @@ services: environment: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: test - GOPROXY: off - TAGS: bindata sqlite sqlite_unlock_notify - GITLAB_READ_TOKEN: - from_secret: gitlab_read_token - depends_on: - - build - when: - branch: - - master - event: - - push - - pull_request - name: mysql8 pull: default @@ -152,7 +140,7 @@ services: image: elasticsearch:7.5.0 - name: minio - image: minio/minio:RELEASE.2020-05-16T01-33-21Z + image: minio/minio:RELEASE.2020-10-09T22-55-05Z commands: - minio server /data environment: @@ -209,6 +197,7 @@ steps: TAGS: bindata TEST_LDAP: 1 USE_REPO_TEST_DIR: 1 + TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200" depends_on: - build @@ -447,23 +436,13 @@ steps: commands: - git fetch --tags --force - - name: static-windows - pull: always - image: techknowlogick/xgo:go-1.14.x - commands: - - apt update && apt -y install curl - - curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt -y install nodejs - - export PATH=$PATH:$GOPATH/bin - - make frontend generate release-windows - environment: - GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not - TAGS: bindata sqlite sqlite_unlock_notify - - name: static pull: always image: techknowlogick/xgo:go-1.15.x commands: - - make release-linux release-darwin release-copy release-compress release-sources release-docs release-check + - curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt -y install nodejs + - export PATH=$PATH:$GOPATH/bin + - make release environment: GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not TAGS: bindata sqlite sqlite_unlock_notify @@ -558,8 +537,7 @@ steps: pull: always image: techknowlogick/xgo:go-1.15.x commands: - - apt update && apt -y install curl - - curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt -y install nodejs + - curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt -y install nodejs - export PATH=$PATH:$GOPATH/bin - make release environment: diff --git a/.editorconfig b/.editorconfig index 82542ea86dcc..54738e8838d6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,28 +1,22 @@ root = true [*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf charset = utf-8 -insert_final_newline = true trim_trailing_whitespace = true -end_of_line = lf - -[*.md] -trim_trailing_whitespace = false +insert_final_newline = true [*.{go,tmpl,html}] indent_style = tab -indent_size = 2 - -[*.{less,css}] -indent_style = space -indent_size = 4 - -[*.{js,json,yml}] -indent_style = space -indent_size = 2 [Makefile] indent_style = tab [*.svg] insert_final_newline = false + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc index 0adfb7e048fd..7b8376e8ea59 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,9 +13,7 @@ plugins: - eslint-plugin-import env: - browser: true - es6: true - jquery: true + es2021: true node: true globals: @@ -24,9 +22,13 @@ globals: Dropzone: false SimpleMDE: false u2fApi: false - Tribute: false overrides: + - files: ["web_src/**/*.js"] + env: + browser: true + jquery: true + node: false - files: ["web_src/**/*worker.js"] env: worker: true @@ -97,7 +99,7 @@ rules: import/no-amd: [0] import/no-anonymous-default-export: [0] import/no-commonjs: [0] - import/no-cycle: [0] + import/no-cycle: [2, {ignoreExternal: true}] import/no-default-export: [0] import/no-deprecated: [0] import/no-dynamic-require: [0] @@ -115,7 +117,7 @@ rules: import/no-self-import: [2] import/no-unassigned-import: [0] import/no-unresolved: [2, {commonjs: true}] - import/no-unused-modules: [0] + import/no-unused-modules: [2, {unusedExports: true}] import/no-useless-path-segments: [2, {commonjs: true}] import/no-webpack-loader-syntax: [2] import/order: [0] @@ -209,7 +211,7 @@ rules: no-mixed-operators: [0] no-mixed-spaces-and-tabs: [2] no-multi-assign: [0] - no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true, VariableDeclarator: true}}] + no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true}}] no-multi-str: [2] no-negated-condition: [0] no-nested-ternary: [0] @@ -334,6 +336,7 @@ rules: unicorn/explicit-length-check: [0] unicorn/filename-case: [0] unicorn/import-index: [0] + unicorn/import-style: [0] unicorn/new-for-builtins: [2] unicorn/no-abusive-eslint-disable: [0] unicorn/no-array-instanceof: [0] @@ -354,11 +357,13 @@ rules: unicorn/no-useless-undefined: [0] unicorn/no-zero-fractions: [2] unicorn/number-literal-case: [0] + unicorn/numeric-separators-style: [0] unicorn/prefer-add-event-listener: [2] unicorn/prefer-array-find: [2] unicorn/prefer-dataset: [2] unicorn/prefer-event-key: [2] unicorn/prefer-includes: [2] + unicorn/prefer-math-trunc: [2] unicorn/prefer-modern-dom-apis: [0] unicorn/prefer-negative-index: [2] unicorn/prefer-node-append: [0] @@ -372,6 +377,7 @@ rules: unicorn/prefer-spread: [0] unicorn/prefer-starts-ends-with: [2] unicorn/prefer-string-slice: [0] + unicorn/prefer-ternary: [0] unicorn/prefer-text-content: [2] unicorn/prefer-trim-start-end: [2] unicorn/prefer-type-error: [0] diff --git a/.github/issue_template.md b/.github/issue_template.md index ab2b73c565dc..15773a478dac 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -12,6 +12,9 @@ - Gitea version (or commit ref): - Git version: - Operating system: + + + - Database (use `[x]`): - [ ] PostgreSQL - [ ] MySQL @@ -20,8 +23,10 @@ - Can you reproduce the bug at https://try.gitea.io: - [ ] Yes (provide example URL) - [ ] No - - [ ] Not relevant - Log gist: + + + ## Description diff --git a/.golangci.yml b/.golangci.yml index 0bd71b7fb1d7..95ad5f308a5e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -98,3 +98,12 @@ issues: - path: models/update.go linters: - unused + - path: cmd/dump.go + linters: + - dupl + - text: "commentFormatting: put a space between `//` and comment text" + linters: + - gocritic + - text: "exitAfterDefer:" + linters: + - gocritic diff --git a/.stylelintrc b/.stylelintrc index fcc33edeff98..427d89b5bcda 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -5,8 +5,9 @@ rules: block-closing-brace-empty-line-before: null color-hex-length: null comment-empty-line-before: null + declaration-block-single-line-max-declarations: null declaration-empty-line-before: null - indentation: 4 + indentation: 2 no-descending-specificity: null number-leading-zero: never rule-empty-line-before: null diff --git a/CHANGELOG.md b/CHANGELOG.md index 351749f94630..8ca56e323f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,350 @@ 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.13.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.13.0-rc1) - 2020-10-14 + +* SECURITY + * Mitigate Security vulnerability in the git hook feature (#13058) + * Disable DSA ssh keys by default (#13056) + * Set TLS minimum version to 1.2 (#12689) + * Use argon as default password hash algorithm (#12688) +* BREAKING + * Don't replace underscores in auto-generated IDs in goldmark (#12805) + * Add Primary Key to Topic and RepoTopic tables (#12639) + * Disable password complexity check default (#12557) + * Change PIDFile default from /var/run/gitea.pid to /run/gitea.pid (#12500) + * Add extension Support to Attachments (allow all types for releases) (#12465) + * Remove IE11 Support (#11470) +* FEATURES + * Adopt repositories (#12920) + * Check passwords against HaveIBeenPwned (#12716) + * Gitea 2 Gitea migration (#12657) + * Support storing Avatars in minio (#12516) + * Allow addition of gpg keyring with multiple keys (#12487) + * Add email notify for new release (#12463) + * Add Access-Control-Expose-Headers (#12446) + * UserProfile Page: Render Description (#12415) + * Add command to recreate tables (#12407) + * Add mermaid JS renderer (#12334) + * Add ssh certificate support (#12281) + * Add spent time to referenced issue in commit message (#12220) + * Initial support for push options (#12169) + * Provide option to unlink a fork (#11858) + * Show exact tag for commit on diff view (#11846) + * Pause, Resume, Release&Reopen, Add and Remove Logging from command line (#11777) + * Issue templates directory (#11450) + * Add a storage layer for attachments (#11387) + * Add hide activity option (#11353) + * Add push commits history comment on PR time-line (#11167) + * Support elastic search for code search (#10273) + * Kanban board (#8346) +* API + * If User is Admin, show 500 error message on PROD mode too (#13115) + * Add Timestamp to Tag list API (#13026) + * Return sample message for login error in api context (#12994) + * Add IsTemplate option in create repo ui and api (#12942) + * GetReleaseByID return 404 if not found (#12933) + * Get release by tags endpoint (#12932) + * NotificationSubject show Issue/Pull State (#12901) + * Expose its limitation settings (#12714) + * Add Created & Updated to Milestone (#12662) + * Milestone endpoints accept names too (#12649) + * Expose Attachment Settings in the API (#12514) + * Add Issue and Repo info to StopWatch (#12458) + * Add cron running API (#12421) + * Add Update Pull HeadBranch Function (#12419) + * Add TOTP header to Swagger Documentation (#12402) + * Delete Token accept names too (#12366) + * Add name filter for GetMilestoneList (#12336) + * Fixed count of filtered issues when api request. (#12275) + * Do not override API issue pagination with UI settings (#12068) + * Expose useful General Repo settings settings (#11758) + * Return error when trying to create Mirrors but Mirrors are globally disabled (#11757) + * Provide diff and patch API endpoints (#11751) + * Allow to create closed milestones (#11745) + * Add language Statistics endpoint (#11737) + * Add Endpoint to get GetGeneralUI Settings (#11735) & (#11854) + * Issue/Pull expose IsLocked Property on API (#11708) + * Add endpoint for Branch Creation (#11607) + * Add pagination headers on endpoints that support total count from database (#11145) +* BUGFIXES + * Show original author's reviews on pull summary box (#13127) + * Update golangci-lint to version 1.31.0 (#13102) + * Fix line break for MS teams webhook (#13081) + * Fix Issue & Pull Request comment headers on mobile (#13039) + * Avoid setting the CONN_STR in queues unless it is meant to be set (#13025) + * Remove code-view class from diff view (#13011) + * Fix the color of PR comment hyperlinks. (#13009) + * (Re)Load issue labels when changing them (#13007) + * Fix Media links in org files not liked to media files (#12997) + * Always return a list from GetCommitsFromIDs (#12981) + * Only set the user password if the password field would have been shown (#12980) + * Fix admin/config page (#12979) + * Changed width of commit signature avatar (#12961) + * Completely quote AppPath and CustomConf paths (#12955) + * Fix handling of migration errors (#12928) + * Fix anonymous GL migration (#12862) + * Fix git open close bug (#12834) + * Fix markdown meta parsing (#12817) + * Add default storage configurations (#12813) + * Show PR settings on empty repos (#12808) + * Disable watch and star if not signed in (#12807) + * Whilst changing the character set to utf8mb4 we should set ROW_FORMAT=dynamic too (#12804) + * Set opengraph attributes on org pages (#12803) + * Return error when creating gitlabdownloader failed (#12790) + * Add migration for password algorithm change (#12784) + * Compare SSH_DOMAIN when parsing submodule URLs (#12753) + * Fix editor.commit_empty_file_text locale string (#12744) + * Fix wrong poster message for code comment on Pull view (#11721) + * Escape failed highlighted files (#12685) + * Ensure that all migration requests are cancellable (#12669) + * Ensure RepoPath is lowercased in gitea serv (#12668) + * Do not disable commit changes button on repost (#12644) + * Dark theme for line numbers in blame view (#12632) + * Fix message when deleting last owner from an organization (#12628) + * Use shellquote to unpack arguments to gitea serv (#12624) + * Fix signing.wont_sign.%!s() if Require Signing commits but not signed in. (#12581) + * Set utf8mb4 as the default charset on MySQL if CHARSET is unset (#12563) + * Set context for running CreateArchive to that of the request (#12555) + * Prevent redirect back to /user/events (#12462) + * Re-attempt to delete temporary upload if the file is locked by another process (#12447) + * Mirror System Notice reports are too frequent (#12438) + * Do not show arrows on comment diffs on pull comment pages (#12434) + * Fix milestone links (#12405) + * Increase size of the language column in language_stat (#12396) + * Use transaction in V102 migration (#12395) + * Only use --exclude on name-rev with git >= 2.13 (#12347) + * Add action feed for new release (#12324) + * Set NoAutoTime when updating is_archived (#12266) + * Support Force-update in Mirror and improve Tracing in mirror (#12242) + * Avoid sending "0 new commits" webhooks (#12212) + * Fix U2F button icon (#12167) + * models/repo_sign.go: break out of loops (#12159) + * Ensure that git commit tree continues properly over the page (#12142) + * Rewrite GitGraph.js (#12137) + * Fix repo API listing stability (#12057) + * Add team support for review request (#12039) + * Fix 500 error on repos with no tags (#11870) + * Fix nil pointer in default issue mail template (#11862) + * Fix commit search in all branches (#11849) + * Don't consider tag refs as valid for branch name (#11847) + * Don't add same line code comment box twice (#11837) + * Fix visibility of forked public repos from private orgs (#11717) + * Fix chardet test and add ordering option (#11621) + * Fix number of files, total additions, and deletions on Diff pages (#11614) + * Properly handle and return empty string for dangling commits in GetBranchName (#11587) + * Include query in sign in redirect (#11579) + * Fix Enter not working in SimpleMDE (#11564) + * Fix bug about can't skip commits base on base branch (#11555) +* ENHANCEMENTS + * Add HostCertificate to sshd_config in Docker image (#13143) + * Save TimeStamps for Star, Label, Follow, Watch and Collaboration to Database (#13124) + * Improve error feedback for duplicate deploy keys (#13112) + * Set appropriate `autocomplete` attributes on password fields (#13078) + * Adding visual cue for "Limited" & "Private" organizations. (#13040) + * Fix Pull Request merge buttons on mobile (#13035) + * Gitea serv, hooks, manager and the like should always display Fatals (#13032) + * CSS tweaks to warning/error segments and misc fixes (#13024) + * Fix formatting of branches ahead-behind on narrow windows (#12989) + * Add config option to make create-on-push repositories public by default (#12936) + * Disable migration items when mirror is selected (#12918) + * Add the checkbox quick button to the comment tool bar also (#12885) + * Support GH enterprise (#12863) + * Simplify CheckUnitUser logic (#12854) + * Fix background of signed-commits on arc-green of timeline commits (#12837) + * Move git update-server-info to hooks (#12826) + * Add ui style for "Open a blank issue" button (#12824) + * Use a simple format for the big number on ui (#12822) + * Make SVG size argument optional (#12814) + * Add placeholder text for bio profile text form (#12792) + * Set language via AJAX (#12785) + * Show git-pull-request icon for closed pull request (#12742) + * Migrate version parsing library to hashicorp/go-version (#12719) + * Only use async pre-empt hack if go < 1.15 (#12718) + * Inform user about meaning of an hourglass on reviews (#12713) + * Add a migrate service type switch page (#12697) + * Migrations: Gitlab Add Reactions Support for Issues & MergeRequests (#12695) + * Remove duplicate logic in initListSubmits (#12660) + * Set avatar image dimensions (#12654) + * Rename models.ProtectedBranchRepoID/PRID to models.EnvRepoID/PRID and ensure EnvPusherEmail is set (#12646) + * Set setting.AppURL as GITEA_ROOT_URL environment variable during pushes (#12752) + * Add postgres schema to the search_path on database connection (#12634) + * Git migration UX improvements (#12619) + * Add link to home page on swagger ui (#12601) + * hCaptcha Support (#12594) + * OpenGraph: use repo avatar if exist (#12586) + * Reaction picker display improvements (#12576) + * Fix emoji replacements, make emoji images consistent (#12567) + * Increase clickable area on files table links (#12553) + * Set z-index for sticky diff box lower (#12537) + * Report error if API merge is not allowed (#12528) + * LFS support to be stored on minio (#12518) + * Show 2FA info on Admin Pannel: Users List (#12515) + * Milestone Issue/Pull List: Add octicons type (#12499) + * Make dashboard newsfeed list length a configurable item (#12469) + * Add placeholder text for send testing email button in admin/config (#12452) + * Add SVG favicon (#12437) + * In issue comments, put issue participants also in completion list when hitting @ (#12433) + * Collapse Swagger UI tags by default (#12428) + * Detect full references to issues and pulls in commit messages (#12399) + * Allow common redis and leveldb connections (#12385) + * Don't use legacy method to send Matrix Webhook (#12348) + * Remove padding/border-radius on image diffs (#12346) + * Render the git graph on the server (#12333) + * Fix clone panel in wiki position not always align right (#12326) + * Rework 'make generate-images' (#12316) + * Refactor webhook payload convertion (#12310) + * Move jquery-minicolors to npm/webpack (#12305) + * Support use nvarchar for all varchar columns when using mssql (#12269) + * Update Octicons to v10 (#12240) + * Disable search box autofocus (#12229) + * Replace code fold icons with octicons (#12222) + * Ensure syntax highlighting is the same inside diffs (#12205) + * Auto-init repo on license, .gitignore select (#12202) + * Default to showing closed Issues/PR list when there are only closed issues/PRs (#12200) + * Enable cloning via Git Wire Protocol v2 over HTTP (#12170) + * Direct SVG rendering (#12157) + * Improve arc-green code colors (#12111) + * Allow admin to merge pr with protected file changes (#12078) + * Show description on individual milestone view (#12055) + * Update the wiki repository remote origin while update the mirror repository's Clone From URL (#12053) + * Server-side syntax highlighting for all code (#12047) + * Use Fomantic's fluid padded for blame full width (#12023) + * Use custom SVGs for commit signing lock icon (#12017) + * Make tabs smaller (#12003) + * Fix sticky diff stats container (#12002) + * Move fomantic and jQuery to main webpack bundle (#11997) + * Use enry language type to detect special languages (#11974) + * Use only first line of commit when creating referenced comment (#11960) + * Rename custom/conf/app.ini.sample to custom/conf/app.example.ini for better syntax light on editor (#11926) + * Fix double divider on issue sidebar (#11919) + * Shorten markdown heading anchors links (#11903) + * Add org avatar on top of internal repo icon (#11895) + * Use label to describe repository type (#11891) + * Make repository size unclickable on repo summary bar (#11887) + * Rework blame template and styling (#11885) + * Fix icon alignment for show/hide outdated link on resolved conversation (#11881) + * Vertically align review icons on repository sidebar (#11880) + * Better align items using flex within review request box (#11879) + * Only write to global gitconfig if necessary (#11876) + * Disable all typographic replacements in markdown renderer (#11871) + * Improve label edit buttons labels (#11841) + * Use crispEdges rendering for octicon-internal-repo (#11801) + * Show update branch item in merge box when it's necessary (#11761) + * Add compare link to releases (#11752) + * Allow site admin to disable mirrors (#11740) + * Export monaco editor on window.codeEditors (#11739) + * Add configurable Trust Models (#11712) + * Show full GPG commit status on PR commit history (#11702) + * Fix align issues and decrease avatar size on PR timeline (#11689) + * Replace jquery-datetimepicker with native date input (#11684) + * Change Style of Tags on Comments (#11668) + * Fix missing styling for shabox on PR commit history (#11625) + * Apply padding to approval icons on PR list (#11622) + * Fix message wrapping on PR commit list (#11616) + * Right-align status icon on pull request commit history (#11594) + * Add missing padding for multi-commit list on PR view (#11593) + * Do not show avatar for "{{user}} added X commits" (#11591) + * Fix styling and padding for commit list on PR view (#11588) + * Style code review comment for arc-green (#11572) + * Use default commit message for wiki edits (#11550) + * Add internal-repo octicon for public repos of private org (#11529) + * Fix dropzone color on arc-green (#11514) + * Insert ui divider directly in templates instead of from inside heatmap vue component (#11508) + * Move tributejs to npm/webpack (#11497) + * Fix text-transform on wiki revisions page (#11486) + * Do not show lock icon on repo list for public repos in private org (#11445) + * Include LFS when calculating repo size (#11060) + * Add check for LDAP group membership (#10869) + * When starting new stopwatch stop previous if it is still running (#10533) + * Add queue for code indexer (#10332) + * Move all push update operations to a queue (#10133) + * Cache last commit when pushing for big repository (#10109) + * Change/remove a branch of an open issue (#9080) + * Sortable Tables Header By Click (#7980) +* TESTING + * Use community codecov drone plugin (#12468) + * Add more tests for diff highlighting (#12467) + * Don't put integration test data outside of test folder (#11746) + * Add debug option to hooks (#11624) + * Log slow tests (#11487) +* TRANSLATION + * Translate two small lables on commit statuse list (#12821) + * Make issues.force_push_codes message shorter (#11575) +* BUILD + * Bump min required golang to 1.13 (#12717) + * Add 'make watch' (#12636) + * Extract Swagger CSS to its own file (#12616) + * Update eslint config (#12609) + * Avoid unnecessary system-ui expansion (#12522) + * Make the default PID file compile-time settable (#12485) + * Add 'watch-backend' (#12330) + * Detect version of sed in Makefile (#12319) + * Update gitea-vet to v0.2.1 (#12282) + * Add logic to build stable and edge builds for gitea snap (#12052) + * Fix missing CGO_EXTRA_FLAGS build arg for docker (#11782) + * Alpine 3.12 (#11720) + * Enable stylelint's shorthand-property-no-redundant-values (#11436) +* DOCS + * Change default log configuration (#13088) + * Add automatic JS license generation (#11810) + * Remove page size limit comment from swagger (#11806) + * Narrow down Edge version in browser support docs (#11640) + +## [1.12.5](https://github.com/go-gitea/gitea/releases/tag/v1.12.5) - 2020-10-01 + +* BUGFIXES + * Allow U2F with default settings for gitea in subpath (#12990) (#13001) + * Prevent empty div when editing comment (#12404) (#12991) + * On mirror update also update address in DB (#12964) (#12967) + * Allow extended config on cron settings (#12939) (#12943) + * Open transaction when adding Avatar email-hash pairs to the DB (#12577) (#12940) + * Fix internal server error from ListUserOrgs API (#12910) (#12915) + * Update only the repository columns that need updating (#12900) (#12912) + * Fix panic when adding long comment (#12892) (#12894) + * Add size limit for content of comment on action ui (#12881) (#12890) + * Convert User expose ID each time (#12855) (#12883) + * Support slashes in release tags (#12864) (#12882) + * Add missing information to CreateRepo API endpoint (#12848) (#12867) + * On Migration respect old DefaultBranch (#12843) (#12858) + * Fix notifications page links (#12838) (#12853) + * Stop cloning unnecessarily on PR update (#12839) (#12852) + * Escape more things that are passed through str2html (#12622) (#12850) + * Remove double escape on labels addition in comments (#12809) (#12810) + * Fix "only mail on mention" bug (#12775) (#12789) + * Fix yet another bug with diff file names (#12771) (#12776) + * RepoInit Respect AlternateDefaultBranch (#12746) (#12751) + * Fix Avatar Resize (resize algo NearestNeighbor -> Bilinear) (#12745) (#12750) +* ENHANCEMENTS + * gitea dump: include version & Check InstallLock (#12760) (#12762) + +## [1.12.4](https://github.com/go-gitea/gitea/releases/tag/v1.12.4) - 2020-09-02 + +* SECURITY + * Escape provider name in oauth2 provider redirect (#12648) (#12650) + * Escape Email on password reset page (#12610) (#12612) + * When reading expired sessions - expire them (#12686) (#12690) +* ENHANCEMENTS + * StaticRootPath configurable at compile time (#12371) (#12652) +* BUGFIXES + * Fix to show an issue that is related to a deleted issue (#12651) (#12692) + * Expire time acknowledged for cache (#12605) (#12611) + * Fix diff path unquoting (#12554) (#12575) + * Improve HTML escaping helper (#12562) + * models: break out of loop (#12386) (#12561) + * Default empty merger list to those with write permissions (#12535) (#12560) + * Skip SSPI authentication attempts for /api/internal (#12556) (#12559) + * Prevent NPE on commenting on lines with invalidated comments (#12549) (#12550) + * Remove hardcoded ES indexername (#12521) (#12526) + * Fix bug preventing transfer to private organization (#12497) (#12501) + * Keys should not verify revoked email addresses (#12486) (#12495) + * Do not add prefix on http/https submodule links (#12477) (#12479) + * Fix ignored login on compare (#12476) (#12478) + * Fix incorrect error logging in Stats indexer and OAuth2 (#12387) (#12422) + * Upgrade google/go-github to v32.1.0 (#12361) (#12390) + * Render emoji's of Commit message on feed-page (#12373) + * Fix handling of diff on unrelated branches when Git 2.28 used (#12370) + ## [1.12.3](https://github.com/go-gitea/gitea/releases/tag/v1.12.3) - 2020-07-28 * BUGFIXES diff --git a/Dockerfile b/Dockerfile index 9c678a60ad35..efa7dfbabc76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ENV GOPROXY ${GOPROXY:-direct} ARG GITEA_VERSION ARG TAGS="sqlite sqlite_unlock_notify" -ENV TAGS "bindata $TAGS" +ENV TAGS "bindata timetzdata $TAGS" ARG CGO_EXTRA_CFLAGS #Build deps @@ -37,8 +37,8 @@ RUN apk --no-cache add \ openssh \ s6 \ sqlite \ + socat \ su-exec \ - tzdata \ gnupg RUN addgroup \ diff --git a/MAINTAINERS b/MAINTAINERS index d805520fd46f..058e35fcc679 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -37,3 +37,4 @@ Mura Li (@typeless) jaqra (@jaqra) David Svantesson (@davidsvantesson) CirnoT (@CirnoT) +a1012112796 <1012112796@qq.com> (@a1012112796) diff --git a/Makefile b/Makefile index d0c1fdabfae4..cd4f34c8c3ee 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) COMMA := , XGO_VERSION := go-1.15.x -MIN_GO_VERSION := 001012000 +MIN_GO_VERSION := 001013000 MIN_NODE_VERSION := 010013000 DOCKER_IMAGE ?= gitea/gitea @@ -42,8 +42,10 @@ ifeq ($(HAS_GO), GO) endif ifeq ($(OS), Windows_NT) + GOFLAGS := -v -buildmode=exe EXECUTABLE ?= gitea.exe else + GOFLAGS := -v EXECUTABLE ?= gitea endif @@ -55,7 +57,6 @@ endif GOFMT ?= gofmt -s -GOFLAGS := -v EXTRA_GOFLAGS ?= MAKE_VERSION := $(shell $(MAKE) -v | head -n 1) @@ -119,11 +120,12 @@ endif GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES)) -#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.20.1 +#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl}}/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl}}/api/v1"|"basePath": "/api/v1"|g +SWAGGER_EXCLUDE := code.gitea.io/sdk SWAGGER_NEWLINE_COMMAND := -e '$$a\' TEST_MYSQL_HOST ?= mysql:3306 @@ -154,6 +156,7 @@ help: @echo " - build build everything" @echo " - frontend build frontend files" @echo " - backend build backend files" + @echo " - watch watch everything and continuously rebuild" @echo " - watch-frontend watch frontend files and continuously rebuild" @echo " - watch-backend watch backend files and continuously rebuild" @echo " - clean delete backend and integration files" @@ -169,6 +172,8 @@ help: @echo " - fomantic build fomantic files" @echo " - generate run \"go generate\"" @echo " - fmt format the Go code" + @echo " - generate-license update license files" + @echo " - generate-gitignore update gitignore files" @echo " - generate-swagger generate the swagger spec from code comments" @echo " - swagger-validate check if the swagger spec is valid" @echo " - golangci-lint run golangci-lint linter" @@ -181,9 +186,9 @@ help: .PHONY: go-check go-check: - $(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell go version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');)) + $(eval GO_VERSION := $(shell printf "%03d%03d%03d" $(shell $(GO) version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');)) @if [ "$(GO_VERSION)" -lt "$(MIN_GO_VERSION)" ]; then \ - echo "Gitea requires Go 1.12 or greater to build. You can get it at https://golang.org/dl/"; \ + echo "Gitea requires Go 1.13 or greater to build. You can get it at https://golang.org/dl/"; \ exit 1; \ fi @@ -239,7 +244,7 @@ endif .PHONY: generate-swagger generate-swagger: - $(SWAGGER) generate spec -o './$(SWAGGER_SPEC)' + $(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' $(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)' @@ -313,6 +318,10 @@ lint-frontend: node_modules .PHONY: lint-backend lint-backend: golangci-lint revive vet +.PHONY: watch +watch: + bash tools/watch.sh + .PHONY: watch-frontend watch-frontend: node-check $(FOMANTIC_DEST) node_modules rm -rf $(WEBPACK_DEST_ENTRIES) @@ -461,7 +470,7 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql .PHONY: test-mssql-migration test-mssql-migration: migrations.mssql.test generate-ini-mssql - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test -test.failfast .PHONY: bench-sqlite bench-sqlite: integrations.sqlite.test generate-ini-sqlite @@ -556,7 +565,7 @@ release-windows: | $(DIST_DIRS) GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \ fi @echo "Warning: windows version is built using golang 1.14" - CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go go-1.14.x -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . + CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif @@ -609,7 +618,7 @@ release-docs: | $(DIST_DIRS) docs .PHONY: docs docs: @hash hugo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/gohugoio/hugo; \ + curl -sL https://github.com/gohugoio/hugo/releases/download/v0.74.3/hugo_0.74.3_Linux-64bit.tar.gz | tar zxf - -C /tmp && mv /tmp/hugo /usr/bin/hugo && chmod +x /usr/bin/hugo; \ fi cd docs; make trans-copy clean build-offline; @@ -667,6 +676,15 @@ update-translations: mv ./translations/*.ini ./options/locale/ rmdir ./translations +.PHONY: generate-license +generate-license: + GO111MODULE=on $(GO) run build/generate-licenses.go + +.PHONY: generate-gitignore +generate-gitignore: + GO111MODULE=on $(GO) run build/generate-gitignores.go + + .PHONY: generate-images generate-images: npm install --no-save --no-package-lock xmldom fabric imagemin-zopfli @@ -680,7 +698,7 @@ pr\#%: clean-all golangci-lint: @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ export BINARY="golangci-lint"; \ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.24.0; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.31.0; \ fi golangci-lint run --timeout 5m diff --git a/README.md b/README.md index e46ae4cd1af7..ad9322205a1f 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ or if sqlite support is required: The `build` target is split into two sub-targets: -- `make backend` which requires [Go 1.12](https://golang.org/dl/) or greater. +- `make backend` which requires [Go 1.13](https://golang.org/dl/) or greater. - `make frontend` which requires [Node.js 10.13](https://nodejs.org/en/download/) or greater. If pre-built frontend files are present it is possible to only build the backend: - TAGS="bindata" make backend + TAGS="bindata" make backend Parallelism is not supported for these targets, so please don't include `-j `. diff --git a/build.go b/build.go index 8f6fd48508b1..ab57fb1d9a0e 100644 --- a/build.go +++ b/build.go @@ -11,12 +11,12 @@ package main import ( // for lint - _ "github.com/BurntSushi/toml" _ "github.com/mgechev/dots" _ "github.com/mgechev/revive/formatter" _ "github.com/mgechev/revive/lint" _ "github.com/mgechev/revive/rule" _ "github.com/mitchellh/go-homedir" + _ "github.com/pelletier/go-toml" // for embed _ "github.com/shurcooL/vfsgen" diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go index f341c1ec484e..846bb076365d 100644 --- a/build/generate-gitignores.go +++ b/build/generate-gitignores.go @@ -21,12 +21,16 @@ import ( func main() { var ( - prefix = "gitea-gitignore" - url = "https://api.github.com/repos/github/gitignore/tarball" - destination = "" + prefix = "gitea-gitignore" + url = "https://api.github.com/repos/github/gitignore/tarball" + githubApiToken = "" + githubUsername = "" + destination = "" ) flag.StringVar(&destination, "dest", "options/gitignore/", "destination for the gitignores") + flag.StringVar(&githubUsername, "username", "", "github username") + flag.StringVar(&githubApiToken, "token", "", "github api token") flag.Parse() file, err := ioutil.TempFile(os.TempDir(), prefix) @@ -37,12 +41,19 @@ func main() { defer util.Remove(file.Name()) - resp, err := http.Get(url) - + req, err := http.NewRequest("GET", url, nil) if err != nil { log.Fatalf("Failed to download archive. %s", err) } + if len(githubApiToken) > 0 && len(githubUsername) > 0 { + req.SetBasicAuth(githubUsername, githubApiToken) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } defer resp.Body.Close() if _, err := io.Copy(file, resp.Body); err != nil { diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 53623e4193c8..9dd13adf9a7b 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -21,12 +21,16 @@ import ( func main() { var ( - prefix = "gitea-licenses" - url = "https://api.github.com/repos/spdx/license-list-data/tarball" - destination = "" + prefix = "gitea-licenses" + url = "https://api.github.com/repos/spdx/license-list-data/tarball" + githubApiToken = "" + githubUsername = "" + destination = "" ) flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses") + flag.StringVar(&githubUsername, "username", "", "github username") + flag.StringVar(&githubApiToken, "token", "", "github api token") flag.Parse() file, err := ioutil.TempFile(os.TempDir(), prefix) @@ -37,8 +41,16 @@ func main() { defer util.Remove(file.Name()) - resp, err := http.Get(url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } + + if len(githubApiToken) > 0 && len(githubUsername) > 0 { + req.SetBasicAuth(githubUsername, githubApiToken) + } + resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatalf("Failed to download archive. %s", err) } diff --git a/build/lint.go b/build/lint.go index bc6ddbec4146..60e93697aa7a 100644 --- a/build/lint.go +++ b/build/lint.go @@ -15,12 +15,12 @@ import ( "path/filepath" "strings" - "github.com/BurntSushi/toml" "github.com/mgechev/dots" "github.com/mgechev/revive/formatter" "github.com/mgechev/revive/lint" "github.com/mgechev/revive/rule" "github.com/mitchellh/go-homedir" + "github.com/pelletier/go-toml" ) func fail(err string) { @@ -133,7 +133,7 @@ func parseConfig(path string) *lint.Config { if err != nil { fail("cannot read the config file") } - _, err = toml.Decode(string(file), config) + err = toml.Unmarshal(file, config) if err != nil { fail("cannot parse the config file: " + err.Error()) } diff --git a/cmd/admin.go b/cmd/admin.go index a049f7f2cf17..d5036572500a 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -6,6 +6,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -29,16 +30,38 @@ var ( Name: "admin", Usage: "Command line interface to perform common administrative operations", Subcommands: []cli.Command{ - subcmdCreateUser, - subcmdChangePassword, + subcmdUser, subcmdRepoSyncReleases, subcmdRegenerate, subcmdAuth, }, } - subcmdCreateUser = cli.Command{ - Name: "create-user", + subcmdUser = cli.Command{ + Name: "user", + Usage: "Modify users", + Subcommands: []cli.Command{ + microcmdUserCreate, + microcmdUserList, + microcmdUserChangePassword, + microcmdUserDelete, + }, + } + + microcmdUserList = cli.Command{ + Name: "list", + Usage: "List users", + Action: runListUsers, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "admin", + Usage: "List only admin users", + }, + }, + } + + microcmdUserCreate = cli.Command{ + Name: "create", Usage: "Create a new user in database", Action: runCreateUser, Flags: []cli.Flag{ @@ -82,7 +105,7 @@ var ( }, } - subcmdChangePassword = cli.Command{ + microcmdUserChangePassword = cli.Command{ Name: "change-password", Usage: "Change a user's password", Action: runChangePassword, @@ -100,6 +123,13 @@ var ( }, } + microcmdUserDelete = cli.Command{ + Name: "delete", + Usage: "Delete specific user", + Flags: []cli.Flag{idFlag}, + Action: runDeleteUser, + } + subcmdRepoSyncReleases = cli.Command{ Name: "repo-sync-releases", Usage: "Synchronize repository releases with tags", @@ -265,6 +295,13 @@ func runChangePassword(c *cli.Context) error { if !pwd.IsComplexEnough(c.String("password")) { return errors.New("Password does not meet complexity requirements") } + pwned, err := pwd.IsPwned(context.Background(), c.String("password")) + if err != nil { + return err + } + if pwned { + return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords") + } uname := c.String("username") user, err := models.GetUserByName(uname) if err != nil { @@ -369,6 +406,56 @@ func runCreateUser(c *cli.Context) error { return nil } +func runListUsers(c *cli.Context) error { + if err := initDB(); err != nil { + return err + } + + users, err := models.GetAllUsers() + + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0) + + if c.IsSet("admin") { + fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n") + for _, u := range users { + if u.IsAdmin { + fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive) + } + } + } else { + fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\n") + for _, u := range users { + fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin) + } + + } + + w.Flush() + return nil + +} + +func runDeleteUser(c *cli.Context) error { + if !c.IsSet("id") { + return fmt.Errorf("--id flag is missing") + } + + if err := initDB(); err != nil { + return err + } + + user, err := models.GetUserByID(c.Int64("id")) + if err != nil { + return err + } + + return models.DeleteUser(user) +} + func runRepoSyncReleases(c *cli.Context) error { if err := initDB(); err != nil { return err diff --git a/cmd/doctor.go b/cmd/doctor.go index 2a93db27da23..2ca2bb5e70b6 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm" "github.com/urfave/cli" ) @@ -62,6 +63,27 @@ var CmdDoctor = cli.Command{ Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`, }, }, + Subcommands: []cli.Command{ + cmdRecreateTable, + }, +} + +var cmdRecreateTable = cli.Command{ + Name: "recreate-table", + Usage: "Recreate tables from XORM definitions and copy the data.", + ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "Print SQL commands sent", + }, + }, + Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns. + +This command will cause Xorm to recreate tables, copying over the data and deleting the old table. + +You should back-up your database before doing this and ensure that your database is up-to-date first.`, + Action: runRecreateTable, } type check struct { @@ -136,6 +158,47 @@ var checklist = []check{ // more checks please append here } +func runRecreateTable(ctx *cli.Context) error { + // Redirect the default golog to here + golog.SetFlags(0) + golog.SetPrefix("") + golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT))) + + setting.NewContext() + setting.InitDBConfig() + + setting.EnableXORMLog = ctx.Bool("debug") + setting.Database.LogSQL = ctx.Bool("debug") + setting.Cfg.Section("log").Key("XORM").SetValue(",") + + setting.NewXORMLogService(!ctx.Bool("debug")) + if err := models.SetEngine(); err != nil { + fmt.Println(err) + fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") + return nil + } + + args := ctx.Args() + names := make([]string, 0, ctx.NArg()) + for i := 0; i < ctx.NArg(); i++ { + names = append(names, args.Get(i)) + } + + beans, err := models.NamesToBean(names...) + if err != nil { + return err + } + recreateTables := migrations.RecreateTables(beans...) + + return models.NewEngine(context.Background(), func(x *xorm.Engine) error { + if err := migrations.EnsureUpToDate(x); err != nil { + return err + } + return recreateTables(x) + }) + +} + func runDoctor(ctx *cli.Context) error { // Silence the default loggers diff --git a/cmd/dump.go b/cmd/dump.go index c64734122106..7ff986f14eac 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" "gitea.com/macaron/session" @@ -57,6 +58,8 @@ func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose boo if err != nil { return fmt.Errorf("Could not open directory %s: %s", absPath, err) } + defer dir.Close() + files, err := dir.Readdir(0) if err != nil { return fmt.Errorf("Unable to list files in %s: %s", absPath, err) @@ -186,6 +189,10 @@ func runDump(ctx *cli.Context) error { if _, err := setting.Cfg.Section("log.console").NewKey("STDERR", "true"); err != nil { fatal("Setting console logger to stderr failed: %v", err) } + if !setting.InstallLock { + log.Error("Is '%s' really the right config path?\n", setting.CustomConf) + return fmt.Errorf("gitea is not initialized") + } setting.NewServices() // cannot access session settings otherwise err := models.SetEngine() @@ -193,6 +200,10 @@ func runDump(ctx *cli.Context) error { return err } + if err := storage.Init(); err != nil { + return err + } + if file == nil { file, err = os.Create(fileName) if err != nil { @@ -227,11 +238,21 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include repositories: %v", err) } - if _, err := os.Stat(setting.LFS.ContentPath); !os.IsNotExist(err) { - log.Info("Dumping lfs... %s", setting.LFS.ContentPath) - if err := addRecursive(w, "lfs", setting.LFS.ContentPath, verbose); err != nil { - fatal("Failed to include lfs: %v", err) + if err := storage.LFS.IterateObjects(func(objPath string, object storage.Object) error { + info, err := object.Stat() + if err != nil { + return err } + + return w.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: info, + CustomName: path.Join("data", "lfs", objPath), + }, + ReadCloser: object, + }) + }); err != nil { + fatal("Failed to dump LFS objects: %v", err) } } @@ -298,13 +319,31 @@ func runDump(ctx *cli.Context) error { } excludes = append(excludes, setting.RepoRootPath) - excludes = append(excludes, setting.LFS.ContentPath) + excludes = append(excludes, setting.LFS.Path) + excludes = append(excludes, setting.Attachment.Path) excludes = append(excludes, setting.LogRootPath) if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { fatal("Failed to include data directory: %v", err) } } + if err := storage.Attachments.IterateObjects(func(objPath string, object storage.Object) error { + info, err := object.Stat() + if err != nil { + return err + } + + return w.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: info, + CustomName: path.Join("data", "attachments", objPath), + }, + ReadCloser: object, + }) + }); err != nil { + fatal("Failed to dump attachments: %v", err) + } + // Doesn't check if LogRootPath exists before processing --skip-log intentionally, // ensuring that it's clear the dump is skipped whether the directory's initialized // yet or not. diff --git a/cmd/hook.go b/cmd/hook.go index 863ed832a9ba..1fcc0a18c316 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -170,7 +170,7 @@ Gitea or set your environment appropriately.`, "") username := os.Getenv(models.EnvRepoUsername) reponame := os.Getenv(models.EnvRepoName) userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) - prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) + prID, _ := strconv.ParseInt(os.Getenv(models.EnvPRID), 10, 64) isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey)) hookOptions := private.HookOptions{ @@ -285,6 +285,12 @@ func runHookUpdate(c *cli.Context) error { } func runHookPostReceive(c *cli.Context) error { + // First of all run update-server-info no matter what + if _, err := git.NewCommand("update-server-info").Run(); err != nil { + return fmt.Errorf("Failed to call 'git update-server-info': %v", err) + } + + // Now if we're an internal don't do anything else if os.Getenv(models.EnvIsInternal) == "true" { return nil } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index 3a26f0b3f5b8..871baed92de9 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -7,6 +7,7 @@ package cmd import ( "context" "fmt" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/migrations" @@ -30,9 +31,9 @@ var CmdMigrateStorage = cli.Command{ Usage: "Kinds of files to migrate, currently only 'attachments' is supported", }, cli.StringFlag{ - Name: "store, s", - Value: "local", - Usage: "New storage type, local or minio", + Name: "storage, s", + Value: "", + Usage: "New storage type: local (default) or minio", }, cli.StringFlag{ Name: "path, p", @@ -83,6 +84,27 @@ func migrateAttachments(dstStorage storage.ObjectStorage) error { }) } +func migrateLFS(dstStorage storage.ObjectStorage) error { + return models.IterateLFS(func(mo *models.LFSMetaObject) error { + _, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath()) + return err + }) +} + +func migrateAvatars(dstStorage storage.ObjectStorage) error { + return models.IterateUser(func(user *models.User) error { + _, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath()) + return err + }) +} + +func migrateRepoAvatars(dstStorage storage.ObjectStorage) error { + return models.IterateRepository(func(repo *models.Repository) error { + _, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath()) + return err + }) +} + func runMigrateStorage(ctx *cli.Context) error { if err := initDB(); err != nil { return err @@ -99,49 +121,70 @@ func runMigrateStorage(ctx *cli.Context) error { return err } + goCtx := context.Background() + if err := storage.Init(); err != nil { return err } - tp := ctx.String("type") + var dstStorage storage.ObjectStorage + var err error + switch strings.ToLower(ctx.String("storage")) { + case "": + fallthrough + case string(storage.LocalStorageType): + p := ctx.String("path") + if p == "" { + log.Fatal("Path must be given when storage is loal") + return nil + } + dstStorage, err = storage.NewLocalStorage( + goCtx, + storage.LocalStorageConfig{ + Path: p, + }) + case string(storage.MinioStorageType): + dstStorage, err = storage.NewMinioStorage( + goCtx, + storage.MinioStorageConfig{ + Endpoint: ctx.String("minio-endpoint"), + AccessKeyID: ctx.String("minio-access-key-id"), + SecretAccessKey: ctx.String("minio-secret-access-key"), + Bucket: ctx.String("minio-bucket"), + Location: ctx.String("minio-location"), + BasePath: ctx.String("minio-base-path"), + UseSSL: ctx.Bool("minio-use-ssl"), + }) + default: + return fmt.Errorf("Unsupported storage type: %s", ctx.String("storage")) + } + if err != nil { + return err + } + + tp := strings.ToLower(ctx.String("type")) switch tp { case "attachments": - var dstStorage storage.ObjectStorage - var err error - switch ctx.String("store") { - case "local": - p := ctx.String("path") - if p == "" { - log.Fatal("Path must be given when store is loal") - return nil - } - dstStorage, err = storage.NewLocalStorage(p) - case "minio": - dstStorage, err = storage.NewMinioStorage( - context.Background(), - ctx.String("minio-endpoint"), - ctx.String("minio-access-key-id"), - ctx.String("minio-secret-access-key"), - ctx.String("minio-bucket"), - ctx.String("minio-location"), - ctx.String("minio-base-path"), - ctx.Bool("minio-use-ssl"), - ) - default: - return fmt.Errorf("Unsupported attachments store type: %s", ctx.String("store")) + if err := migrateAttachments(dstStorage); err != nil { + return err } - - if err != nil { + case "lfs": + if err := migrateLFS(dstStorage); err != nil { return err } - if err := migrateAttachments(dstStorage); err != nil { + case "avatars": + if err := migrateAvatars(dstStorage); err != nil { return err } - - log.Warn("All files have been copied to the new placement but old files are still on the orignial placement.") - - return nil + case "repo-avatars": + if err := migrateRepoAvatars(dstStorage); err != nil { + return err + } + default: + return fmt.Errorf("Unsupported storage: %s", ctx.String("type")) } + log.Warn("All files have been copied to the new placement but old files are still on the orignial placement.") + return nil } diff --git a/cmd/serv.go b/cmd/serv.go index 7c2be5157ad1..1b41a5a0782c 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/setting" "github.com/dgrijalva/jwt-go" + "github.com/kballard/go-shellquote" "github.com/unknwon/com" "github.com/urfave/cli" ) @@ -50,8 +51,11 @@ var CmdServ = cli.Command{ } func setup(logPath string, debug bool) { - if !debug { - _ = log.DelLogger("console") + _ = log.DelLogger("console") + if debug { + _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) + } else { + _ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`) } setting.NewContext() if debug { @@ -59,14 +63,6 @@ func setup(logPath string, debug bool) { } } -func parseCmd(cmd string) (string, string) { - ss := strings.SplitN(cmd, " ", 2) - if len(ss) != 2 { - return "", "" - } - return ss[0], strings.Replace(ss[1], "'/", "'", 1) -} - var ( allowedCommands = map[string]models.AccessMode{ "git-upload-pack": models.AccessModeRead, @@ -117,16 +113,34 @@ func runServ(c *cli.Context) error { if err != nil { fail("Internal error", "Failed to check provided key: %v", err) } - if key.Type == models.KeyTypeDeploy { + switch key.Type { + case models.KeyTypeDeploy: println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.") - } else { + case models.KeyTypePrincipal: + println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Gitea does not provide shell access.") + default: println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.") } println("If this is unexpected, please log in with password and setup Gitea under another user.") return nil + } else if c.Bool("debug") { + log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND")) } - verb, args := parseCmd(cmd) + words, err := shellquote.Split(cmd) + if err != nil { + fail("Error parsing arguments", "Failed to parse arguments: %v", err) + } + + if len(words) < 2 { + fail("Too few arguments", "Too few arguments in cmd: %s", cmd) + } + + verb := words[0] + repoPath := words[1] + if repoPath[0] == '/' { + repoPath = repoPath[1:] + } var lfsVerb string if verb == lfsAuthenticateVerb { @@ -134,17 +148,17 @@ func runServ(c *cli.Context) error { fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled") } - argsSplit := strings.Split(args, " ") - if len(argsSplit) >= 2 { - args = strings.TrimSpace(argsSplit[0]) - lfsVerb = strings.TrimSpace(argsSplit[1]) + if len(words) > 2 { + lfsVerb = words[2] } } - repoPath := strings.ToLower(strings.Trim(args, "'")) + // LowerCase and trim the repoPath as that's how they are stored. + repoPath = strings.ToLower(strings.TrimSpace(repoPath)) + rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { - fail("Invalid repository path", "Invalid repository path: %v", args) + fail("Invalid repository path", "Invalid repository path: %v", repoPath) } username := strings.ToLower(rr[0]) @@ -203,11 +217,13 @@ func runServ(c *cli.Context) error { os.Setenv(models.EnvRepoName, results.RepoName) os.Setenv(models.EnvRepoUsername, results.OwnerName) os.Setenv(models.EnvPusherName, results.UserName) + os.Setenv(models.EnvPusherEmail, results.UserEmail) os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) - os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) - os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) + os.Setenv(models.EnvRepoID, strconv.FormatInt(results.RepoID, 10)) + os.Setenv(models.EnvPRID, fmt.Sprintf("%d", 0)) os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey)) os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID)) + os.Setenv(models.EnvAppURL, setting.AppURL) //LFS token authentication if verb == lfsAuthenticateVerb { diff --git a/cmd/web.go b/cmd/web.go index f0e1b16e7fe1..e16d1afb5343 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -19,6 +19,8 @@ import ( "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" + "gitea.com/macaron/macaron" + context2 "github.com/gorilla/context" "github.com/unknwon/com" "github.com/urfave/cli" @@ -93,7 +95,7 @@ func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { // Remove the trailing slash at the end of setting.AppURL, the request // URI always contains a leading slash, which would result in a double // slash - target := strings.TrimRight(setting.AppURL, "/") + r.URL.RequestURI() + target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI() http.Redirect(w, r, target, http.StatusFound) } @@ -114,6 +116,39 @@ func runWeb(ctx *cli.Context) error { setting.WritePIDFile = true } + // Flag for port number in case first time run conflict. + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + + // Perform pre-initialization + needsInstall := routers.PreInstallInit(graceful.GetManager().HammerContext()) + if needsInstall { + m := routes.NewMacaron() + routes.RegisterInstallRoute(m) + err := listen(m, false) + select { + case <-graceful.GetManager().IsShutdown(): + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err + default: + } + } else { + NoInstallListener() + } + + if setting.EnablePprof { + go func() { + log.Info("Starting pprof server on localhost:6060") + log.Info("%v", http.ListenAndServe("localhost:6060", nil)) + }() + } + + log.Info("Global init") // Perform global initialization routers.GlobalInit(graceful.GetManager().HammerContext()) @@ -121,41 +156,49 @@ func runWeb(ctx *cli.Context) error { m := routes.NewMacaron() routes.RegisterRoutes(m) - // Flag for port number in case first time run conflict. - if ctx.IsSet("port") { - setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1) - setting.HTTPPort = ctx.String("port") + err := listen(m, true) + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err +} - switch setting.Protocol { - case setting.UnixSocket: - case setting.FCGI: - case setting.FCGIUnix: - default: - // Save LOCAL_ROOT_URL if port changed - cfg := ini.Empty() - if com.IsFile(setting.CustomConf) { - // Keeps custom settings if there is already something. - if err := cfg.Append(setting.CustomConf); err != nil { - return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err) - } - } +func setPort(port string) error { + setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1) + setting.HTTPPort = port - defaultLocalURL := string(setting.Protocol) + "://" - if setting.HTTPAddr == "0.0.0.0" { - defaultLocalURL += "localhost" - } else { - defaultLocalURL += setting.HTTPAddr + switch setting.Protocol { + case setting.UnixSocket: + case setting.FCGI: + case setting.FCGIUnix: + default: + // Save LOCAL_ROOT_URL if port changed + cfg := ini.Empty() + if com.IsFile(setting.CustomConf) { + // Keeps custom settings if there is already something. + if err := cfg.Append(setting.CustomConf); err != nil { + return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err) } - defaultLocalURL += ":" + setting.HTTPPort + "/" + } + + defaultLocalURL := string(setting.Protocol) + "://" + if setting.HTTPAddr == "0.0.0.0" { + defaultLocalURL += "localhost" + } else { + defaultLocalURL += setting.HTTPAddr + } + defaultLocalURL += ":" + setting.HTTPPort + "/" - cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) + cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) - if err := cfg.SaveTo(setting.CustomConf); err != nil { - return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err) - } + if err := cfg.SaveTo(setting.CustomConf); err != nil { + return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err) } } + return nil +} +func listen(m *macaron.Macaron, handleRedirector bool) error { listenAddr := setting.HTTPAddr if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix { listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort) @@ -166,37 +209,40 @@ func runWeb(ctx *cli.Context) error { log.Info("LFS server enabled") } - if setting.EnablePprof { - go func() { - log.Info("Starting pprof server on localhost:6060") - log.Info("%v", http.ListenAndServe("localhost:6060", nil)) - }() - } - var err error switch setting.Protocol { case setting.HTTP: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runHTTP("tcp", listenAddr, context2.ClearHandler(m)) case setting.HTTPS: if setting.EnableLetsEncrypt { err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m)) break } - if setting.RedirectOtherPort { - go runHTTPRedirector() - } else { - NoHTTPRedirector() + if handleRedirector { + if setting.RedirectOtherPort { + go runHTTPRedirector() + } else { + NoHTTPRedirector() + } } err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) case setting.FCGI: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runFCGI("tcp", listenAddr, context2.ClearHandler(m)) case setting.UnixSocket: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runHTTP("unix", listenAddr, context2.ClearHandler(m)) case setting.FCGIUnix: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runFCGI("unix", listenAddr, context2.ClearHandler(m)) default: log.Fatal("Invalid protocol: %s", setting.Protocol) @@ -206,8 +252,5 @@ func runWeb(ctx *cli.Context) error { log.Critical("Failed to start server: %v", err) } log.Info("HTTP Listener: %s Closed", listenAddr) - <-graceful.GetManager().Done() - log.Info("PID: %d Gitea Web Finished", os.Getpid()) - log.Close() - return nil + return err } diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index f3c41766af49..9e039de69996 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -37,6 +37,12 @@ func NoMainListener() { graceful.GetManager().InformCleanup() } +// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener +// for our install HTTP/HTTPS service +func NoInstallListener() { + graceful.GetManager().InformCleanup() +} + func runFCGI(network, listenAddr string, m http.Handler) error { // This needs to handle stdin as fcgi point fcgiServer := graceful.NewServer(network, listenAddr) diff --git a/contrib/ide/vscode/tasks.json b/contrib/ide/vscode/tasks.json index a9876f71013e..e35ae303b27b 100644 --- a/contrib/ide/vscode/tasks.json +++ b/contrib/ide/vscode/tasks.json @@ -12,15 +12,14 @@ "focus": false, "panel": "shared" }, - "args": ["build"], "linux": { - "args": [ "-o", "gitea", "${workspaceRoot}/main.go" ] + "args": ["build", "-o", "gitea", "${workspaceRoot}/main.go" ] }, "osx": { - "args": [ "-o", "gitea", "${workspaceRoot}/main.go" ] + "args": ["build", "-o", "gitea", "${workspaceRoot}/main.go" ] }, "windows": { - "args": [ "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""] + "args": ["build", "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""] }, "problemMatcher": ["$go"] }, @@ -35,15 +34,14 @@ "focus": false, "panel": "shared" }, - "args": ["build", "-tags=\"sqlite sqlite_unlock_notify\""], "linux": { - "args": ["-o", "gitea", "${workspaceRoot}/main.go"] + "args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea", "${workspaceRoot}/main.go"] }, "osx": { - "args": ["-o", "gitea", "${workspaceRoot}/main.go"] + "args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea", "${workspaceRoot}/main.go"] }, "windows": { - "args": ["-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""] + "args": ["build", "-tags=\"sqlite sqlite_unlock_notify\"", "-o", "gitea.exe", "\"${workspaceRoot}\\main.go\""] }, "problemMatcher": ["$go"] } diff --git a/contrib/k8s/gitea.yml b/contrib/k8s/gitea.yml deleted file mode 100644 index c4aed869f7fe..000000000000 --- a/contrib/k8s/gitea.yml +++ /dev/null @@ -1,107 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: gitea ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: gitea - namespace: gitea - labels: - app: gitea -spec: - replicas: 1 - template: - metadata: - name: gitea - labels: - app: gitea - spec: - containers: - - name: gitea - image: gitea/gitea:latest - imagePullPolicy: Always - volumeMounts: - - mountPath: "/var/lib/gitea" - name: "root" - - mountPath: "/data" - name: "data" - ports: - - containerPort: 22 - name: ssh - protocol: TCP - - containerPort: 3000 - name: http - protocol: TCP - restartPolicy: Always - volumes: - # Set up a data directory for gitea - # For production usage, you should consider using PV/PVC instead(or simply using storage like NAS) - # For more details, please see https://kubernetes.io/docs/concepts/storage/volumes/ - - name: "root" - hostPath: - # directory location on host - path: "/var/lib/gitea" - # this field is optional - type: Directory - - name: "data" - hostPath: - path: "/data/gitea" - type: Directory - selector: - matchLabels: - app: gitea ---- -# Using cluster mode -apiVersion: v1 -kind: Service -metadata: - name: gitea-web - namespace: gitea - labels: - app: gitea-web -spec: - ports: - - port: 80 - targetPort: 3000 - name: http - selector: - app: gitea ---- -# Using node-port mode -# This mainly open a specific TCP port for SSH usage on each host, -# so you can use a proxy layer to handle it(e.g. slb, nginx) -apiVersion: v1 -kind: Service -metadata: - name: gitea-ssh - namespace: gitea - labels: - app: gitea-ssh -spec: - ports: - - port: 22 - targetPort: 22 - nodePort: 30022 - name: ssh - selector: - app: gitea - type: NodePort ---- -# Ingress is always suitable for HTTP usage, -# we suggest using an proxy layer such as slb to send traffic to different ports. -# Usually 80/443 for web and 22 directly for SSH. -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: gitea - namespace: gitea -spec: - rules: - - host: your-gitea-host.com - http: - paths: - - backend: - serviceName: gitea-web - servicePort: 80 diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 8d4636bfe412..a4e35d2495f5 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -30,6 +30,8 @@ ANSI_CHARSET = FORCE_PRIVATE = false ; Default privacy setting when creating a new repository, allowed values: last, private, public. Default is last which means the last setting used. DEFAULT_PRIVATE = last +; Default private when using push-to-create +DEFAULT_PUSH_CREATE_PRIVATE = true ; Global limit of repositories per user, applied at creation time. -1 means no limit MAX_CREATION_LIMIT = -1 ; Mirror sync queue length, increase if mirror syncing starts hanging @@ -64,6 +66,10 @@ PREFIX_ARCHIVE_FILES = true DISABLE_MIRRORS = false ; The default branch name of new repositories DEFAULT_BRANCH=master +; Allow adoption of unadopted repositories +ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES=false +; Allow deletion of unadopted repositories +ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES=false [repository.editor] ; List of file extensions for which lines should be wrapped in the Monaco editor @@ -76,15 +82,13 @@ PREVIEWABLE_FILE_MODES = markdown [repository.local] ; Path for local repository copy. Defaults to `tmp/local-repo` LOCAL_COPY_PATH = tmp/local-repo -; Path for local wiki copy. Defaults to `tmp/local-wiki` -LOCAL_WIKI_PATH = tmp/local-wiki [repository.upload] ; Whether repository file uploads are enabled. Defaults to `true` ENABLED = true ; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart) TEMP_PATH = data/tmp/uploads -; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type +; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ALLOWED_TYPES = ; Max size of each file in megabytes. Defaults to 3MB FILE_MAX_SIZE = 3 @@ -113,6 +117,10 @@ DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY=true ; List of reasons why a Pull Request or Issue can be locked LOCK_REASONS=Too heated,Off-topic,Resolved,Spam +[repository.release] +; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +ALLOWED_TYPES = + [repository.signing] ; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey ; run in the context of the RUN_USER @@ -124,6 +132,8 @@ SIGNING_KEY = default ; by setting the SIGNING_KEY ID to the correct ID.) SIGNING_NAME = SIGNING_EMAIL = +; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter +DEFAULT_TRUST_MODEL=collaborator ; Determines when gitea should sign the initial commit when creating a repository ; Either: ; - never @@ -287,6 +297,9 @@ SSH_ROOT_PATH = ; Gitea will create a authorized_keys file by default when it is not using the internal ssh server ; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. SSH_CREATE_AUTHORIZED_KEYS_FILE = true +; Gitea will create a authorized_principals file by default when it is not using the internal ssh server +; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. +SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ; For the built-in SSH server, choose the ciphers to support for SSH connections, ; for system SSH this setting has no effect SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128 @@ -302,7 +315,26 @@ SSH_KEY_TEST_PATH = ; Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. SSH_KEYGEN_PATH = ssh-keygen ; Enable SSH Authorized Key Backup when rewriting all keys, default is true -SSH_BACKUP_AUTHORIZED_KEYS = true +SSH_AUTHORIZED_KEYS_BACKUP = true +; Determines which principals to allow +; - empty: if SSH_TRUSTED_USER_CA_KEYS is empty this will default to off, otherwise will default to email, username. +; - off: Do not allow authorized principals +; - email: the principal must match the user's email +; - username: the principal must match the user's username +; - anything: there will be no checking on the content of the principal +SSH_AUTHORIZED_PRINCIPALS_ALLOW = email, username +; Enable SSH Authorized Principals Backup when rewriting all keys, default is true +SSH_AUTHORIZED_PRINCIPALS_BACKUP = true +; Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication. +; Multiple keys should be comma separated. +; E.g."ssh- ". or "ssh- , ssh- ". +; For more information see "TrustedUserCAKeys" in the sshd config manpages. +SSH_TRUSTED_USER_CA_KEYS = +; Absolute path of the `TrustedUserCaKeys` file gitea will manage. +; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem +; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your +; sshd_config to point to this file. The official docker image will automatically work without further configuration. +SSH_TRUSTED_USER_CA_KEYS_FILENAME = ; Enable exposure of SSH clone URL to anonymous visitors, default is false SSH_EXPOSE_ANONYMOUS = false ; Indicate whether to check minimum key size with corresponding type @@ -365,7 +397,7 @@ STATIC_CACHE_TIME = 6h ED25519 = 256 ECDSA = 256 RSA = 2048 -DSA = 1024 +DSA = -1 ; set to 1024 to switch on [database] ; Database to use. Either "mysql", "postgres", "mssql" or "sqlite3". @@ -428,7 +460,15 @@ STARTUP_TIMEOUT=30s ; repo indexer by default disabled, since it uses a lot of disk space REPO_INDEXER_ENABLED = false +; Code search engine type, could be `bleve` or `elasticsearch`. +REPO_INDEXER_TYPE = bleve +; Index file used for code search. REPO_INDEXER_PATH = indexers/repos.bleve +; Code indexer connection string, available when `REPO_INDEXER_TYPE` is elasticsearch. i.e. http://elastic:changeme@localhost:9200 +REPO_INDEXER_CONN_STR = +; Code indexer name, available when `REPO_INDEXER_TYPE` is elasticsearch +REPO_INDEXER_NAME = gitea_codes + UPDATE_BUFFER_LEN = 20 MAX_FILE_SIZE = 1048576 ; A comma separated list of glob patterns (see https://github.com/gobwas/glob) to include @@ -451,8 +491,10 @@ LENGTH = 20 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. +; Provides the suffix of the default redis/disk queue name - specific queues can be overriden within in their [queue.name] sections. QUEUE_NAME = "_queue" +; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overriden within in their [queue.name] sections. +SET_NAME = "_unique" ; 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 @@ -492,18 +534,25 @@ REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL MIN_PASSWORD_LENGTH = 6 ; Set to true to allow users to import local server paths IMPORT_LOCAL_PATHS = false -; Set to true to prevent all users (including admin) from creating custom git hooks -DISABLE_GIT_HOOKS = false +; Set to false to allow users with git hook privileges to create custom git hooks. +; Custom git hooks can be used to perform arbitrary code execution on the host operating system. +; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service. +; By modifying the Gitea database, users can gain Gitea administrator privileges. +; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. +; WARNING: This maybe harmful to you website or your operating system. +DISABLE_GIT_HOOKS = true ; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true ;Comma separated list of character classes required to pass minimum complexity. ;If left empty or no valid values are specified, the default is off (no checking) ;Classes include "lower,upper,digit,spec" PASSWORD_COMPLEXITY = off -; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" -PASSWORD_HASH_ALGO = pbkdf2 +; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt" +PASSWORD_HASH_ALGO = argon2 ; Set false to allow JavaScript to read CSRF cookie CSRF_COOKIE_HTTP_ONLY = true +; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed +PASSWORD_CHECK_PWN = false [openid] ; @@ -567,12 +616,15 @@ ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false ENABLE_REVERSE_PROXY_EMAIL = false ; Enable captcha validation for registration ENABLE_CAPTCHA = false -; Type of captcha you want to use. Options: image, recaptcha +; Type of captcha you want to use. Options: image, recaptcha, hcaptcha CAPTCHA_TYPE = image ; Enable recaptcha to use Google's recaptcha service ; Go to https://www.google.com/recaptcha/admin to sign up for a key RECAPTCHA_SECRET = RECAPTCHA_SITEKEY = +; For hCaptcha, create an account at https://accounts.hcaptcha.com/login to get your keys +HCAPTCHA_SECRET = +HCAPTCHA_SITEKEY = ; Change this to use recaptcha.net or other recaptcha service RECAPTCHA_URL = https://www.google.com/recaptcha/ ; Default value for KeepEmailPrivate @@ -715,8 +767,6 @@ PROVIDER_CONFIG = data/sessions COOKIE_NAME = i_like_gitea ; If you use session in https only, default is false COOKIE_SECURE = false -; Enable set cookie, default is true -ENABLE_SET_COOKIE = true ; Session GC time interval in seconds, default is 86400 (1 day) GC_INTERVAL_TIME = 86400 ; Session life time in seconds, default is 86400 (1 day) @@ -747,36 +797,35 @@ DISABLE_GRAVATAR = false ENABLE_FEDERATED_AVATAR = false [attachment] -; Whether attachments are enabled. Defaults to `true` +; Whether issue and pull request attachments are enabled. Defaults to `true` ENABLED = true - -; One or more allowed types, e.g. "image/jpeg|image/png". Use "*/*" for all types. -ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip +; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip ; Max size of each file. Defaults to 4MB MAX_SIZE = 4 ; Max number of files per upload. Defaults to 5 MAX_FILES = 5 ; Storage type for attachments, `local` for local disk or `minio` for s3 compatible ; object storage service, default is `local`. -STORE_TYPE = local +STORAGE_TYPE = local ; Allows the storage driver to redirect to authenticated URLs to serve files directly ; Currently, only `minio` is supported. SERVE_DIRECT = false -; Path for attachments. Defaults to `data/attachments` only available when STORE_TYPE is `local` +; Path for attachments. Defaults to `data/attachments` only available when STORAGE_TYPE is `local` PATH = data/attachments -; Minio endpoint to connect only available when STORE_TYPE is `minio` +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` MINIO_ENDPOINT = localhost:9000 -; Minio accessKeyID to connect only available when STORE_TYPE is `minio` +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` MINIO_ACCESS_KEY_ID = -; Minio secretAccessKey to connect only available when STORE_TYPE is `minio` +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` MINIO_SECRET_ACCESS_KEY = -; Minio bucket to store the attachments only available when STORE_TYPE is `minio` +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` MINIO_BUCKET = gitea -; Minio location to create bucket only available when STORE_TYPE is `minio` +; Minio location to create bucket only available when STORAGE_TYPE is `minio` MINIO_LOCATION = us-east-1 -; Minio base path on the bucket only available when STORE_TYPE is `minio` +; Minio base path on the bucket only available when STORAGE_TYPE is `minio` MINIO_BASE_PATH = attachments/ -; Minio enabled ssl only available when STORE_TYPE is `minio` +; Minio enabled ssl only available when STORAGE_TYPE is `minio` MINIO_USE_SSL = false [time] @@ -828,8 +877,6 @@ LEVEL = FILE_NAME = ; This enables automated log rotate(switch of following options), default is true LOG_ROTATE = true -; Max number of lines in a single file, default is 1000000 -MAX_LINES = 1000000 ; Max size shift of a single file, default is 28 means 1 << 28, 256MB MAX_SIZE_SHIFT = 28 ; Segment log daily, default is true @@ -868,18 +915,45 @@ PASSWD = RECEIVERS = [cron] -; Enable running cron tasks periodically. -ENABLED = true +; Enable running all cron tasks periodically with default settings. +ENABLED = false ; Run cron tasks when Gitea starts. RUN_AT_START = false +; Basic cron tasks - enabled by default + +; Clean up old repository archives +[cron.archive_cleanup] +; Whether to enable the job +ENABLED = true +; Whether to always run at least once at start up time (if ENABLED) +RUN_AT_START = true +; Notice if not success +NO_SUCCESS_NOTICE = false +; Time interval for job to run +SCHEDULE = @every 24h +; Archives created more than OLDER_THAN ago are subject to deletion +OLDER_THAN = 24h + ; Update mirrors [cron.update_mirrors] SCHEDULE = @every 10m +; Enable running Update mirrors task periodically. +ENABLED = true +; Run Update mirrors task when Gitea starts. +RUN_AT_START = false +; Notice if not success +NO_SUCCESS_NOTICE = true ; Repository health check [cron.repo_health_check] SCHEDULE = @every 24h +; Enable running Repository health check task periodically. +ENABLED = true +; Run Repository health check task when Gitea starts. +RUN_AT_START = false +; Notice if not success +NO_SUCCESS_NOTICE = false TIMEOUT = 60s ; Arguments for command 'git fsck', e.g. "--unreachable --tags" ; see more on http://git-scm.com/docs/git-fsck @@ -887,34 +961,111 @@ ARGS = ; Check repository statistics [cron.check_repo_stats] +; Enable running check repository statistics task periodically. +ENABLED = true +; Run check repository statistics task when Gitea starts. RUN_AT_START = true +; Notice if not success +NO_SUCCESS_NOTICE = false SCHEDULE = @every 24h -; Clean up old repository archives -[cron.archive_cleanup] -; Whether to enable the job +[cron.update_migration_poster_id] +; Update migrated repositories' issues and comments' posterid, it will always attempt synchronization when the instance starts. ENABLED = true -; Whether to always run at least once at start up time (if ENABLED) +; Update migrated repositories' issues and comments' posterid when starting server (default true) RUN_AT_START = true -; Time interval for job to run +; Notice if not success +NO_SUCCESS_NOTICE = false +; Interval as a duration between each synchronization. (default every 24h) SCHEDULE = @every 24h -; Archives created more than OLDER_THAN ago are subject to deletion -OLDER_THAN = 24h ; Synchronize external user data (only LDAP user synchronization is supported) [cron.sync_external_users] +ENABLED = true ; Synchronize external user data when starting server (default false) RUN_AT_START = false +; Notice if not success +NO_SUCCESS_NOTICE = false ; Interval as a duration between each synchronization (default every 24h) SCHEDULE = @every 24h ; Create new users, update existing user data and disable users that are not in external source anymore (default) ; or only create new users if UPDATE_EXISTING is set to false UPDATE_EXISTING = true -; Update migrated repositories' issues and comments' posterid, it will always attempt synchronization when the instance starts. -[cron.update_migration_poster_id] -; Interval as a duration between each synchronization. (default every 24h) +; Clean-up deleted branches +[cron.deleted_branches_cleanup] +ENABLED = true +; Clean-up deleted branches when starting server (default true) +RUN_AT_START = true +; Notice if not success +NO_SUCCESS_NOTICE = false +; Interval as a duration between each synchronization (default every 24h) SCHEDULE = @every 24h +; deleted branches than OLDER_THAN ago are subject to deletion +OLDER_THAN = 24h + +; Extended cron task - not enabled by default + +; Delete all unactivated accounts +[cron.delete_inactive_accounts] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @annually +OLDER_THAN = 168h + +; Delete all repository archives +[cron.delete_repo_archives] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @annually + +; Garbage collect all repositories +[cron.git_gc_repos] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h +TIMEOUT = 60s +; Arguments for command 'git gc' +; The default value is same with [git] -> GC_ARGS +ARGS = + +; Update the '.ssh/authorized_keys' file with Gitea SSH keys +[cron.resync_all_sshkeys] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h + +; Resynchronize pre-receive, update and post-receive hooks of all repositories. +[cron.resync_all_hooks] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h + +; Reinitialize all missing Git repositories for which records exist +[cron.reinit_missing_repos] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h + +; Delete all repositories missing their Git files +[cron.delete_missing_repos] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h + +; Delete generated repository avatars +[cron.delete_generated_repository_avatars] +ENABLED = false +RUN_AT_START = false +NO_SUCCESS_NOTICE = false +SCHEDULE = @every 72h [git] ; The path of git executable. If empty, Gitea searches through the PATH environment. @@ -967,12 +1118,12 @@ DEFAULT_MAX_BLOB_SIZE = 10485760 ENABLE = true ; Lifetime of an OAuth2 access token in seconds ACCESS_TOKEN_EXPIRATION_TIME=3600 -; Lifetime of an OAuth2 access token in hours +; Lifetime of an OAuth2 refresh token in hours REFRESH_TOKEN_EXPIRATION_TIME=730 ; Check if refresh token got already used INVALIDATE_REFRESH_TOKENS=false -; OAuth2 authentication secret for access and refresh tokens, change this to a unique string. -JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU +; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate +JWT_SECRET= ; Maximum length of oauth2 token/cookie stored on server MAX_TOKEN_LENGTH=32767 @@ -1036,3 +1187,28 @@ QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" MAX_ATTEMPTS = 3 ; Backoff time per http/https request retry (seconds) RETRY_BACKOFF = 3 + +; default storage for attachments, lfs and avatars +[storage] +; storage type +STORAGE_TYPE = local + +; lfs storage will override storage +[lfs] +STORAGE_TYPE = local + +; customize storage +;[storage.my_minio] +;STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +;MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +;MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +;MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +;MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +;MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +;MINIO_USE_SSL = false diff --git a/docker/root/etc/templates/app.ini b/docker/root/etc/templates/app.ini index 9b23c1270d4d..1a831a6d10ee 100644 --- a/docker/root/etc/templates/app.ini +++ b/docker/root/etc/templates/app.ini @@ -29,6 +29,7 @@ HOST = $DB_HOST NAME = $DB_NAME USER = $DB_USER PASSWD = $DB_PASSWD +LOG_SQL = false [indexer] ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve @@ -44,6 +45,11 @@ REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars PATH = /data/gitea/attachments [log] +MODE = console +LEVEL = info +REDIRECT_MACARON_LOG = true +MACARON = console +ROUTER = console ROOT_PATH = /data/gitea/log [security] diff --git a/docker/root/etc/templates/sshd_config b/docker/root/etc/templates/sshd_config index 20e0b36012c9..26e26feb4127 100644 --- a/docker/root/etc/templates/sshd_config +++ b/docker/root/etc/templates/sshd_config @@ -8,11 +8,18 @@ ListenAddress :: LogLevel INFO HostKey /data/ssh/ssh_host_ed25519_key +HostCertificate /data/ssh/ssh_host_ed25519_cert HostKey /data/ssh/ssh_host_rsa_key -HostKey /data/ssh/ssh_host_dsa_key +HostCertificate /data/ssh/ssh_host_rsa_cert HostKey /data/ssh/ssh_host_ecdsa_key +HostCertificate /data/ssh/ssh_host_ecdsa_cert +HostKey /data/ssh/ssh_host_dsa_key +HostCertificate /data/ssh/ssh_host_dsa_cert AuthorizedKeysFile .ssh/authorized_keys +AuthorizedPrincipalsFile .ssh/authorized_principals +TrustedUserCAKeys /data/git/.ssh/gitea-trusted-user-ca-keys.pem +CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256,ssh-rsa UseDNS no AllowAgentForwarding no diff --git a/docs/config.yaml b/docs/config.yaml index 3935d04dd361..2bd0add4b902 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,8 +18,8 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io - version: 1.12.2 - minGoVersion: 1.12 + version: 1.12.5 + minGoVersion: 1.13 goVersion: 1.15 minNodeVersion: 10.13 diff --git a/docs/content/doc/advanced/api-usage.en-us.md b/docs/content/doc/advanced/api-usage.en-us.md index 624d639545c9..81ebc42eea84 100644 --- a/docs/content/doc/advanced/api-usage.en-us.md +++ b/docs/content/doc/advanced/api-usage.en-us.md @@ -70,9 +70,9 @@ the `token=` string in a GET request. ## API Guide: -API Reference guide is auto-generated by swagger and available on: +API Reference guide is auto-generated by swagger and available on: `https://gitea.your.host/api/swagger` - or on + or on [gitea demo instance](https://try.gitea.io/api/swagger) @@ -99,3 +99,8 @@ $ curl -H "X-Gitea-OTP: 123456" --request GET --url https://yourusername:yourpas ## Sudo The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo. + +## SDKs + +* [Official go-sdk](https://gitea.com/gitea/go-sdk) +* [more](https://gitea.com/gitea/awesome-gitea#user-content-sdk) 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 56da1d220c75..ed2254f2472c 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -51,6 +51,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `FORCE_PRIVATE`: **false**: Force every new repository to be private. - `DEFAULT_PRIVATE`: **last**: Default private when creating a new repository. \[last, private, public\] +- `DEFAULT_PUSH_CREATE_PRIVATE`: **true**: Default private when creating a new repository with push-to-create. - `MAX_CREATION_LIMIT`: **-1**: Global maximum creation limit of repositories per user, `-1` means no limit. - `PULL_REQUEST_QUEUE_LENGTH`: **1000**: Length of pull request patch test queue, make it @@ -69,9 +70,18 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed. - `ENABLE_PUSH_CREATE_USER`: **false**: Allow users to push local repositories to Gitea and have them automatically created for a user. - `ENABLE_PUSH_CREATE_ORG`: **false**: Allow users to push local repositories to Gitea and have them automatically created for an org. +- `DISABLED_REPO_UNITS`: **_empty_**: Comma separated list of globally disabled repo units. Allowed values: \[repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects\] +- `DEFAULT_REPO_UNITS`: **repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects**: Comma separated list of default repo units. Allowed values: \[repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects\]. Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. External wiki and issue tracker can't be enabled by default as it requires additional settings. Disabled repo units will not be added to new repositories regardless if it is in the default list. - `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository. - `DISABLE_MIRRORS`: **false**: Disable the creation of **new** mirrors. Pre-existing mirrors remain valid. - `DEFAULT_BRANCH`: **master**: Default branch name of all repositories. +- `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories +- `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories + +### Repository - Editor (`repository.editor`) + +- `LINE_WRAP_EXTENSIONS`: **.txt,.md,.markdown,.mdown,.mkd,**: List of file extensions for which lines should be wrapped in the Monaco editor. Separate extensions with a comma. To line wrap files without an extension, just put a comma +- `PREVIEWABLE_FILE_MODES`: **markdown**: Valid file modes that have a preview API associated with them, such as `api/v1/markdown`. Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match. ### Repository - Pull Request (`repository.pull-request`) @@ -91,6 +101,18 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked +### Repository - Upload (`repository.upload`) + +- `ENABLED`: **true**: Whether repository file uploads are enabled +- `TEMP_PATH`: **data/tmp/uploads**: Path for uploads (tmp gets deleted on gitea restart) +- `ALLOWED_TYPES`: **\**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +- `FILE_MAX_SIZE`: **3**: Max size of each file in megabytes. +- `MAX_FILES`: **5**: Max number of files per upload + +### Repository - Release (`repository.release`) + +- `ALLOWED_TYPES`: **\**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. + ### Repository - Signing (`repository.signing`) - `SIGNING_KEY`: **default**: \[none, KEYID, default \]: Key to sign with. @@ -101,6 +123,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `twofa`: Only sign if the user is logged in with twofa - `always`: Always sign - Options other than `never` and `always` can be combined as a comma separated list. +- `DEFAULT_TRUST_MODEL`: **collaborator**: \[collaborator, committer, collaboratorcommitter\]: The default trust model used for verifying commits. + - `collaborator`: Trust signatures signed by keys of collaborators. + - `committer`: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the commmitter). + - `collaboratorcommitter`: Trust signatures signed by keys of collaborators which match the commiter. - `WIKI`: **never**: \[never, pubkey, twofa, always, parentsigned\]: Sign commits to wiki. - `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions. - Options as above, with the addition of: @@ -111,6 +137,19 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `headsigned`: Only sign if the head commit in the head branch is signed. - `commitssigned`: Only sign if all the commits in the head branch to the merge point are signed. +## Repository - Local (`repository.local`) + +- `LOCAL_COPY_PATH`: **tmp/local-repo**: Path for temporary local repository copies. Defaults to `tmp/local-repo` + +## Repository - Upload (`repository.upload`) + +- `ENABLED`: **true**: Whether repository file uploads are enabled. Defaults to `true` +- `TEMP_PATH`: **data/tmp/uploads**: Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart) +- `ALLOWED_TYPES`: **_empty_**:; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type +- `FILE_MAX_SIZE`: **3**: Max size of each file in megabytes. Defaults to 3MB +- `MAX_FILES`: **5**: Max number of files per upload. Defaults to 5 + + ## CORS (`cors`) - `ENABLED`: **false**: enable cors headers (disabled by default) @@ -129,9 +168,13 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed. - `FEED_PAGING_NUM`: **20**: Number of items that are displayed in home feed. - `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph. +- `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment. - `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install. -- `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes +- `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page. +- `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes. regardless of the value of `DEFAULT_THEME`. +- `THEME_COLOR_META_TAG`: **#6cc644**: Value of `theme-color` meta tag, used by Android >= 5.0. An invalid color like "none" or "disable" will have the default style. More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android +- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB) - `REACTIONS`: All available reactions users can choose on issues/prs and comments Values can be emoji alias (:smile:) or a unicode emoji. For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png @@ -146,6 +189,12 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `NOTICE_PAGING_NUM`: **25**: Number of notices that are shown in one page. - `ORG_PAGING_NUM`: **50**: Number of organizations that are shown in one page. +### UI - Metadata (`ui.meta`) + +- `AUTHOR`: **Gitea - Git with a cup of tea**: Author meta tag of the homepage. +- `DESCRIPTION`: **Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go**: Description meta tag of the homepage. +- `KEYWORDS`: **go,git,self-hosted,gitea**: Keywords meta tag of the homepage. + ### UI - Notification (`ui.notification`) - `MIN_TIMEOUT`: **10s**: These options control how often notification endpoint is polled to update the notification count. On page load the notification count will be checked after `MIN_TIMEOUT`. The timeout will increase to `MAX_TIMEOUT` by `TIMEOUT_STEP` if the notification count is unchanged. Set MIN_TIMEOUT to 0 to turn off. @@ -192,26 +241,49 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. most cases you do not need to change the default value. Alter it only if your SSH server node is not the same as HTTP node. Do not set this variable if `PROTOCOL` is set to `unix`. + - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. +- `BUILTIN_SSH_SERVER_USER`: **%(RUN_USER)s**: Username to use for the built-in SSH Server. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_PORT`: **22**: SSH port displayed in clone URL. - `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address for the built-in SSH server. - `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. +- `SSH_ROOT_PATH`: **~/.ssh**: Root path of SSH directory. +- `SSH_CREATE_AUTHORIZED_KEYS_FILE`: **true**: Gitea will create a authorized_keys file by default when it is not using the internal ssh server. If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. +- `SSH_AUTHORIZED_KEYS_BACKUP`: **true**: Enable SSH Authorized Key Backup when rewriting all keys, default is true. +- `SSH_TRUSTED_USER_CA_KEYS`: **\**: Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication. Multiple keys should be comma separated. E.g.`ssh- ` or `ssh- , ssh- `. For more information see `TrustedUserCAKeys` in the sshd config man pages. When empty no file will be created and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` will default to `off`. +- `SSH_TRUSTED_USER_CA_KEYS_FILENAME`: **`RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem**: Absolute path of the `TrustedUserCaKeys` file gitea will manage. If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your sshd_config to point to this file. The official docker image will automatically work without further configuration. +- `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** or **username, email**: \[off, username, email, anything\]: Specify the principals values that users are allowed to use as principal. When set to `anything` no checks are done on the principal string. When set to `off` authorized principal are not allowed to be set. +- `SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE`: **false/true**: Gitea will create a authorized_principals file by default when it is not using the internal ssh server and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`. +- `SSH_AUTHORIZED_PRINCIPALS_BACKUP`: **false/true**: Enable SSH Authorized Principals Backup when rewriting all keys, default is true if `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`. +- `SSH_SERVER_CIPHERS`: **aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128**: For the built-in SSH server, choose the ciphers to support for SSH connections, for system SSH this setting has no effect. +- `SSH_SERVER_KEY_EXCHANGES`: **diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org**: For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, for system SSH this setting has no effect. +- `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96**: For the built-in SSH server, choose the MACs to support for SSH connections, for system SSH this setting has no effect +- `SSH_KEY_TEST_PATH`: **/tmp**: Directory to create temporary files in when testing public keys using ssh-keygen, default is the system temporary directory. +- `SSH_KEYGEN_PATH`: **ssh-keygen**: Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. +- `SSH_EXPOSE_ANONYMOUS`: **false**: Enable exposure of SSH clone URL to anonymous visitors, default is false. +- `MINIMUM_KEY_SIZE_CHECK`: **true**: Indicate whether to check minimum key size with corresponding type. + - `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures. - `DISABLE_ROUTER_LOG`: **false**: Mute printing of the router log. -- `CERT_FILE`: **https/cert.pem**: Cert file path used for HTTPS. From 1.11 paths are relative to `CUSTOM_PATH`. +- `CERT_FILE`: **https/cert.pem**: Cert file path used for HTTPS. When chaining, the server certificate must come first, then intermediate CA certificates (if any). From 1.11 paths are relative to `CUSTOM_PATH`. - `KEY_FILE`: **https/key.pem**: Key file path used for HTTPS. From 1.11 paths are relative to `CUSTOM_PATH`. - `STATIC_ROOT_PATH`: **./**: Upper level of template and static files path. +- `APP_DATA_PATH`: **data** (**/data/gitea** on docker): Default path for application data. - `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. +- `ENABLE_PPROF`: **false**: Application profiling (memory and cpu). For "web" command it listens on localhost:6060. For "serv" command it dumps to disk at `PPROF_DATA_PATH` as `(cpuprofile|memprofile)__` +- `PPROF_DATA_PATH`: **data/tmp/pprof**: `PPROF_DATA_PATH`, use an absolute path when you start gitea as service - `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_CONTENT_PATH`: **%(APP_DATA_PATH)/lfs**: Default LFS content path. (if it is on local storage.) - `LFS_JWT_SECRET`: **\**: LFS authentication secret, change this a unique string. - `LFS_HTTP_AUTH_EXPIRY`: **20m**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail. - `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit). -- `LFS_LOCK_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. +- `LFS_LOCKS_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. + - `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on. - `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true. - `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server). @@ -229,7 +301,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres\] (ex: /var/run/mysqld/mysqld.sock). - `NAME`: **gitea**: Database name. - `USER`: **root**: Database username. -- `PASSWD`: **\**: Database user password. Use \`your password\` for quoting if you use special characters in the password. +- `PASSWD`: **\**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password. - `SCHEMA`: **\**: For PostgreSQL only, schema to use if different from "public". The schema must exist beforehand, the user must have creation privileges on it, and the user search path must be set to the look into the schema first (e.g. `ALTER USER user SET SEARCH_PATH = schema_name,"$user",public;`). @@ -245,6 +317,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `require`: Enable TLS without any verifications. - `verify-ca`: Enable TLS with verification of the database server certificate against its root certificate. - `verify-full`: Enable TLS and verify the database server name matches the given certificate in either the `Common Name` or `Subject Alternative Name` fields. +- `SQLITE_TIMEOUT`: **500**: Query timeout for sqlite3 only. +- `ITERATE_BUFFER_SIZE`: **50**: Internal buffer size for iterating. - `CHARSET`: **utf8mb4**: For MySQL only, either "utf8" or "utf8mb4". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. - `LOG_SQL`: **true**: Log the executed SQL. @@ -270,7 +344,11 @@ relation to port exhaustion. - `ISSUE_INDEXER_QUEUE_BATCH_NUMBER`: **20**: Batch queue number. - `REPO_INDEXER_ENABLED`: **false**: Enables code search (uses a lot of disk space, about 6 times more than the repository size). +- `REPO_INDEXER_TYPE`: **bleve**: Code search engine type, could be `bleve` or `elasticsearch`. - `REPO_INDEXER_PATH`: **indexers/repos.bleve**: Index file used for code search. +- `REPO_INDEXER_CONN_STR`: ****: Code indexer connection string, available when `REPO_INDEXER_TYPE` is elasticsearch. i.e. http://elastic:changeme@localhost:9200 +- `REPO_INDEXER_NAME`: **gitea_codes**: Code indexer name, available when `REPO_INDEXER_TYPE` is elasticsearch + - `REPO_INDEXER_INCLUDE`: **empty**: A comma separated list of glob patterns (see https://github.com/gobwas/glob) to **include** in the index. Use `**.txt` to match any files with .txt extension. An empty list means include all files. - `REPO_INDEXER_EXCLUDE`: **empty**: A comma separated list of glob patterns (see https://github.com/gobwas/glob) to **exclude** from the index. Files that match this list will not be indexed, even if they match in `REPO_INDEXER_INCLUDE`. - `REPO_INDEXER_EXCLUDE_VENDORED`: **true**: Exclude vendored files from index. @@ -281,15 +359,13 @@ relation to port exhaustion. ## 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`**. +- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual 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. -- `SET_NAME`: **_unique**: The suffix that will added to the default redis -set name for unique queues. Individual queues will default to -**`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific -`queue.name` section. +- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. Options can be set using query params. Similarly LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value** +- `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section. +- `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to + **`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden 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. @@ -301,7 +377,9 @@ set name for unique queues. Individual queues will default to - `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 +- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. ## Security (`security`) @@ -315,20 +393,27 @@ set name for unique queues. Individual queues will default to authentication. - `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**: Header name for reverse proxy authentication provided email. -- `DISABLE_GIT_HOOKS`: **false**: Set to `true` to prevent all users (including admin) from creating custom - git hooks. +- `DISABLE_GIT_HOOKS`: **true**: Set to `false` to enable users with git hook privilege to create custom git hooks. + WARNING: Custom git hooks can be used to perform arbitrary code execution on the host operating system. + This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service. + By modifying the Gitea database, users can gain Gitea administrator privileges. + It also enables them to access other resources available to the user on the operating system that is running the + Gitea instance and perform arbitrary actions in the name of the Gitea OS user. + This maybe harmful to you website or your operating system. - `ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET`: **true**: Set to `false` to allow local users to push to gitea-repositories without setting up the Gitea environment. This is not recommended and if you want local users to push to gitea repositories you should set the environment appropriately. - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. - `INTERNAL_TOKEN`: **\**: Secret used to validate communication within Gitea binary. - `INTERNAL_TOKEN_URI`: ****: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) -- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[pbkdf2, argon2, scrypt, bcrypt\]. +- `PASSWORD_HASH_ALGO`: **argon2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\]. - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. +- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users. - `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off): - lower - use one or more lower latin characters - upper - use one or more upper latin characters - digit - use one or more digits - spec - use one or more special characters as ``!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~`` - off - do not check password complexity +- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed. ## OpenID (`openid`) @@ -366,13 +451,20 @@ set name for unique queues. Individual queues will default to - `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration. - `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation even for External Accounts (i.e. GitHub, OpenID Connect, etc). You must `ENABLE_CAPTCHA` also. -- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] +- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha\] - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha. - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha. - `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net. +- `HCAPTCHA_SECRET`: **""**: Sign up at https://www.hcaptcha.com/ to get a secret for hcaptcha. +- `HCAPTCHA_SITEKEY`: **""**: Sign up at https://www.hcaptcha.com/ to get a sitekey for hcaptcha. +- `DEFAULT_KEEP_EMAIL_PRIVATE`: **false**: By default set users to keep their email address private. +- `DEFAULT_ALLOW_CREATE_ORGANIZATION`: **true**: Allow new users to create organizations by default. - `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default. - `ALLOW_CROSS_REPOSITORY_DEPENDENCIES` : **true** Enable this to allow dependencies on issues from any repository where the user is granted access. - `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles. +- `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature. +- `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by deault. +- `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time. - `EMAIL_DOMAIN_WHITELIST`: **\**: If non-empty, list of domain names that can only be used to register on this instance. - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button @@ -385,6 +477,15 @@ set name for unique queues. Individual queues will default to - `NO_REPLY_ADDRESS`: **DOMAIN** Default value for the domain part of the user's email address in the git log if he has set KeepEmailPrivate to true. The user's email will be replaced with a concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. +## SSH Minimum Key Sizes (`ssh.minimum_key_sizes`) + +Define allowed algorithms and their minimum key length (use -1 to disable a type): + +- `ED25519`: **256** +- `ECDSA`: **256** +- `RSA`: **2048** +- `DSA`: **-1**: DSA is now disabled by default. Set to **1024** to re-enable but ensure you may need to reconfigure your SSHD provider + ## Webhook (`webhook`) - `QUEUE_LENGTH`: **1000**: Hook task queue length. Use caution when editing this value. @@ -409,9 +510,13 @@ set name for unique queues. Individual queues will default to - `USER`: **\**: Username of mailing user (usually the sender's e-mail address). - `PASSWD`: **\**: Password of mailing user. Use \`your password\` for quoting if you use special characters in the password. - Please note: authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via `STARTTLS`) or `HOST=localhost`. See [Email Setup]({{< relref "doc/usage/email-setup.en-us.md" >}}) for more information. +- `SEND_AS_PLAIN_TEXT`: **false**: Send mails as plain text. - `SKIP_VERIFY`: **false**: Whether or not to skip verification of certificates; `true` to disable verification. - **Warning:** This option is unsafe. Consider adding the certificate to the system trust store instead. - **Note:** Gitea only supports SMTP with STARTTLS. +- `USE_CERTIFICATE`: **false**: Use client certificate. +- `CERT_FILE`: **custom/mailer/cert.pem** +- `KEY_FILE`: **custom/mailer/key.pem** - `SUBJECT_PREFIX`: **\**: Prefix to be placed before e-mail subject lines. - `MAILER_TYPE`: **smtp**: \[smtp, sendmail, dummy\] - **smtp** Use SMTP to send mail @@ -423,7 +528,9 @@ set name for unique queues. Individual queues will default to - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`. - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path). +- `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. - `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail +- `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. ## Cache (`cache`) @@ -431,7 +538,7 @@ set name for unique queues. Individual queues will default to - `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`. - `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only. - `HOST`: **\**: Connection string for `redis` and `memcache`. - - Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180` + - Redis: `redis://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` - Memcache: `127.0.0.1:9090;127.0.0.1:9091` - `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching. @@ -448,6 +555,7 @@ set name for unique queues. Individual queues will default to - `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access. - `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID. - `GC_INTERVAL_TIME`: **86400**: GC interval in seconds. +- `SESSION_LIFE_TIME`: **86400**: Session life time in seconds, default is 86400 (1 day) ## Picture (`picture`) @@ -456,34 +564,45 @@ set name for unique queues. Individual queues will default to - `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. - `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see [http://www.libravatar.org](http://www.libravatar.org)). + +- `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`. - `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files. +- `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels. +- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels. +- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes. + +- `REPOSITORY_AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`. - `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files. - `REPOSITORY_AVATAR_FALLBACK`: **none**: How Gitea deals with missing repository avatars - none = no avatar will be displayed - random = random avatar will be generated - - image = default image will be used (which is set in `REPOSITORY_AVATAR_DEFAULT_IMAGE`) + - image = default image will be used (which is set in `REPOSITORY_AVATAR_FALLBACK_IMAGE`) - `REPOSITORY_AVATAR_FALLBACK_IMAGE`: **/img/repo_default.png**: Image used as default repository avatar (if `REPOSITORY_AVATAR_FALLBACK` is set to image and none was uploaded) -- `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels. -- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels. -- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes. -## Attachment (`attachment`) -- `ENABLED`: **true**: Enable this to allow uploading attachments. -- `ALLOWED_TYPES`: **see app.example.ini**: Allowed MIME types, e.g. `image/jpeg|image/png`. - Use `*/*` for all types. +## Project (`project`) + +Default templates for project boards: + +- `PROJECT_BOARD_BASIC_KANBAN_TYPE`: **To Do, In Progress, Done** +- `PROJECT_BOARD_BUG_TRIAGE_TYPE`: **Needs Triage, High Priority, Low Priority, Closed** + +## Issue and pull request attachments (`attachment`) + +- `ENABLED`: **true**: Whether issue and pull request attachments are enabled. +- `ALLOWED_TYPES`: **.docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `MAX_SIZE`: **4**: Maximum size (MB). - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. -- `STORE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local`. +- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. -- `PATH`: **data/attachments**: Path to store attachments only available when STORE_TYPE is `local` -- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when STORE_TYPE is `minio` -- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORE_TYPE is `minio` -- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORE_TYPE is `minio` -- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORE_TYPE is `minio` -- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORE_TYPE is `minio` -- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORE_TYPE is `minio` -- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORE_TYPE is `minio` +- `PATH`: **data/attachments**: Path to store attachments only available when STORAGE_TYPE is `local` +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when STORAGE_TYPE is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio` +- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio` ## Log (`log`) @@ -549,38 +668,88 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` ## Cron (`cron`) -- `ENABLED`: **true**: Run cron tasks periodically. +- `ENABLED`: **false**: Enable to run all cron tasks periodically with default settings. - `RUN_AT_START`: **false**: Run cron tasks at application start-up. +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. -### Cron - Cleanup old repository archives (`cron.archive_cleanup`) +### Basic cron tasks - enabled by default + +#### Cron - Cleanup old repository archives (`cron.archive_cleanup`) - `ENABLED`: **true**: Enable service. - `RUN_AT_START`: **true**: Run tasks at start up time (if ENABLED). - `SCHEDULE`: **@every 24h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. - `OLDER_THAN`: **24h**: Archives created more than `OLDER_THAN` ago are subject to deletion, e.g. `12h`. -### Cron - Update Mirrors (`cron.update_mirrors`) +#### Cron - Update Mirrors (`cron.update_mirrors`) - `SCHEDULE`: **@every 10m**: Cron syntax for scheduling update mirrors, e.g. `@every 3h`. +- `NO_SUCCESS_NOTICE`: **true**: The cron task for update mirrors success report is not very useful - as it just means that the mirrors have been queued. Therefore this is turned off by default. -### Cron - Repository Health Check (`cron.repo_health_check`) +#### Cron - Repository Health Check (`cron.repo_health_check`) - `SCHEDULE`: **@every 24h**: Cron syntax for scheduling repository health check. - `TIMEOUT`: **60s**: Time duration syntax for health check execution timeout. - `ARGS`: **\**: Arguments for command `git fsck`, e.g. `--unreachable --tags`. See more on http://git-scm.com/docs/git-fsck -### Cron - Repository Statistics Check (`cron.check_repo_stats`) +#### Cron - Repository Statistics Check (`cron.check_repo_stats`) - `RUN_AT_START`: **true**: Run repository statistics check at start time. - `SCHEDULE`: **@every 24h**: Cron syntax for scheduling repository statistics check. -### Cron - Update Migration Poster ID (`cron.update_migration_poster_id`) +#### Cron - Update Migration Poster ID (`cron.update_migration_poster_id`) + +- `SCHEDULE`: **@every 24h** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts. + +#### Cron - Sync External Users (`cron.sync_external_users`) - `SCHEDULE`: **@every 24h** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts. +- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false. + +### Extended cron tasks (not enabled by default) + +#### Cron - Garbage collect all repositories ('cron.git_gc_repos') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. +- `TIMEOUT`: **60s**: Time duration syntax for garbage collection execution timeout. +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `ARGS`: **\**: Arguments for command `git gc`, e.g. `--aggressive --auto`. The default value is same with [git] -> GC_ARGS + +#### Cron - Update the '.ssh/authorized_keys' file with Gitea SSH keys ('cron.resync_all_sshkeys') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. + +#### Cron - Resynchronize pre-receive, update and post-receive hooks of all repositories ('cron.resync_all_hooks') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. + +#### Cron - Reinitialize all missing Git repositories for which records exist ('cron.reinit_missing_repos') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. + +#### Cron - Delete all repositories missing their Git files ('cron.delete_missing_repos') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. + +#### Cron - Delete generated repository avatars ('cron.delete_generated_repository_avatars') +- `ENABLED`: **false**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices. +- `SCHEDULE`: **@every 72h**: Cron syntax for scheduling repository archive cleanup, e.g. `@every 1h`. ## Git (`git`) - `PATH`: **""**: The path of git executable. If empty, Gitea searches through the PATH environment. +- `DISABLE_DIFF_HIGHLIGHT`: **false**: Disables highlight of added and removed changes. - `MAX_GIT_DIFF_LINES`: **100**: Max number of lines allowed of a single file in diff view. - `MAX_GIT_DIFF_LINE_CHARACTERS`: **5000**: Max character count per line highlighted in diff view. - `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view. @@ -615,8 +784,8 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` - `ENABLE`: **true**: Enables OAuth2 provider. - `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds -- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 access token in hours -- `INVALIDATE_REFRESH_TOKEN`: **false**: Check if refresh token got already used +- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 refresh token in hours +- `INVALIDATE_REFRESH_TOKENS`: **false**: Check if refresh token has already been used - `JWT_SECRET`: **\**: OAuth2 authentication secret for access and refresh tokens, change this a unique string. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider @@ -680,15 +849,70 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `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 redis needs a password, use `addrs=127.0.0.1:6379 password=123 db=0`. +- `QUEUE_CONN_STR`: **redis://127.0.0.1:6379/0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `redis://123@127.0.0.1:6379/0`. ## Migrations (`migrations`) - `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations. - `RETRY_BACKOFF`: **3**: Backoff time per http/https request retry (seconds) +## Mirror (`mirror`) + +- `DEFAULT_INTERVAL`: **8h**: Default interval between each check +- `MIN_INTERVAL`: **10m**: Minimum interval for checking. (Must be >1m). + +## LFS (`lfs`) + +Storage configuration for lfs data. It will be derived from default `[storage]` or +`[storage.xxx]` when set `STORAGE_TYPE` to `xxx`. When derived, the default of `PATH` +is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`. + +- `STORAGE_TYPE`: **local**: Storage type for lfs, `local` for local disk or `minio` for s3 compatible object storage service or other name defined with `[storage.xxx]` +- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. +- `CONTENT_PATH`: **./data/lfs**: Where to store LFS files, only available when `STORAGE_TYPE` is `local`. +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` + +## Storage (`storage`) + +Default storage configuration for attachments, lfs, avatars and etc. + +- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing. +- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio` +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio` +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio` +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio` +- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio` + +And you can also define a customize storage like below: + +```ini +[storage.my_minio] +STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +MINIO_USE_SSL = false +``` + +And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`. + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. -- `SHOW_FOOTER_VERSION`: **true**: Show Gitea version information in the footer. +- `SHOW_FOOTER_VERSION`: **true**: Show Gitea and Go version information in the footer. - `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index dd3735e9c091..505fdcdf718e 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -30,6 +30,7 @@ menu: - `ANSI_CHARSET`: 默认字符编码。 - `FORCE_PRIVATE`: 强制所有git工程必须私有。 - `DEFAULT_PRIVATE`: 默认创建的git工程为私有。 可以是`last`, `private` 或 `public`。默认值是 `last`表示用户最后创建的Repo的选择。 +- `DEFAULT_PUSH_CREATE_PRIVATE`: **true**: 通过 ``push-to-create`` 方式创建的仓库是否默认为私有仓库. - `MAX_CREATION_LIMIT`: 全局最大每个用户创建的git工程数目, `-1` 表示没限制。 - `PULL_REQUEST_QUEUE_LENGTH`: 小心:合并请求测试队列的长度,尽量放大。 @@ -69,8 +70,8 @@ menu: - `STATIC_CACHE_TIME`: **6h**: 静态资源文件,包括 `custom/`, `public/` 和所有上传的头像的浏览器缓存时间。 - `ENABLE_GZIP`: 启用应用级别的 GZIP 压缩。 - `LANDING_PAGE`: 未登录用户的默认页面,可选 `home` 或 `explore`。 + - `LFS_START_SERVER`: 是否启用 git-lfs 支持. 可以为 `true` 或 `false`, 默认是 `false`。 -- `LFS_CONTENT_PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。 - `LFS_JWT_SECRET`: LFS 认证密钥,改成自己的。 ## Database (`database`) @@ -98,8 +99,12 @@ menu: - `ISSUE_INDEXER_QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: 当 `ISSUE_INDEXER_QUEUE_TYPE` 为 `redis` 时,保存Redis队列的连接字符串。 - `ISSUE_INDEXER_QUEUE_BATCH_NUMBER`: **20**: 队列处理中批量提交数量。 -- `REPO_INDEXER_ENABLED`: **false**: 是否启用代码搜索(启用后会占用比较大的磁盘空间)。 +- `REPO_INDEXER_ENABLED`: **false**: 是否启用代码搜索(启用后会占用比较大的磁盘空间,如果是bleve可能需要占用约6倍存储空间)。 +- `REPO_INDEXER_TYPE`: **bleve**: 代码搜索引擎类型,可以为 `bleve` 或者 `elasticsearch`。 - `REPO_INDEXER_PATH`: **indexers/repos.bleve**: 用于代码搜索的索引文件路径。 +- `REPO_INDEXER_CONN_STR`: ****: 代码搜索引擎连接字符串,当 `REPO_INDEXER_TYPE` 为 `elasticsearch` 时有效。例如: http://elastic:changeme@localhost:9200 +- `REPO_INDEXER_NAME`: **gitea_codes**: 代码搜索引擎的名字,当 `REPO_INDEXER_TYPE` 为 `elasticsearch` 时有效。 + - `UPDATE_BUFFER_LEN`: **20**: 代码索引请求的缓冲区长度。 - `MAX_FILE_SIZE`: **1048576**: 进行解析的源代码文件的最大长度,小于该值时才会索引。 @@ -177,21 +182,35 @@ menu: - `DISABLE_GRAVATAR`: 开启则只使用内部头像。 - `ENABLE_FEDERATED_AVATAR`: 启用头像联盟支持 (参见 http://www.libravatar.org) +- `AVATAR_STORAGE_TYPE`: **local**: 头像存储类型,可以为 `local` 或 `minio`,分别支持本地文件系统和 minio 兼容的API。 +- `AVATAR_UPLOAD_PATH`: **data/avatars**: 存储头像的文件系统路径。 +- `AVATAR_MAX_WIDTH`: **4096**: 头像最大宽度,单位像素。 +- `AVATAR_MAX_HEIGHT`: **3072**: 头像最大高度,单位像素。 +- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): 头像最大大小。 + +- `REPOSITORY_AVATAR_STORAGE_TYPE`: **local**: 仓库头像存储类型,可以为 `local` 或 `minio`,分别支持本地文件系统和 minio 兼容的API。 +- `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: 存储仓库头像的路径。 +- `REPOSITORY_AVATAR_FALLBACK`: **none**: 当头像丢失时的处理方式 + - none = 不显示头像 + - random = 显示随机生成的头像 + - image = 显示默认头像,通过 `REPOSITORY_AVATAR_FALLBACK_IMAGE` 设置 +- `REPOSITORY_AVATAR_FALLBACK_IMAGE`: **/img/repo_default.png**: 默认仓库头像 + ## Attachment (`attachment`) - `ENABLED`: 是否允许用户上传附件。 - `ALLOWED_TYPES`: 允许上传的附件类型。比如:`image/jpeg|image/png`,用 `*/*` 表示允许任何类型。 - `MAX_SIZE`: 附件最大限制,单位 MB,比如: `4`。 - `MAX_FILES`: 一次最多上传的附件数量,比如: `5`。 -- `STORE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 -- `PATH`: **data/attachments**: 附件存储路径,仅当 `STORE_TYPE` 为 `local` 时有效。 -- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket,仅当 `STORE_TYPE` 是 `minio` 时有效。 -- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORE_TYPE` 是 `minio` 时有效。 +- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 +- `PATH`: **data/attachments**: 附件存储路径,仅当 `STORAGE_TYPE` 为 `local` 时有效。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 关于 `ALLOWED_TYPES`, 在 (*)unix 系统中可以使用`file -I ` 来快速获得对应的 `MIME type`。 @@ -295,6 +314,55 @@ IS_INPUT_FILE = false - `MAX_ATTEMPTS`: **3**: 在迁移过程中的 http/https 请求重试次数。 - `RETRY_BACKOFF`: **3**: 等待下一次重试的时间,单位秒。 +## LFS (`lfs`) + +LFS 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[storage]` 继承。如果不为 `local` 或者 `minio` 而为 `xxx`, 则从 `[storage.xxx]` 继承。当继承时, `PATH` 默认为 `data/lfs`,`MINIO_BASE_PATH` 默认为 `lfs/`。 + +- `STORAGE_TYPE`: **local**: LFS 的存储类型,`local` 将存储到磁盘,`minio` 将存储到 s3 兼容的对象服务。 +- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。 +- `CONTENT_PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 地址,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_BASE_PATH`: **lfs/**: Minio base path ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio 是否启用 ssl ,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。 + +## Storage (`storage`) + +Attachments, lfs, avatars and etc 的默认存储配置。 + +- `STORAGE_TYPE`: **local**: 附件存储类型,`local` 将存储到本地文件夹, `minio` 将存储到 s3 兼容的对象存储服务中。 +- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。 +- `MINIO_ENDPOINT`: **localhost:9000**: Minio 终端,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID ,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 +- `MINIO_USE_SSL`: **false**: Minio enabled ssl,仅当 `STORAGE_TYPE` 是 `minio` 时有效。 + +你也可以自定义一个存储的名字如下: + +```ini +[storage.my_minio] +STORAGE_TYPE = minio +; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +MINIO_ENDPOINT = localhost:9000 +; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +MINIO_ACCESS_KEY_ID = +; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +MINIO_SECRET_ACCESS_KEY = +; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +MINIO_BUCKET = gitea +; Minio location to create bucket only available when STORAGE_TYPE is `minio` +MINIO_LOCATION = us-east-1 +; Minio enabled ssl only available when STORAGE_TYPE is `minio` +MINIO_USE_SSL = false +``` + +然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。 + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: 为真则在页面底部显示Gitea的字样。 diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index 6bc7be4ad05c..474c7274eb54 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -295,3 +295,15 @@ A full list of supported emoji's is at [emoji list](https://gitea.com/gitea/gite As of version 1.6.0 Gitea has built-in themes. The two built-in themes are, the default theme `gitea`, and a dark theme `arc-green`. To change the look of your Gitea install change the value of `DEFAULT_THEME` in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini` to another one of the available options. As of version 1.8.0 Gitea also has per-user themes. The list of themes a user can choose from can be configured with the `THEMES` value in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini` (defaults to `gitea` and `arc-green`, light and dark respectively) + +## Customizing fonts + +Fonts can be customized using CSS variables: + +```css +:root { + --fonts-proportional: /* custom proportional fonts */ !important; + --fonts-monospace: /* custom monospace fonts */ !important; + --fonts-emoji: /* custom emoji fonts */ !important; +} +``` diff --git a/docs/content/doc/advanced/external-renderers.en-us.md b/docs/content/doc/advanced/external-renderers.en-us.md index db5baf60609d..c91cbce116d7 100644 --- a/docs/content/doc/advanced/external-renderers.en-us.md +++ b/docs/content/doc/advanced/external-renderers.en-us.md @@ -36,12 +36,12 @@ FROM gitea/gitea:{{< version >}} COPY custom/app.ini /data/gitea/conf/app.ini [...] -RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng python-dev py-pip python3-dev py3-pip py3-pyzmq +RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng libffi-dev py-pip python3-dev py3-pip py3-pyzmq # install any other package you need for your external renderers RUN pip3 install --upgrade pip RUN pip3 install -U setuptools -RUN pip3 install jupyter matplotlib docutils +RUN pip3 install jupyter docutils # add above any other python package you may need to install ``` @@ -53,7 +53,7 @@ add one `[markup.XXXXX]` section per external renderer on your custom `app.ini`: [markup.asciidoc] ENABLED = true FILE_EXTENSIONS = .adoc,.asciidoc -RENDER_COMMAND = "asciidoctor -e -a leveloffset=-1 --out-file=- -" +RENDER_COMMAND = "asciidoctor -s -a showtitle --out-file=- -" ; Input is not a standard input but a file IS_INPUT_FILE = false @@ -93,4 +93,4 @@ To define multiple entries, add a unique alphanumeric suffix (e.g., `[markup.san Once your configuration changes have been made, restart Gitea to have changes take effect. **Note**: Prior to Gitea 1.12 there was a single `markup.sanitiser` section with keys that were redefined for multiple rules, however, -there were significant problems with this method of configuration necessitating configuration through multiple sections. \ No newline at end of file +there were significant problems with this method of configuration necessitating configuration through multiple sections. diff --git a/docs/content/doc/advanced/hacking-on-gitea.en-us.md b/docs/content/doc/advanced/hacking-on-gitea.en-us.md index b24260c68a42..1d2702a5e899 100644 --- a/docs/content/doc/advanced/hacking-on-gitea.en-us.md +++ b/docs/content/doc/advanced/hacking-on-gitea.en-us.md @@ -15,13 +15,10 @@ menu: # Hacking on Gitea -## Installing go and setting the GOPATH +## Installing go You should [install go](https://golang.org/doc/install) and set up your go -environment correctly. In particular, it is recommended to set the `$GOPATH` -environment variable and to add the go bin directory or directories -`${GOPATH//://bin:}/bin` to the `$PATH`. See the Go wiki entry for -[GOPATH](https://github.com/golang/go/wiki/GOPATH). +environment correctly. Next, [install Node.js with npm](https://nodejs.org/en/download/) which is required to build the JavaScript and CSS files. The minimum supported Node.js @@ -97,14 +94,10 @@ See `make help` for all available `make` targets. Also see [`.drone.yml`](https: ## Building continuously -Both the `frontend` and `backend` targets can be ran continuously when source files change: +To run and continously rebuild when source files change: ````bash -# in your first terminal -make watch-backend - -# in your second terminal -make watch-frontend +make watch ```` On macOS, watching all backend source files may hit the default open files limit which can be increased via `ulimit -n 12288` for the current shell or in your shell startup file for all future shells. diff --git a/docs/content/doc/advanced/logging-documentation.en-us.md b/docs/content/doc/advanced/logging-documentation.en-us.md index 0b3b018fabe1..7e0f85430b1c 100644 --- a/docs/content/doc/advanced/logging-documentation.en-us.md +++ b/docs/content/doc/advanced/logging-documentation.en-us.md @@ -290,9 +290,48 @@ messages. However, you could perhaps set this logger to work on `FATAL`. * `RECEIVERS`: Email addresses to send to. * `SUBJECT`: **Diagnostic message from Gitea** -## Default Configuration +## Debugging problems -The default empty configuration is equivalent to: +When submitting logs in Gitea issues it is often helpful to submit +merged logs obtained by either by redirecting the console log to a file or +copying and pasting it. To that end it is recommended to set your logging to: + +```ini +[database] +LOG_SQL = false ; SQL logs are rarely helpful unless we specifically ask for them + +... + +[log] +MODE = console +LEVEL = debug ; please set the level to debug when we are debugging a problem +REDIRECT_MACARON_LOG = true +MACARON = console +ROUTER = console +COLORIZE = false ; this can be true if you can strip out the ansi coloring +``` + +Sometimes it will be helpful get some specific `TRACE` level logging retricted +to messages that match a specific `EXPRESSION`. Adjusting the `MODE` in the +`[log]` section to `MODE = console,traceconsole` to add a new logger output +`traceconsole` and then adding its corresponding section would be helpful: + +```ini +[log.traceconsole] ; traceconsole here is just a name +MODE = console ; this is the output that the traceconsole writes to +LEVEL = trace +EXPRESSION = ; putting a string here will restrict this logger to logging only those messages that match this expression +``` + +(It's worth noting that log messages that match the expression at or above debug +level will get logged twice so don't worry about that.) + +`STACKTRACE_LEVEL` should generally be left unconfigured (and hence kept at +`none`). There are only very specific occasions when it useful. + +## Empty Configuration + +The empty configuration is equivalent to: ```ini [log] diff --git a/docs/content/doc/advanced/migrations.en-us.md b/docs/content/doc/advanced/migrations.en-us.md index 2097dbbf66a8..746c68f42672 100644 --- a/docs/content/doc/advanced/migrations.en-us.md +++ b/docs/content/doc/advanced/migrations.en-us.md @@ -17,10 +17,10 @@ menu: The new migration features were introduced in Gitea 1.9.0. It defines two interfaces to support migrating repositories data from other git host platforms to gitea or, in the future migrating gitea data to other -git host platforms. Currently, only the migrations from github via APIv3 to Gitea is implemented. +git host platforms. Currently, migrations from Github, Gitlab and Gitea to Gitea is implemented. First of all, Gitea defines some standard objects in packages `modules/migrations/base`. They are - `Repository`, `Milestone`, `Release`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`. + `Repository`, `Milestone`, `Release`, `ReleaseAsset`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`. ## Downloader Interfaces @@ -33,6 +33,7 @@ create a Downloader. ```Go type Downloader interface { + GetAsset(relTag string, relID, id int64) (io.ReadCloser, error) SetContext(context.Context) GetRepoInfo() (*Repository, error) GetTopics() ([]string, error) @@ -41,15 +42,15 @@ type Downloader interface { GetLabels() ([]*Label, error) GetIssues(page, perPage int) ([]*Issue, bool, error) GetComments(issueNumber int64) ([]*Comment, error) - GetPullRequests(page, perPage int) ([]*PullRequest, error) + GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) GetReviews(pullRequestNumber int64) ([]*Review, error) } ``` ```Go type DownloaderFactory interface { - Match(opts MigrateOptions) (bool, error) - New(opts MigrateOptions) (Downloader, error) + New(ctx context.Context, opts MigrateOptions) (Downloader, error) + GitServiceType() structs.GitServiceType } ``` @@ -66,7 +67,7 @@ type Uploader interface { CreateRepo(repo *Repository, opts MigrateOptions) error CreateTopics(topic ...string) error CreateMilestones(milestones ...*Milestone) error - CreateReleases(releases ...*Release) error + CreateReleases(downloader Downloader, releases ...*Release) error SyncTags() error CreateLabels(labels ...*Label) error CreateIssues(issues ...*Issue) error diff --git a/docs/content/doc/advanced/third-party-tools.en-us.md b/docs/content/doc/advanced/third-party-tools.en-us.md index 5f1b3d899681..9d7e7d6d62c3 100644 --- a/docs/content/doc/advanced/third-party-tools.en-us.md +++ b/docs/content/doc/advanced/third-party-tools.en-us.md @@ -23,7 +23,7 @@ Instead, check out [awesome-gitea](https://gitea.com/gitea/awesome-gitea/src/bra Check our [CI/CD page]({{< relref "doc/advanced/ci-cd.en-us.md" >}}) -### Internationalization +### Internationalization - [Weblate](https://docs.weblate.org/en/latest/admin/continuous.html#gitea-setup) ### Migrating @@ -32,11 +32,12 @@ Check our [CI/CD page]({{< relref "doc/advanced/ci-cd.en-us.md" >}}) ### Mobile -- [GitNex for Android](https://gitlab.com/mmarif4u/gitnex) +- [GitNex for Android](https://codeberg.org/gitnex/GitNex) +- [GitTouch for Android and iOS](https://github.com/git-touch/git-touch) ### Editor Extensions - [Gitea Extension for Visual Studio](https://github.com/maikebing/Gitea.VisualStudio) - Download from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=MysticBoy.GiteaExtensionforVisualStudio) - + ### Project Management - [YouTrack by JetBrains](https://blog.jetbrains.com/youtrack/2019/12/whats-new-in-youtrack-2019-3/) diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index 8e47b0224e0c..2a622a4b39c4 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -79,25 +79,25 @@ _Symbols used in table:_ | Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Multiple assignees for issues | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | -| Related issues | ✘ | ✘ | ⁄ | ✘ | ✓ | ✘ | ✘ | +| Related issues | ✘ | ✘ | ⁄ | [✓](https://docs.gitlab.com/ce/user/project/issues/related_issues.html) | ✓ | ✘ | ✘ | | Confidential issues | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Lock Discussion | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Issue Boards | [✓](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | +| Issue Boards (Kanban) | [✓](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Global issue search | [✘](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | | Create issue via email | [✘](https://github.com/go-gitea/gitea/issues/6226) | [✘](https://github.com/gogs/gogs/issues/2602) | ✘ | ✘ | ✓ | ✓ | ✘ | -| Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | ✘ | ✓ | ✘ | ✘ | +| Service Desk | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | [✓](https://gitlab.com/groups/gitlab-org/-/epics/3103) | ✓ | ✘ | ✘ | #### Pull/Merge requests | Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | |---------|-------|------|-----------|-----------|-----------|-----------|--------------| | Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Squash merging | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | +| Squash merging | ✓ | ✘ | ✓ | [✓](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓ | ✓ | ✓ | | Rebase merging | ✓ | ✓ | ✓ | ✘ | ⁄ | ✘ | ✓ | | Pull/Merge request inline comments | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request approval | ✓ | ✘ | ⁄ | ✓ | ✓ | ✓ | ✓ | diff --git a/docs/content/doc/help/faq.en-us.md b/docs/content/doc/help/faq.en-us.md index 5752dc923303..7a7044f61c87 100644 --- a/docs/content/doc/help/faq.en-us.md +++ b/docs/content/doc/help/faq.en-us.md @@ -47,7 +47,8 @@ Also see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}) * [How can I enable password reset](#how-can-i-enable-password-reset) * [How can a user's password be changed](#how-can-a-user-s-password-be-changed) * [Why is my markdown broken](#why-is-my-markdown-broken) - +* [Errors during upgrade on MySQL: Error 1118: Row size too large.](#upgrade-errors-with-mysql) +* [Why are emoji broken on MySQL](#why-are-emoji-broken-on-mysql) ## Difference between 1.x and 1.x.x downloads Version 1.7.x will be used for this example. @@ -161,7 +162,7 @@ At some point, a customer or third party needs access to a specific repo and onl ### Enable Fail2ban -Use [Fail2Ban]({{ relref "doc/usage/fail2ban-setup.md" >}}) to monitor and stop automated login attempts or other malicious behavior based on log patterns +Use [Fail2Ban]({{< relref "doc/usage/fail2ban-setup.en-us.md" >}}) to monitor and stop automated login attempts or other malicious behavior based on log patterns ## How to add/use custom themes Gitea supports two official themes right now, `gitea` and `arc-green` (`light` and `dark` respectively) @@ -308,3 +309,30 @@ There is no setting for password resets. It is enabled when a [mail service]({{< In Gitea version `1.11` we moved to [goldmark](https://github.com/yuin/goldmark) for markdown rendering, which is [CommonMark](https://commonmark.org/) compliant. If you have markdown that worked as you expected prior to version `1.11` and after upgrading it's not working anymore, please look through the CommonMark spec to see whether the problem is due to a bug or non-compliant syntax. If it is the latter, _usually_ there is a compliant alternative listed in the spec. + +## Upgrade errors with MySQL + +If you are receiving errors on upgrade of Gitea using MySQL that read: + +> `ORM engine initialization failed: migrate: do migrate: Error: 1118: Row size too large...` + +Please run `gitea convert` or run `ALTER TABLE table_name ROW_FORMAT=dynamic;` for each table in the database. + +The underlying problem is that the space allocated for indices by the default row format +is too small. Gitea requires that the `ROWFORMAT` for its tables is `DYNAMIC`. + +If you are receiving an error line containing `Error 1071: Specified key was too long; max key length is 1000 bytes...` +then you are attempting to run Gitea on tables which use the ISAM engine. While this may have worked by chance in previous versions of Gitea, it has never been officially supported and +you must use InnoDB. You should run `ALTER TABLE table_name ENGINE=InnoDB;` for each table in the database. + +## Why Are Emoji Broken On MySQL + +Unfortunately MySQL's `utf8` charset does not completely allow all possible UTF-8 characters, in particular Emoji. +They created a new charset and collation called `utf8mb4` that allows for emoji to be stored but tables which use +the `utf8` charset, and connections which use the `utf8` charset will not use this. + +Please run `gitea convert`, or run `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` +for the database_name and run `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` +for each table in the database. + +You will also need to change the app.ini database charset to `CHARSET=utf8mb4`. diff --git a/docs/content/doc/installation/database-preparation.en-us.md b/docs/content/doc/installation/database-preparation.en-us.md index 674fb66f47f8..3d7a84061cbc 100644 --- a/docs/content/doc/installation/database-preparation.en-us.md +++ b/docs/content/doc/installation/database-preparation.en-us.md @@ -21,7 +21,12 @@ Note: All steps below requires that the database engine of your choice is instal ## MySQL -1. On database instance, login to database console as root: +1. For remote database setup, you will need to make MySQL listen to your IP address. Edit `bind-address` option on `/etc/mysql/my.cnf` on database instance to: + + ```ini + bind-address = 203.0.113.3 + ``` +2. On database instance, login to database console as root: ``` mysql -u root -p @@ -29,7 +34,7 @@ Note: All steps below requires that the database engine of your choice is instal Enter the password as prompted. -2. Create database user which will be used by Gitea, authenticated by password. This example uses `'gitea'` as password. Please use a secure password for your instance. +3. Create database user which will be used by Gitea, authenticated by password. This example uses `'gitea'` as password. Please use a secure password for your instance. For local database: @@ -49,7 +54,7 @@ Note: All steps below requires that the database engine of your choice is instal Replace username and password above as appropriate. -3. Create database with UTF-8 charset and collation. Make sure to use `utf8mb4` charset instead of `utf8` as the former supports all Unicode characters (including emojis) beyond *Basic Multilingual Plane*. Also, collation chosen depending on your expected content. When in doubt, use either `unicode_ci` or `general_ci`. +4. Create database with UTF-8 charset and collation. Make sure to use `utf8mb4` charset instead of `utf8` as the former supports all Unicode characters (including emojis) beyond *Basic Multilingual Plane*. Also, collation chosen depending on your expected content. When in doubt, use either `unicode_ci` or `general_ci`. ```sql CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'; @@ -57,7 +62,7 @@ Note: All steps below requires that the database engine of your choice is instal Replace database name as appropriate. -4. Grant all privileges on the database to database user created above. +5. Grant all privileges on the database to database user created above. For local database: @@ -73,9 +78,9 @@ Note: All steps below requires that the database engine of your choice is instal FLUSH PRIVILEGES; ``` -5. Quit from database console by `exit`. +6. Quit from database console by `exit`. -6. On your Gitea server, test connection to the database: +7. On your Gitea server, test connection to the database: ``` mysql -u gitea -h 203.0.113.3 -p giteadb @@ -87,7 +92,13 @@ Note: All steps below requires that the database engine of your choice is instal ## PostgreSQL -1. PostgreSQL uses `md5` challenge-response encryption scheme for password authentication by default. Nowadays this scheme is not considered secure anymore. Use SCRAM-SHA-256 scheme instead by editing the `postgresql.conf` configuration file on the database server to: +1. For remote database setup, configure PostgreSQL on database instance to listen to your IP address by editing `listen_addresses` on `postgresql.conf` to: + + ```ini + listen_addresses = 'localhost, 203.0.113.3' + ``` + +2. PostgreSQL uses `md5` challenge-response encryption scheme for password authentication by default. Nowadays this scheme is not considered secure anymore. Use SCRAM-SHA-256 scheme instead by editing the `postgresql.conf` configuration file on the database server to: ```ini password_encryption = scram-sha-256 @@ -95,13 +106,13 @@ Note: All steps below requires that the database engine of your choice is instal Restart PostgreSQL to apply the setting. -2. On the database server, login to the database console as superuser: +3. On the database server, login to the database console as superuser: ``` su -c "psql" - postgres ``` -3. Create database user (role in PostgreSQL terms) with login privilege and password. Please use a secure, strong password instead of `'gitea'` below: +4. Create database user (role in PostgreSQL terms) with login privilege and password. Please use a secure, strong password instead of `'gitea'` below: ```sql CREATE ROLE gitea WITH LOGIN PASSWORD 'gitea'; @@ -109,7 +120,7 @@ Note: All steps below requires that the database engine of your choice is instal Replace username and password as appropriate. -4. Create database with UTF-8 charset and owned by the database user created earlier. Any `libc` collations can be specified with `LC_COLLATE` and `LC_CTYPE` parameter, depending on expected content: +5. Create database with UTF-8 charset and owned by the database user created earlier. Any `libc` collations can be specified with `LC_COLLATE` and `LC_CTYPE` parameter, depending on expected content: ```sql CREATE DATABASE giteadb WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8'; @@ -117,7 +128,7 @@ Note: All steps below requires that the database engine of your choice is instal Replace database name as appropriate. -5. Allow the database user to access the database created above by adding the following authentication rule to `pg_hba.conf`. +6. Allow the database user to access the database created above by adding the following authentication rule to `pg_hba.conf`. For local database: @@ -137,7 +148,7 @@ Note: All steps below requires that the database engine of your choice is instal Restart PostgreSQL to apply new authentication rules. -6. On your Gitea server, test connection to the database. +7. On your Gitea server, test connection to the database. For local database: diff --git a/docs/content/doc/installation/from-package.zh-cn.md b/docs/content/doc/installation/from-package.zh-cn.md index c56a54fe0d5e..0557cae97c30 100644 --- a/docs/content/doc/installation/from-package.zh-cn.md +++ b/docs/content/doc/installation/from-package.zh-cn.md @@ -25,7 +25,7 @@ menu: ## macOS -macOS 平台下当前我们仅支持通过 `brew` 来安装。如果您没有安装 [Homebrew](http://brew.sh/),你冶可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令: +macOS 平台下当前我们仅支持通过 `brew` 来安装。如果您没有安装 [Homebrew](http://brew.sh/),你也可以查看 [从二进制安装]({{< relref "from-binary.zh-cn.md" >}})。在你安装了 `brew` 之后, 你可以执行以下命令: ``` brew tap go-gitea/gitea diff --git a/docs/content/doc/installation/with-docker.en-us.md b/docs/content/doc/installation/with-docker.en-us.md index c65e7bc2290a..8773e34872d9 100644 --- a/docs/content/doc/installation/with-docker.en-us.md +++ b/docs/content/doc/installation/with-docker.en-us.md @@ -34,7 +34,7 @@ Also be aware that the tag `:latest` will install the current development versio For a stable release you can use `:1` or specify a certain release like `:{{< version >}}`. ```yaml -version: "2" +version: "3" networks: gitea: @@ -43,6 +43,7 @@ networks: services: server: image: gitea/gitea:latest + container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 @@ -65,7 +66,7 @@ the port section. It's common to just change the host port and keep the ports wi the container like they are. ```diff -version: "2" +version: "3" networks: gitea: @@ -74,6 +75,7 @@ networks: services: server: image: gitea/gitea:latest + container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 @@ -97,7 +99,7 @@ To start Gitea in combination with a MySQL database, apply these changes to the `docker-compose.yml` file created above. ```diff -version: "2" +version: "3" networks: gitea: @@ -106,6 +108,7 @@ networks: services: server: image: gitea/gitea:latest + container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 @@ -147,7 +150,7 @@ To start Gitea in combination with a PostgreSQL database, apply these changes to the `docker-compose.yml` file created above. ```diff -version: "2" +version: "3" networks: gitea: @@ -156,6 +159,7 @@ networks: services: server: image: gitea/gitea:latest + container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 @@ -198,7 +202,7 @@ create the required volume. You don't need to worry about permissions with named volumes; Docker will deal with that automatically. ```diff -version: "2" +version: "3" networks: gitea: @@ -211,6 +215,7 @@ networks: services: server: image: gitea/gitea:latest + container_name: gitea restart: always networks: - gitea @@ -257,7 +262,7 @@ You can configure some of Gitea's settings via environment variables: * `SSH_DOMAIN`: **localhost**: Domain name of this server, used for the displayed ssh clone URL in Gitea's UI. If the install page is enabled, SSH Domain Server takes DOMAIN value in the form (which overwrite this setting on save). * `SSH_PORT`: **22**: SSH port displayed in clone URL. * `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. -* `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. +* `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. If you want to disable SSH feature, you should set SSH port to `0` when installing Gitea. * `HTTP_PORT`: **3000**: HTTP listen port. * `ROOT_URL`: **""**: Overwrite the automatically generated public URL. This is useful if the internal and the external URL don't match (e.g. in Docker). * `LFS_START_SERVER`: **false**: Enables git-lfs support. @@ -306,9 +311,12 @@ UID/GID as the container values `USER_UID`/`USER_GID`. You should also create th `/var/lib/gitea` on the host, owned by the `git` user and mounted in the container, e.g. ``` +version: "3" + services: server: image: gitea/gitea:latest + container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 @@ -341,7 +349,9 @@ Your `git` user needs to have an SSH key generated: sudo -u git ssh-keygen -t rsa -b 4096 -C "Gitea Host Key" ``` -Still on the host, symlink the container `.ssh/authorized_keys` file to your git user `.ssh/authorized_keys`. +Now, proceed with one of the points given below: + +- symlink the container `.ssh/authorized_keys` file to your git user `.ssh/authorized_keys`. This can be done on the host as the `/var/lib/gitea` directory is mounted inside the container under `/data`: ``` @@ -354,6 +364,23 @@ Then echo the `git` user SSH key into the authorized_keys file so the host can t echo "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $(cat /home/git/.ssh/id_rsa.pub)" >> /var/lib/gitea/git/.ssh/authorized_keys ``` +Lastly, Gitea makes `authorized_keys` backups by default. This could be a problem +as the symbolic link made to `authorized_keys` previously could end up pointing +to an old backup. To resolve this, please put the following into your Gitea +config: + +``` +[ssh] +SSH_AUTHORIZED_KEYS_BACKUP=false +``` + +- mount your `.ssh` directory directly into the container i.e. add the + following to the `volumes` section of your Docker container config: + +``` +- /home/git/.ssh/:/data/git/.ssh/ +``` + Now you should be able to use Git over SSH to your container without disrupting SSH access to the host. Please note: SSH container passthrough will work only if using opensshd in container, and will not work if diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index e458b11ba47b..193f2166587e 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -55,28 +55,40 @@ Starts the server: Admin operations: - Commands: - - `create-user` - - Options: - - `--name value`: Username. Required. As of gitea 1.9.0, use the `--username` flag instead. - - `--username value`: Username. Required. New in gitea 1.9.0. - - `--password value`: Password. Required. - - `--email value`: Email. Required. - - `--admin`: If provided, this makes the user an admin. Optional. - - `--access-token`: If provided, an access token will be created for the user. Optional. (default: false). - - `--must-change-password`: If provided, the created user will be required to choose a newer password after + - `user`: + - `list`: + - Options: + - `--admin`: List only admin users. Optional. + - Description: lists all users that exist + - Examples: + - `gitea admin user list` + - `delete`: + - Options: + - `--id`: ID of user to be deleted. Required. + - Examples: + - `gitea admin user delete --id 1` + - `create`: + - Options: + - `--name value`: Username. Required. As of gitea 1.9.0, use the `--username` flag instead. + - `--username value`: Username. Required. New in gitea 1.9.0. + - `--password value`: Password. Required. + - `--email value`: Email. Required. + - `--admin`: If provided, this makes the user an admin. Optional. + - `--access-token`: If provided, an access token will be created for the user. Optional. (default: false). + - `--must-change-password`: If provided, the created user will be required to choose a newer password after the initial login. Optional. (default: true). - - ``--random-password``: If provided, a randomly generated password will be used as the password of + - ``--random-password``: If provided, a randomly generated password will be used as the password of the created user. The value of `--password` will be discarded. Optional. - - `--random-password-length`: If provided, it will be used to configure the length of the randomly + - `--random-password-length`: If provided, it will be used to configure the length of the randomly generated password. Optional. (default: 12) - - Examples: - - `gitea admin create-user --username myname --password asecurepassword --email me@example.com` - - `change-password` - - Options: - - `--username value`, `-u value`: Username. Required. - - `--password value`, `-p value`: New password. Required. - - Examples: - - `gitea admin change-password --username myname --password asecurepassword` + - Examples: + - `gitea admin create-user --username myname --password asecurepassword --email me@example.com` + - `change-password`: + - Options: + - `--username value`, `-u value`: Username. Required. + - `--password value`, `-p value`: New password. Required. + - Examples: + - `gitea admin change-password --username myname --password asecurepassword` - `regenerate` - Options: - `hooks`: Regenerate git-hooks for all repositories @@ -319,6 +331,36 @@ var checklist = []check{ This function will receive a command line context and return a list of details about the problems or error. +##### doctor recreate-table + +Sometimes when there are migrations the old columns and default values may be left +unchanged in the database schema. This may lead to warning such as: + +``` +2020/08/02 11:32:29 ...rm/session_schema.go:360:Sync2() [W] Table user Column keep_activity_private db default is , struct default is 0 +``` + +You can cause Gitea to recreate these tables and copy the old data into the new table +with the defaults set appropriately by using: + +``` +gitea doctor recreate-table user +``` + +You can ask gitea to recreate multiple tables using: + +``` +gitea doctor recreate-table table1 table2 ... +``` + +And if you would like Gitea to recreate all tables simply call: + +``` +gitea doctor recreate-table +``` + +It is highly recommended to back-up your database before running these commands. + #### manager Manage running server operations: diff --git a/docs/content/doc/usage/fail2ban-setup.md b/docs/content/doc/usage/fail2ban-setup.en-us.md similarity index 100% rename from docs/content/doc/usage/fail2ban-setup.md rename to docs/content/doc/usage/fail2ban-setup.en-us.md diff --git a/docs/content/doc/usage/https-support.md b/docs/content/doc/usage/https-support.md index a94372ff1f60..dfafa21c5c80 100644 --- a/docs/content/doc/usage/https-support.md +++ b/docs/content/doc/usage/https-support.md @@ -32,7 +32,7 @@ HTTP_PORT = 3000 CERT_FILE = cert.pem KEY_FILE = key.pem ``` - +Note that if your certificate is signed by a third party certificate authority (i.e. not self-signed), then cert.pem should contain the certificate chain. The server certificate must be the first entry in cert.pem, followed by the intermediaries in order (if any). The root certificate does not have to be included because the connecting client must already have it in order to estalbish the trust relationship. To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server). ### Setting up HTTP redirection diff --git a/docs/content/doc/usage/issue-pull-request-templates.en-us.md b/docs/content/doc/usage/issue-pull-request-templates.en-us.md index a4fc51b81fc2..4f5da04cb6dc 100644 --- a/docs/content/doc/usage/issue-pull-request-templates.en-us.md +++ b/docs/content/doc/usage/issue-pull-request-templates.en-us.md @@ -41,4 +41,39 @@ Possible file names for PR templates: * .github/pull_request_template.md -Additionally, the New Issue page URL can be suffixed with `?body=Issue+Text` and the form will be populated with that string. This string will be used instead of the template if there is one. +Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one. + +# Issue Template Directory + +Alternatively, users can create multiple issue templates inside a special directory and allow users to choose one that more specifically +addresses their problem. + +Possible directory names for issue templates: + +* ISSUE_TEMPLATE +* issue_template +* .gitea/ISSUE_TEMPLATE +* .gitea/issue_template +* .github/ISSUE_TEMPLATE +* .github/issue_template +* .gitlab/ISSUE_TEMPLATE +* .gitlab/issue_template + +Inside the directory can be multiple issue templates with the form + +```markdown +----- +name: "Template Name" +about: "This template is for testing!" +title: "[TEST] " +labels: + - bug + - "help needed" +----- +This is the template! +``` + +In the above example, when a user is presented with the list of issues they can submit, this would show as `Template Name` with the description +`This template is for testing!`. When submitting an issue with the above example, the issue title would be pre-populated with +`[TEST] ` while the issue body would be pre-populated with `This is the template!`. The issue would also be assigned two labels, +`bug` and `help needed`. diff --git a/docs/content/doc/usage/linked-references.en-us.md b/docs/content/doc/usage/linked-references.en-us.md index d2836f857199..bbfd1ef64ea2 100644 --- a/docs/content/doc/usage/linked-references.en-us.md +++ b/docs/content/doc/usage/linked-references.en-us.md @@ -42,7 +42,6 @@ Example: This is also valid for teams and organizations: > [@Documenters](#), we need to plan for this. - > [@CoolCompanyInc](#), this issue concerns us all! Teams will receive mail notifications when appropriate, but whole organizations won't. @@ -123,6 +122,33 @@ The default _keywords_ are: * **Closing**: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved * **Reopening**: reopen, reopens, reopened +## Time tracking in Pull Requests and Commit Messages + +When commit or merging of pull request results in automatic closing of issue +it is possible to also add spent time resolving this issue through commit message. + +To specify spent time on resolving issue you need to specify time in format +`@` after issue number. In one commit message you can specify +multiple fixed issues and spent time for each of them. + +Supported time units (``): + +* `m` - minutes +* `h` - hours +* `d` - days (equals to 8 hours) +* `w` - weeks (equals to 5 days) +* `mo` - months (equals to 4 weeks) + +Numbers to specify time (``) can be also decimal numbers, ex. `@1.5h` would +result in one and half hours. Multiple time units can be combined, ex. `@1h10m` would +mean 1 hour and 10 minutes. + +Example of commit message: + +> Fixed #123 spent @1h, refs #102, fixes #124 @1.5h + +This would result in 1 hour added to issue #123 and 1 and half hours added to issue #124. + ## External Trackers Gitea supports the use of external issue trackers, and references to issues @@ -132,7 +158,6 @@ the pull requests hosted in Gitea. To address this, Gitea allows the use of the `!` marker to identify pull requests. For example: > This is issue [#1234](#), and links to the external tracker. - > This is pull request [!1234](#), and links to a pull request in Gitea. The `!` and `#` can be used interchangeably for issues and pull request _except_ diff --git a/go.mod b/go.mod index b44c040274e9..6b05d34c5867 100644 --- a/go.mod +++ b/go.mod @@ -3,123 +3,130 @@ module code.gitea.io/gitea go 1.14 require ( - cloud.google.com/go v0.45.0 // indirect code.gitea.io/gitea-vet v0.2.1 + code.gitea.io/sdk/gitea v0.13.1 gitea.com/lunny/levelqueue v0.3.0 gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 - gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae - gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 + gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5 + gitea.com/macaron/i18n v0.0.0-20200910171939-7bbf54aa4c76 gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a - gitea.com/macaron/macaron v1.4.0 - gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d + gitea.com/macaron/macaron v1.5.0 + gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 - github.com/BurntSushi/toml v0.3.1 github.com/PuerkitoBio/goquery v1.5.1 - github.com/RoaringBitmap/roaring v0.4.23 // indirect - github.com/alecthomas/chroma v0.8.0 - github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/blevesearch/bleve v1.0.7 + github.com/RoaringBitmap/roaring v0.5.1 // indirect + github.com/alecthomas/chroma v0.8.1 + github.com/andybalholm/brotli v1.0.1 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/blevesearch/bleve v1.0.12 github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 // indirect github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dustin/go-humanize v1.0.0 - github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 + github.com/editorconfig/editorconfig-core-go/v2 v2.3.7 github.com/emirpasic/gods v1.12.0 github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect - github.com/gliderlabs/ssh v0.2.2 + github.com/gliderlabs/ssh v0.3.1 github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect github.com/go-enry/go-enry/v2 v2.5.2 github.com/go-git/go-billy/v5 v5.0.0 - github.com/go-git/go-git/v5 v5.1.0 - github.com/go-openapi/jsonreference v0.19.3 // indirect - github.com/go-redis/redis v6.15.2+incompatible + github.com/go-git/go-git/v5 v5.2.0 + github.com/go-ldap/ldap/v3 v3.2.4 + github.com/go-redis/redis/v7 v7.4.0 github.com/go-sql-driver/mysql v1.5.0 - github.com/go-swagger/go-swagger v0.21.0 + github.com/go-swagger/go-swagger v0.25.0 github.com/go-testfixtures/testfixtures/v3 v3.4.0 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 - github.com/golang/protobuf v1.4.1 // indirect + github.com/golang/snappy v0.0.2 // indirect github.com/google/go-github/v32 v32.1.0 - github.com/google/uuid v1.1.1 + github.com/google/uuid v1.1.2 github.com/gorilla/context v1.1.1 - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect - github.com/huandu/xstrings v1.3.0 + github.com/hashicorp/go-retryablehttp v0.6.7 // indirect + github.com/hashicorp/go-version v1.2.1 + github.com/huandu/xstrings v1.3.2 + github.com/imdario/mergo v0.3.11 // indirect github.com/issue9/assert v1.3.2 // indirect github.com/issue9/identicon v1.0.1 github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d github.com/jmhodges/levigo v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 - github.com/klauspost/compress v1.10.2 + github.com/klauspost/compress v1.11.1 + github.com/klauspost/pgzip v1.2.5 // indirect github.com/lafriks/xormstore v1.3.2 - github.com/lib/pq v1.7.0 + github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 - github.com/mailru/easyjson v0.7.0 // indirect - github.com/markbates/goth v1.61.2 + github.com/markbates/goth v1.65.0 github.com/mattn/go-isatty v0.0.12 - github.com/mattn/go-sqlite3 v1.14.0 - github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-sqlite3 v1.14.4 github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 - github.com/mgechev/revive v1.0.2 - github.com/mholt/archiver/v3 v3.3.0 - github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 - github.com/minio/minio-go/v7 v7.0.4 + github.com/mgechev/revive v1.0.3-0.20200921231451-246eac737dc7 + github.com/mholt/archiver/v3 v3.3.2 + github.com/microcosm-cc/bluemonday v1.0.4 + github.com/minio/minio-go/v7 v7.0.5 github.com/mitchellh/go-homedir v1.1.0 github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc - github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 - github.com/niklasfasching/go-org v0.1.9 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 + github.com/niklasfasching/go-org v1.3.2 github.com/oliamb/cutter v0.2.2 - github.com/olivere/elastic/v7 v7.0.9 + github.com/olivere/elastic/v7 v7.0.20 + github.com/pelletier/go-toml v1.8.1 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.2.0 - github.com/prometheus/client_golang v1.1.0 - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect - github.com/prometheus/procfs v0.0.4 // indirect + github.com/prometheus/client_golang v1.8.0 github.com/quasoft/websspi v1.0.0 github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect github.com/sergi/go-diff v1.1.0 - github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b // indirect - github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd - github.com/stretchr/testify v1.5.1 + github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 + github.com/stretchr/testify v1.6.1 + github.com/syndtr/goleveldb v1.0.0 github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect github.com/tinylib/msgp v1.1.2 // indirect github.com/tstranex/u2f v1.0.0 + github.com/ulikunitz/xz v0.5.8 // indirect github.com/unknwon/com v1.0.1 - github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 + github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 - github.com/urfave/cli v1.20.0 - github.com/xanzy/go-gitlab v0.31.0 + github.com/urfave/cli v1.22.4 + github.com/willf/bitset v1.1.11 // indirect + github.com/xanzy/go-gitlab v0.38.1 github.com/yohcop/openid-go v1.0.0 github.com/yuin/goldmark v1.2.1 github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 - golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de - golang.org/x/net v0.0.0-20200707034311-ab3426394381 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae + go.jolheiser.com/hcaptcha v0.0.4 + go.jolheiser.com/pwn v0.0.3 + golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee + golang.org/x/net v0.0.0-20201010224723-4f7140c49acb + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 golang.org/x/text v0.3.3 - golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect - golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d - google.golang.org/appengine v1.6.5 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect + golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f + google.golang.org/appengine v1.6.7 // indirect 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.57.0 - gopkg.in/ldap.v3 v3.0.2 + gopkg.in/ini.v1 v1.62.0 gopkg.in/yaml.v2 v2.3.0 - mvdan.cc/xurls/v2 v2.1.0 + mvdan.cc/xurls/v2 v2.2.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.7 - xorm.io/xorm v1.0.4-0.20200718080127-318102c9ff87 + xorm.io/xorm v1.0.5 ) + +replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4 diff --git a/go.sum b/go.sum index 7d4ca0ffb557..abe593297b6a 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,46 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.0 h1:bALuGBSgE+BD4rxsopAYlqjcwqcQtye6pWG4bC3N/k0= -cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak= +cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s= code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= +code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk= +code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I= gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ= @@ -23,32 +53,38 @@ gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 h1:e2rAFDejB0qN8OrY4xP gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A= gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 h1:88c34YM29a1GlWLrLBaG/GTT2htDdJz1u3n9+lmPolg= gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439/go.mod h1:IsQPHx73HnnqFBYiVHjg87q4XBZyGXXu77xANukvZuk= -gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae h1:OXxYwGmGNfYrC0/sUUL9KSvr2Sfvmzwgd2YD65vIjGE= -gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae/go.mod h1:jGHtoovArcQj+sw7NJxyPgjuRxOSG9a/oFu3VkLRTKQ= -gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 h1:iZWwQif/LHMjBgfY/ua8CFVa4XMDfbbs7EZ0Q1dYguU= -gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223/go.mod h1:+qsc10s4hBsHKU/9luGGumFh4m5FFVc7uih+8/mM1NY= +gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5 h1:6rbhThlqfOb+sSmhrsVFz3bZoAeoloe7TZqyeiPbbWI= +gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5/go.mod h1:z8vCjuhqDfvzPUJDowGqbsgoeYBvDbl95S5k6y43Pxo= +gitea.com/macaron/i18n v0.0.0-20200910171939-7bbf54aa4c76 h1:r+z4ExFB3GHAXaGfWz+TMGs5q/RuOzDsTCGiXiAk5AY= +gitea.com/macaron/i18n v0.0.0-20200910171939-7bbf54aa4c76/go.mod h1:g5ope1b+iWhBdHzAn6EJ9u9Gp3FRESxpG+CDf7HYc/A= gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM= gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok= gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM= 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/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ= +gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY= gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA= -gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d h1:XLww3CvnFZkXVwauN67fniDaIpIqsE+9KVcxlZKlvLU= -gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY= +gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e h1:BHoJ/xWNt6FrVsL54JennM9HPIQlnbmRvmaC5DO65pU= +gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY= gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk= gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +github.com/6543/go-version v1.2.4 h1:MPsSnqNrM0HwA9tnmWNnsMdQMg4/u4fflARjwomoof4= +github.com/6543/go-version v1.2.4/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -56,21 +92,23 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v0.4.21 h1:WJ/zIlNX4wQZ9x8Ey33O1UaD9TCTakYsdLFSBcTwH+8= -github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= +github.com/RoaringBitmap/roaring v0.5.1 h1:ugdwntNygzk1FZnmtxUr+jM9AYrpU3I3zpt49npDWVo= +github.com/RoaringBitmap/roaring v0.5.1/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s= -github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY= -github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= +github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= @@ -80,21 +118,37 @@ github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MR github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c= -github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.25.25/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.34.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -103,8 +157,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/blevesearch/bleve v1.0.7 h1:4PspZE7XABMSKcVpzAKp0E05Yer1PIYmTWk+1ngNr/c= -github.com/blevesearch/bleve v1.0.7/go.mod h1:3xvmBtaw12Y4C9iA1RTzwWCof5j5HjydjCTiDE2TeE0= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blevesearch/bleve v1.0.12 h1:2qJUSBpU/h1z8x3ERRB5WwpmEpJwoivPqmDpHzv4tuk= +github.com/blevesearch/bleve v1.0.12/go.mod h1:G0ErXWdIrUSYZLPoMpS9Z3saTnTsk4ebhPsVv/+0nxk= github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040 h1:SjYVcfJVZoCfBlg+fkaq2eoZHTf5HaJfaTeTkOtyfHQ= github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= @@ -115,45 +170,65 @@ github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= -github.com/blevesearch/zap/v11 v11.0.7 h1:nnmAOP6eXBkqEa1Srq1eqA5Wmn4w+BZjLdjynNxvd+M= -github.com/blevesearch/zap/v11 v11.0.7/go.mod h1:bJoY56fdU2m/IP4LLz/1h4jY2thBoREvoqbuJ8zhm9k= -github.com/blevesearch/zap/v12 v12.0.7 h1:y8FWSAYkdc4p1dn4YLxNNr1dxXlSUsakJh2Fc/r6cj4= -github.com/blevesearch/zap/v12 v12.0.7/go.mod h1:70DNK4ZN4tb42LubeDbfpp6xnm8g3ROYVvvZ6pEoXD8= +github.com/blevesearch/zap/v11 v11.0.12 h1:ZA+80yajko2tXr1kmbSoVRMCo0mFZAVJmoijjYsZuwc= +github.com/blevesearch/zap/v11 v11.0.12/go.mod h1:JLfFhc8DWP01zMG/6VwEY2eAnlJsTN1vDE4S0rC5Y78= +github.com/blevesearch/zap/v12 v12.0.12 h1:9eWaL9/2hcjy1VR3lrl/b+kWh5G7w/BkNYI07mWActw= +github.com/blevesearch/zap/v12 v12.0.12/go.mod h1:1HrB4hhPfI8u8x4SPYbluhb8xhflpPvvj8EcWImNnJY= +github.com/blevesearch/zap/v13 v13.0.4 h1:eoRvJmLeIQUs1mAF+fAFALg1dPHOI1e1KFuXL0I7us4= +github.com/blevesearch/zap/v13 v13.0.4/go.mod h1:YdB7UuG7TBWu/1dz9e2SaLp1RKfFfdJx+ulIK5HR1bA= +github.com/blevesearch/zap/v14 v14.0.3 h1:ccEv296u6DEUHFF9U4W2E/6/WkbuDrS9/1VJM34SCzA= +github.com/blevesearch/zap/v14 v14.0.3/go.mod h1:oObAhcDHw7p1ahiTCqhRkdxdl7UA8qpvX10pSgrTMHc= +github.com/blevesearch/zap/v15 v15.0.1 h1:jEism63eY+qdcvwXH0K8MiKhv5tb10T1k7SNx6fauCM= +github.com/blevesearch/zap/v15 v15.0.1/go.mod h1:ho0frqAex2ktT9cYFAxQpoQXsxb/KEfdjpx4s49rf/M= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= -github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= -github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d h1:XMf4E1U+b9E3ElF0mjvfXZdflBRZz4gLp16nQ/QSHQM= github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 h1:vZryARwW4PSFXd9arwegEywvMTvPuXL3/oa+4L5NTe8= github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= -github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b h1:bZ9rKU2/V8sY+NulSfxDOnXTWcs1rySqdF1sVepihvo= github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85 h1:0WMIDtuXCKEm4wtAJgAAXa/qtM5O9MariLwgHaRlYmk= github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= -github.com/couchbase/vellum v1.0.1 h1:qrj9ohvZedvc51S5KzPfJ6P6z0Vqzv7Lx7k3mVc2WOk= -github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= +github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw= +github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8= github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= @@ -164,13 +239,14 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 h1:MZRmHqDBd0vxNwenEbKSQqRVT24d3C05ft8kduSwlqM= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0= github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 h1:bpWCJ5MddHsv4Xtl3azkK89mZzd/vvut32mvAnKbyUA= github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= @@ -179,24 +255,30 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 h1:mhPg/0hGebcpiiQLqJD2PWWyoHRLEdZ3sXKaEvT1EQU= -github.com/editorconfig/editorconfig-core-go/v2 v2.1.1/go.mod h1:/LuhWJiQ9Gvo1DhVpa4ssm5qeg8rrztdtI7j/iCie2k= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/editorconfig/editorconfig-core-go/v2 v2.3.7 h1:e88U5ztaklGv7X0gHIgR/cCJmHkLyVAS8aOIoEPKJP0= +github.com/editorconfig/editorconfig-core-go/v2 v2.3.7/go.mod h1:NifC+uYhAGV/U2AxhZa3bNy4EdFMHz9mVU02vbGSMWQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a h1:M1bRpaZAn4GSsqu3hdK2R8H0AH9O6vqCTCbm2oAFGfE= github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a/go.mod h1:MkKY/CB98aVE4VxO63X5vTQKUgcn+3XP15LMASe3lYs= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= @@ -205,6 +287,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= @@ -213,11 +296,19 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.4.0/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= +github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.1 h1:L6VrMUGZaMlNIMN8Hj+CHh4U9yodJE3FAt/rgvfaKvE= +github.com/gliderlabs/ssh v0.3.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -225,6 +316,9 @@ github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqo github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo= github.com/go-enry/go-enry/v2 v2.5.2 h1:3f3PFAO6JitWkPi1GQ5/m6Xu4gNL1U5soJ8QaYqJ0YQ= github.com/go-enry/go-enry/v2 v2.5.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= @@ -233,25 +327,36 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= -github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= -github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= -github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= +github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-ldap/ldap/v3 v3.2.4 h1:PFavAq2xTgzo/loE8qNXcQaofAaqIpI4WgaLdv+1l3E= +github.com/go-ldap/ldap/v3 v3.2.4/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6 h1:xZMThgv5SQ7SMbWtKFkCf9bBdvR2iEyw9k3zGZONuys= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= @@ -262,59 +367,107 @@ github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3 h1:jwIoahqCmaA5OBoc/B+1+Mu2L0Gr8xYQnbeyQEo/7b0= github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.5 h1:h4Zk7oTfB3ZYM2oMNliQvL+3BrDstTIX8lqP7yaYCuI= -github.com/go-openapi/runtime v0.19.5/go.mod h1:WIH6IYPXOrtgTClTV8xzdrD20jBlrK25D0aQbdSlqp8= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.20 h1:J/t+QIjbcoq8WJvjGxRKiFBhqUE8slS9SbmD0Oi/raQ= +github.com/go-openapi/runtime v0.19.20/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww/kCg4= github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNPlDK+Yk= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-swagger/go-swagger v0.21.0 h1:AX9mdfzp6eJtUe92nFrWmbK7ocRgkCDPJs0FsSSTDlA= -github.com/go-swagger/go-swagger v0.21.0/go.mod h1:tDb8PdDVFcaE8EPXkMOsuxpL3UEPiwu1UDZar9Z/1RY= +github.com/go-swagger/go-swagger v0.25.0 h1:FxhyrWWV8V/A9P6GtI5szWordAdbb6Y0nqdY/y9So2w= +github.com/go-swagger/go-swagger v0.25.0/go.mod h1:9639ioXrPX9E6BbnbaDklGXjNz7upAXoNBwL4Ok11Vk= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-testfixtures/testfixtures/v3 v3.4.0 h1:cny44xqH4ctXRld/COxFGPC7XDyOU8KNnwmfCxEEqoQ= github.com/go-testfixtures/testfixtures/v3 v3.4.0/go.mod h1:P4L3WxgOsCLbAeUC50qX5rdj1ULZfUMqgCbqah3OH5U= -github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -324,47 +477,90 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw= -github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.31.0/go.mod h1:aMQuNCA+NDU5+4jLL5pEuFHoue0IznKE2+/GsFvvs8A= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -378,7 +574,6 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -389,28 +584,55 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= +github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo= -github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/issue9/assert v1.3.1/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio= github.com/issue9/assert v1.3.2 h1:IaTa37u4m1fUuTH9K9ldO5IONKVDXjLiUO1T9vj0OF0= github.com/issue9/assert v1.3.2/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio= @@ -464,21 +686,34 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 h1:vE7J1m7cCpiRVEIr1B5ccDxRpbPsWT5JU3if2Di5nE4= github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= @@ -486,31 +721,39 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4= github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.2 h1:Znfn6hXZAHaLPNnlqUYRrBSReFHYybslgv4PTiyz6P0= -github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= +github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= -github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM= -github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyoh86/exportloopref v0.1.7/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8= github.com/lafriks/xormstore v1.3.2 h1:hqi3F8s/B4rz8GuEZZDuHuOxRjeuOpEI/cC7vcnWwH4= github.com/lafriks/xormstore v1.3.2/go.mod h1:mVNIwIa25QIr8rfR7YlVjrqN/apswHkVdtLCyVYBzXw= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= @@ -518,14 +761,19 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc h1:ERSU1OvZ6MdWhHieo2oT7xwR/HCksqKdgK6iYPU5pHI= +github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ= github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk= github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ= github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ= github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -533,61 +781,81 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= -github.com/markbates/goth v1.61.2 h1:jDowrUH5qw8KGuQdKwFhLzkXkTYCIPfz3LHADJsiPIs= -github.com/markbates/goth v1.61.2/go.mod h1:qh2QfwZoWRucQ+DR5KVKC6dUGkNCToWh4vS45GIzFsY= +github.com/markbates/goth v1.65.0 h1:IbXpMneUhqbxgJ8JP1Ghl8ghlAaVX66jWDAapU1KxqU= +github.com/markbates/goth v1.65.0/go.mod h1:65frybxoeSCfORin51KOKqAKbIh7wREIDvdCkdWj//4= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk= -github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 h1:QASJXOGm2RZ5Ardbc86qNFvby9AqkLDibfChMtAg5QM= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.0.2 h1:v0NxxQ7fSFz/u1NQydPo6EGdq7va0J1BtsZmae6kzUg= -github.com/mgechev/revive v1.0.2/go.mod h1:rb0dQy1LVAxW9SWy5R3LPUjevzUbUS316U5MFySA2lo= -github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= -github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= -github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 h1:hJde9rA24hlTcAYSwJoXpDUyGtfKQ/jsofw+WaDqGrI= -github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= +github.com/mgechev/revive v1.0.3-0.20200921231451-246eac737dc7 h1:ydVkpU/M4/c45yT3e5lzMeguKJm9GxGgsawx4/XlwK0= +github.com/mgechev/revive v1.0.3-0.20200921231451-246eac737dc7/go.mod h1:no/hfevHbndpXR5CaJahkYCfM/FFpmM/dSOwFGU7Z1o= +github.com/mholt/archiver/v3 v3.3.2 h1:L72ZVRKdmAWDB+Zl8isK+cb0bfaCa2JQlKCvEXUG1WQ= +github.com/mholt/archiver/v3 v3.3.2/go.mod h1:wZCaCDpKnb7vsqOlgW3WO756DciCRSCOZCVMkXkrxfs= +github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.4 h1:M9glnGclD87VfttesWzURw7SHqq1XDIYGrfTykBTI50= -github.com/minio/minio-go/v7 v7.0.4/go.mod h1:CSt2ETZNs+bIIhWTse0mcZKZWMGrFU7Er7RR0TmkDYk= +github.com/minio/minio-go/v7 v7.0.5 h1:I2NIJ2ojwJqD/YByemC1M59e1b4FW9kS7NlOar7HPV4= +github.com/minio/minio-go/v7 v7.0.5/go.mod h1:TA0CQCjJZHM5SJj9IjqR0NmpmQJ6bCbXifAJ3mUU6Hw= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -596,45 +864,87 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc h1:z1PgdCCmYYVL0BoJTUgmAq1p7ca8fzYIPsNyfsN3xAU= github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= -github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI= -github.com/niklasfasching/go-org v0.1.9/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= +github.com/niklasfasching/go-org v1.3.2 h1:ZKTSd+GdJYkoZl1pBXLR/k7DRiRXnmB96TRiHmHdzwI= +github.com/niklasfasching/go-org v1.3.2/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= +github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= -github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k= github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU= -github.com/olivere/elastic/v7 v7.0.9 h1:+bTR1xJbfLYD8WnTBt9672mFlKxjfWRJpEQ1y8BMS3g= -github.com/olivere/elastic/v7 v7.0.9/go.mod h1:2TeRd0vhLRTK9zqm5xP0uLiVeZ5yUoL7kZ+8SZA9r9Y= +github.com/olivere/elastic/v7 v7.0.20 h1:5FFpGPVJlBSlWBOdict406Y3yNTIpVpAiUvdFZeSbAo= +github.com/olivere/elastic/v7 v7.0.20/go.mod h1:Kh7iIsXIBl5qRQOBFoylCsXVTtye3keQU2Y/YbR7HD8= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/cmdflag v0.0.2/go.mod h1:a3zKGZ3cdQUfxjd0RGMLZr8xI3nvpJOB+m6o/1X5BmU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v3 v3.3.2 h1:QTUOCbMNDbK4PYtkuHyOBd28C0UhPBw3T4OH4WpFDik= +github.com/pierrec/lz4/v3 v3.3.2/go.mod h1:280XNCGS8jAcG++AHdd6SeWnzyJ1w9oow2vbORyey8Q= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= @@ -642,27 +952,37 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78= -github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasoft/websspi v1.0.0 h1:5nDgdM5xSur9s+B5w2xQ5kxf5nUGqgFgU4W0aDLZ8Mw= github.com/quasoft/websspi v1.0.0/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -670,27 +990,47 @@ github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqn github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 h1:YDeskXpkNDhPdWN3REluVa46HQOVuVkjkd2sWnrABNQ= github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec/v2 v2.4.0/go.mod h1:0/Q4cjmlFDfDUj1+Fib61sc+U5IQb2w+Iv9/C3wPVko= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= -github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -698,49 +1038,73 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= +github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.6.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= +github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM= github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 h1:HOxvxvnntLiPn123Fk+twfUhCQdMDaqmb0cclArW0T0= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= @@ -749,23 +1113,36 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= 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/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0YYjZvDMJPYN7W9FGSGNoLmKxM= +github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:Z79lyIznnziKADUf0J7EP8Z4ZL7YJDiPuaazlfUBSy4= github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xanzy/go-gitlab v0.31.0 h1:+nHztQuCXGSMluKe5Q9IRaPdz6tO8O0gMkQ0vqGpiBk= -github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/go-gitlab v0.38.1/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -775,6 +1152,7 @@ github.com/yohcop/openid-go v1.0.0/go.mod h1:/408xiwkeItSPJZSTPF7+VtZxPkPrRRpRNK github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -783,55 +1161,93 @@ github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.jolheiser.com/hcaptcha v0.0.4 h1:RrDERcr/Tz/kWyJenjVtI+V09RtLinXxlAemiwN5F+I= +go.jolheiser.com/hcaptcha v0.0.4/go.mod h1:aw32WQOxnQZ6E06C0LypCf+sxNxPACyOnq+ZGnrIYho= +go.jolheiser.com/pwn v0.0.3 h1:MQowb3QvCL5r5NmHmCPxw93SdjfgJ0q6rAwYn4i1Hjg= +go.jolheiser.com/pwn v0.0.3/go.mod h1:/j5Dl8ftNqqJ8Dlx3YTrJV1wIR2lWOTyrNU3Qe7rk6I= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.5 h1:S0ZOruh4YGHjD7JoN7mIsTrNjnQbOjrmgrx6l6pZN7I= +go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -840,8 +1256,10 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -855,33 +1273,53 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -895,73 +1333,170 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190907184412-d223b2b6db03/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d h1:XZxUC4/ZNKTjrT4/Oc9gCgIYnzPW3/CefdPjsndrVWM= -golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4 h1:v8Jgq9X6Es9K9otVr9jxENEJigepKMZgA9OmrIZDtFA= +golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f h1:18s2P7JILnVhIF2+ZtGJQ9czV5bvTsb13/UGtNPDbjA= +golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -969,82 +1504,136 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= -gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 h1:nn6Zav2sOQHCFJHEspya8KqxhFwKci30UxHy3HXPTyQ= -gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -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.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/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/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.60.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -mvdan.cc/xurls/v2 v2.1.0 h1:KaMb5GLhlcSX+e+qhbRJODnUUBvlw01jt4yrjFIHAuA= -mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= -xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= -xorm.io/xorm v1.0.4-0.20200718080127-318102c9ff87 h1:vgc2F0wjD0cyrNrSKiIdWu123wuKkPQI84DZUKvJ6ns= -xorm.io/xorm v1.0.4-0.20200718080127-318102c9ff87/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= +xorm.io/xorm v1.0.5/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go index eda8f076c3e0..9ff9d71493fa 100644 --- a/integrations/api_admin_test.go +++ b/integrations/api_admin_test.go @@ -24,7 +24,7 @@ func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { token := getTokenForLoggedInUser(t, session) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", "title": "test-key", }) resp := session.MakeRequest(t, req, http.StatusCreated) @@ -64,7 +64,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { token := getTokenForLoggedInUser(t, session) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", adminUsername, token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", "title": "test-key", }) resp := session.MakeRequest(t, req, http.StatusCreated) diff --git a/integrations/api_comment_test.go b/integrations/api_comment_test.go index 2c754272e568..f64f4a1c9c0f 100644 --- a/integrations/api_comment_test.go +++ b/integrations/api_comment_test.go @@ -11,6 +11,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -125,7 +126,7 @@ func TestAPIGetComment(t *testing.T) { DecodeJSON(t, resp, &apiComment) assert.NoError(t, comment.LoadPoster()) - expect := comment.APIFormat() + expect := convert.ToComment(comment) assert.Equal(t, expect.ID, apiComment.ID) assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go index 1d8143de7099..fd80858b12e4 100644 --- a/integrations/api_helper_for_declarative_test.go +++ b/integrations/api_helper_for_declarative_test.go @@ -49,6 +49,7 @@ func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*tes Description: "Temporary repo", Name: ctx.Reponame, Private: true, + Template: true, Gitignores: "", License: "WTFPL", Readme: "Default", diff --git a/integrations/api_issue_milestone_test.go b/integrations/api_issue_milestone_test.go index 4cc574b9ebc3..b9942d9a3c7b 100644 --- a/integrations/api_issue_milestone_test.go +++ b/integrations/api_issue_milestone_test.go @@ -61,6 +61,11 @@ func TestAPIIssuesMilestone(t *testing.T) { DecodeJSON(t, resp, &apiMilestones) assert.Len(t, apiMilestones, 4) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%s?token=%s", owner.Name, repo.Name, apiMilestones[2].Title, token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestone) + assert.EqualValues(t, apiMilestones[2], apiMilestone) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s&token=%s", owner.Name, repo.Name, "all", "milestone2", token)) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestones) diff --git a/integrations/api_issue_reaction_test.go b/integrations/api_issue_reaction_test.go index abbc6429fb49..20b83db2aab6 100644 --- a/integrations/api_issue_reaction_test.go +++ b/integrations/api_issue_reaction_test.go @@ -11,25 +11,12 @@ import ( "time" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) -func TestAPIAllowedReactions(t *testing.T) { - defer prepareTestEnv(t)() - - a := new(api.GeneralUISettings) - - req := NewRequest(t, "GET", "/api/v1/settings/ui") - resp := MakeRequest(t, req, http.StatusOK) - - DecodeJSON(t, resp, &a) - assert.Len(t, a.AllowedReactions, len(setting.UI.Reactions)) - assert.ElementsMatch(t, setting.UI.Reactions, a.AllowedReactions) -} - func TestAPIIssuesReactions(t *testing.T) { defer prepareTestEnv(t)() @@ -74,7 +61,7 @@ func TestAPIIssuesReactions(t *testing.T) { DecodeJSON(t, resp, &apiReactions) expectResponse := make(map[int]api.Reaction) expectResponse[0] = api.Reaction{ - User: user2.APIFormat(), + User: convert.ToUser(user2, true, true), Reaction: "eyes", Created: time.Unix(1573248003, 0), } @@ -134,12 +121,12 @@ func TestAPICommentReactions(t *testing.T) { DecodeJSON(t, resp, &apiReactions) expectResponse := make(map[int]api.Reaction) expectResponse[0] = api.Reaction{ - User: user2.APIFormat(), + User: convert.ToUser(user2, true, true), Reaction: "laugh", Created: time.Unix(1573248004, 0), } expectResponse[1] = api.Reaction{ - User: user1.APIFormat(), + User: convert.ToUser(user1, true, true), Reaction: "laugh", Created: time.Unix(1573248005, 0), } diff --git a/integrations/api_issue_stopwatch_test.go b/integrations/api_issue_stopwatch_test.go index e0fe00c41526..39b9b97411de 100644 --- a/integrations/api_issue_stopwatch_test.go +++ b/integrations/api_issue_stopwatch_test.go @@ -7,6 +7,7 @@ package integrations import ( "net/http" "testing" + "time" "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" @@ -26,12 +27,19 @@ func TestAPIListStopWatches(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) var apiWatches []*api.StopWatch DecodeJSON(t, resp, &apiWatches) - expect := models.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) - expectAPI, _ := expect.APIFormat() - assert.Len(t, apiWatches, 1) - - assert.EqualValues(t, expectAPI.IssueIndex, apiWatches[0].IssueIndex) - assert.EqualValues(t, expectAPI.Created.Unix(), apiWatches[0].Created.Unix()) + stopwatch := models.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) + issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue) + if assert.Len(t, apiWatches, 1) { + assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + apiWatches[0].Created = time.Time{} + assert.EqualValues(t, api.StopWatch{ + Created: time.Time{}, + IssueIndex: issue.Index, + IssueTitle: issue.Title, + RepoName: repo.Name, + RepoOwnerName: repo.OwnerName, + }, *apiWatches[0]) + } } func TestAPIStopStopWatches(t *testing.T) { diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index d742049335c9..9311d50c5cbf 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -153,7 +153,7 @@ func TestAPISearchIssues(t *testing.T) { var apiIssues []*api.Issue DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 9) + assert.Len(t, apiIssues, 10) query := url.Values{} query.Add("token", token) @@ -161,7 +161,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 9) + assert.Len(t, apiIssues, 10) query.Add("state", "closed") link.RawQuery = query.Encode() @@ -182,7 +182,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 1) + assert.Len(t, apiIssues, 2) } func TestAPISearchIssuesWithLabels(t *testing.T) { @@ -197,7 +197,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { var apiIssues []*api.Issue DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 9) + assert.Len(t, apiIssues, 10) query := url.Values{} query.Add("token", token) @@ -205,7 +205,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.Len(t, apiIssues, 9) + assert.Len(t, apiIssues, 10) query.Add("labels", "label1") link.RawQuery = query.Encode() diff --git a/integrations/api_keys_test.go b/integrations/api_keys_test.go index 6979480543b2..50e1e7a35bcd 100644 --- a/integrations/api_keys_test.go +++ b/integrations/api_keys_test.go @@ -53,7 +53,7 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) rawKeyBody := api.CreateKeyOption{ Title: "read-only", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", ReadOnly: true, } req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) @@ -79,7 +79,7 @@ func TestCreateReadWriteDeployKey(t *testing.T) { keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) rawKeyBody := api.CreateKeyOption{ Title: "read-write", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsufOCrDDlT8DLkodnnJtbq7uGflcPae7euTfM+Laq4So+v4WeSV362Rg0O/+Sje1UthrhN6lQkfRkdWIlCRQEXg+LMqr6RhvDfZquE2Xwqv/itlz7LjbdAUdYoO1iH7rMSmYvQh4WEnC/DAacKGbhdGIM/ZBz0z6tHm7bPgbI9ykEKekTmPwQFP1Qebvf5NYOFMWqQ2sCEAI9dBMVLoojsIpV+KADf+BotiIi8yNfTG2rzmzpxBpW9fYjd1Sy1yd4NSUpoPbEJJYJ1TrjiSWlYOVq9Ar8xW1O87i6gBjL/3zN7ANeoYhaAXupdOS6YL22YOK/yC0tJtXwwdh/eSrh", + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", } req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody) resp := session.MakeRequest(t, req, http.StatusCreated) @@ -102,7 +102,7 @@ func TestCreateUserKey(t *testing.T) { token := url.QueryEscape(getTokenForLoggedInUser(t, session)) keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token) keyType := "ssh-rsa" - keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCyTiPTeHJl6Gs5D1FyHT0qTWpVkAy9+LIKjctQXklrePTvUNVrSpt4r2exFYXNMPeA8V0zCrc3Kzs1SZw3jWkG3i53te9onCp85DqyatxOD2pyZ30/gPn1ZUg40WowlFM8gsUFMZqaH7ax6d8nsBKW7N/cRyqesiOQEV9up3tnKjIB8XMTVvC5X4rBWgywz7AFxSv8mmaTHnUgVW4LgMPwnTWo0pxtiIWbeMLyrEE4hIM74gSwp6CRQYo6xnG3fn4yWkcK2X2mT9adQ241IDdwpENJHcry/T6AJ8dNXduEZ67egnk+rVlQ2HM4LpymAv9DAAFFeaQK0hT+3aMDoumV" + keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=" rawKeyBody := api.CreateKeyOption{ Title: "test-key", Key: keyType + " " + keyContent, diff --git a/integrations/api_pull_review_test.go b/integrations/api_pull_review_test.go index 611b34712cae..261a3a8bfa98 100644 --- a/integrations/api_pull_review_test.go +++ b/integrations/api_pull_review_test.go @@ -43,7 +43,7 @@ func TestAPIPullReview(t *testing.T) { assert.EqualValues(t, 10, reviews[5].ID) assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State) assert.EqualValues(t, 1, reviews[5].CodeCommentsCount) - assert.EqualValues(t, 0, reviews[5].Reviewer.ID) // ghost user + assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user assert.EqualValues(t, false, reviews[5].Stale) assert.EqualValues(t, true, reviews[5].Official) @@ -122,4 +122,110 @@ func TestAPIPullReview(t *testing.T) { assert.EqualValues(t, 0, review.CodeCommentsCount) req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token) resp = session.MakeRequest(t, req, http.StatusNoContent) + + // test get review requests + // to make it simple, use same api with get review + pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) + assert.NoError(t, pullIssue12.LoadAttributes()) + repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) + + req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &reviews) + assert.EqualValues(t, 11, reviews[0].ID) + assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State) + assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.EqualValues(t, false, reviews[0].Stale) + assert.EqualValues(t, true, reviews[0].Official) + assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name) + + assert.EqualValues(t, 12, reviews[1].ID) + assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State) + assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.EqualValues(t, false, reviews[1].Stale) + assert.EqualValues(t, true, reviews[1].Official) + assert.EqualValues(t, 1, reviews[1].Reviewer.ID) +} + +func TestAPIPullReviewRequest(t *testing.T) { + defer prepareTestEnv(t)() + pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) + assert.NoError(t, pullIssue.LoadAttributes()) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository) + + // Test add Review Request + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user4@example.com", "user8"}, + }) + session.MakeRequest(t, req, http.StatusCreated) + + // poster of pr can't be reviewer + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user1"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // test user not exist + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"testOther"}, + }) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test Remove Review Request + session2 := loginUser(t, "user4") + token2 := getTokenForLoggedInUser(t, session2) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{ + Reviewers: []string{"user4"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // doer is not admin + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{ + Reviewers: []string{"user8"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ + Reviewers: []string{"user8"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // Test team review request + pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue) + assert.NoError(t, pullIssue12.LoadAttributes()) + repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository) + + // Test add Team Review Request + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"team1", "owners"}, + }) + session.MakeRequest(t, req, http.StatusCreated) + + // Test add Team Review Request to not allowned + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"test_team"}, + }) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add Team Review Request to not exist + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"not_exist_team"}, + }) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test Remove team Review Request + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{ + TeamReviewers: []string{"team1"}, + }) + session.MakeRequest(t, req, http.StatusNoContent) + + // empty request test + req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{}) + session.MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{}) + session.MakeRequest(t, req, http.StatusNoContent) } diff --git a/integrations/api_releases_test.go b/integrations/api_releases_test.go index 9aef33d06897..58c2e35440f0 100644 --- a/integrations/api_releases_test.go +++ b/integrations/api_releases_test.go @@ -7,6 +7,7 @@ package integrations import ( "fmt" "net/http" + "strings" "testing" "code.gitea.io/gitea/models" @@ -120,3 +121,36 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") } + +func TestAPIGetReleaseByTag(t *testing.T) { + defer prepareTestEnv(t)() + + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) + session := loginUser(t, owner.LowerName) + + tag := "v1.1" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/", + owner.Name, repo.Name, tag) + + req := NewRequestf(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var release *api.Release + DecodeJSON(t, resp, &release) + + assert.Equal(t, "testing-release", release.Title) + + nonexistingtag := "nonexistingtag" + + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/", + owner.Name, repo.Name, nonexistingtag) + + req = NewRequestf(t, "GET", urlStr) + resp = session.MakeRequest(t, req, http.StatusNotFound) + + var err *api.APIError + DecodeJSON(t, resp, &err) + assert.True(t, strings.HasPrefix(err.Message, "release tag does not exist")) +} diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 0d0d4a117b37..c8afa73ae681 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -316,12 +316,21 @@ func TestAPIRepoMigrate(t *testing.T) { user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User) session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session) - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOption{ - CloneAddr: testCase.cloneURL, - UID: int(testCase.userID), - RepoName: testCase.repoName, + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ + CloneAddr: testCase.cloneURL, + RepoOwnerID: testCase.userID, + RepoName: testCase.repoName, }) - session.MakeRequest(t, req, testCase.expectedStatus) + resp := MakeRequest(t, req, NoExpectedStatus) + if resp.Code == http.StatusUnprocessableEntity { + respJSON := map[string]string{} + DecodeJSON(t, resp, &respJSON) + if assert.Equal(t, respJSON["message"], "Remote visit addressed rate limitation.") { + t.Log("test hit github rate limitation") + } + } else { + assert.EqualValues(t, testCase.expectedStatus, resp.Code) + } } } @@ -351,10 +360,10 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { cloneURL := "https://github.com/go-gitea/test_repo.git" req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+httpContext.Token, - &api.MigrateRepoOption{ - CloneAddr: cloneURL, - UID: int(userID), - RepoName: httpContext.Reponame, + &api.MigrateRepoOptions{ + CloneAddr: cloneURL, + RepoOwnerID: userID, + RepoName: httpContext.Reponame, }) resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict) respJSON := map[string]string{} diff --git a/integrations/api_settings_test.go b/integrations/api_settings_test.go new file mode 100644 index 000000000000..60dbf7a9dc0f --- /dev/null +++ b/integrations/api_settings_test.go @@ -0,0 +1,61 @@ +// 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 integrations + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIExposedSettings(t *testing.T) { + defer prepareTestEnv(t)() + + ui := new(api.GeneralUISettings) + req := NewRequest(t, "GET", "/api/v1/settings/ui") + resp := MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &ui) + assert.Len(t, ui.AllowedReactions, len(setting.UI.Reactions)) + assert.ElementsMatch(t, setting.UI.Reactions, ui.AllowedReactions) + + apiSettings := new(api.GeneralAPISettings) + req = NewRequest(t, "GET", "/api/v1/settings/api") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiSettings) + assert.EqualValues(t, &api.GeneralAPISettings{ + MaxResponseItems: setting.API.MaxResponseItems, + DefaultPagingNum: setting.API.DefaultPagingNum, + DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage, + DefaultMaxBlobSize: setting.API.DefaultMaxBlobSize, + }, apiSettings) + + repo := new(api.GeneralRepoSettings) + req = NewRequest(t, "GET", "/api/v1/settings/repository") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, &api.GeneralRepoSettings{ + MirrorsDisabled: setting.Repository.DisableMirrors, + HTTPGitDisabled: setting.Repository.DisableHTTPGit, + }, repo) + + attachment := new(api.GeneralAttachmentSettings) + req = NewRequest(t, "GET", "/api/v1/settings/attachment") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &attachment) + assert.EqualValues(t, &api.GeneralAttachmentSettings{ + Enabled: setting.Attachment.Enabled, + AllowedTypes: setting.Attachment.AllowedTypes, + MaxFiles: setting.Attachment.MaxFiles, + MaxSize: setting.Attachment.MaxSize, + }, attachment) +} diff --git a/integrations/api_token_test.go b/integrations/api_token_test.go index 05b9af7a266f..464b7ba38e2f 100644 --- a/integrations/api_token_test.go +++ b/integrations/api_token_test.go @@ -37,6 +37,19 @@ func TestAPICreateAndDeleteToken(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) + + req = NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ + "name": "test-key-2", + }) + req = AddBasicAuthHeader(req, user.Name) + resp = MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &newAccessToken) + + req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%s", newAccessToken.Name) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) } // TestAPIDeleteMissingToken ensures that error is thrown when token not found diff --git a/integrations/api_user_heatmap_test.go b/integrations/api_user_heatmap_test.go index 159d70e8679a..105d39e9ae2d 100644 --- a/integrations/api_user_heatmap_test.go +++ b/integrations/api_user_heatmap_test.go @@ -26,7 +26,7 @@ func TestUserHeatmap(t *testing.T) { var heatmap []*models.UserHeatmapData DecodeJSON(t, resp, &heatmap) var dummyheatmap []*models.UserHeatmapData - dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1571616000, Contributions: 1}) + dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1603152000, Contributions: 1}) assert.Equal(t, dummyheatmap, heatmap) } diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index 666186222820..c5295fbba5da 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -5,9 +5,12 @@ package integrations import ( + "fmt" "net/http" "testing" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -45,8 +48,14 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { var results SearchResults DecodeJSON(t, resp, &results) assert.NotEmpty(t, results.Data) + var modelUser *models.User for _, user := range results.Data { assert.Contains(t, user.UserName, query) - assert.Empty(t, user.Email) + modelUser = models.AssertExistsAndLoadBean(t, &models.User{ID: user.ID}).(*models.User) + if modelUser.KeepEmailPrivate { + assert.EqualValues(t, fmt.Sprintf("%s@%s", modelUser.LowerName, setting.Service.NoReplyAddress), user.Email) + } else { + assert.EqualValues(t, modelUser.Email, user.Email) + } } } diff --git a/integrations/attachment_test.go b/integrations/attachment_test.go index 7219adf7d75b..dd734145d2b9 100644 --- a/integrations/attachment_test.go +++ b/integrations/attachment_test.go @@ -43,7 +43,7 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri csrf := GetCSRF(t, session, repoURL) - req := NewRequestWithBody(t, "POST", "/attachments", body) + req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body) req.Header.Add("X-Csrf-Token", csrf) req.Header.Add("Content-Type", writer.FormDataContentType()) resp := session.MakeRequest(t, req, expectedStatus) diff --git a/integrations/cmd_keys_test.go b/integrations/cmd_keys_test.go index 64867d6e4c75..27ae20cccd8d 100644 --- a/integrations/cmd_keys_test.go +++ b/integrations/cmd_keys_test.go @@ -10,11 +10,11 @@ import ( "io" "net/url" "os" - "strconv" "testing" "code.gitea.io/gitea/cmd" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/urfave/cli" ) @@ -32,7 +32,7 @@ func Test_CmdKeys(t *testing.T) { {"with_key", []string{"keys", "-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="}, false, - "# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + strconv.Quote(strconv.Quote(setting.CustomConf))[1:len(strconv.Quote(strconv.Quote(setting.CustomConf)))-1] + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n", + "# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n", }, {"invalid", []string{"keys", "--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"}, } diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 b/integrations/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 new file mode 100644 index 000000000000..e319f8ce3447 Binary files /dev/null and b/integrations/gitea-repositories-meta/user3/repo3.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 differ diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 b/integrations/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 new file mode 100644 index 000000000000..ed431f70d3a5 Binary files /dev/null and b/integrations/gitea-repositories-meta/user3/repo3.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 differ diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df b/integrations/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df new file mode 100644 index 000000000000..e177f69e372b Binary files /dev/null and b/integrations/gitea-repositories-meta/user3/repo3.git/objects/ee/16d127df463aa491e08958120f2108b02468df differ diff --git a/integrations/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch b/integrations/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch new file mode 100644 index 000000000000..dfe0c6a128e7 --- /dev/null +++ b/integrations/gitea-repositories-meta/user3/repo3.git/refs/heads/test_branch @@ -0,0 +1 @@ +d22b4d4daa5be07329fcef6ed458f00cf3392da0 diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 36e0682099b9..3942d5441057 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -198,16 +198,6 @@ func initIntegrationTest() { } } - // Make the user's default search path the created schema; this will affect new connections - if _, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema)); err != nil { - log.Fatalf("db.Exec: ALTER USER SET search_path: %v", err) - } - - // Make the current connection's search the created schema - if _, err = db.Exec(fmt.Sprintf(`SET search_path = %s`, setting.Database.Schema)); err != nil { - log.Fatalf("db.Exec: ALTER USER SET search_path: %v", err) - } - case setting.Database.UseMSSQL: host, port := setting.ParseMSSQLHostPort(setting.Database.Host) db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go index 45e94406a598..431c7ed9e8d4 100644 --- a/integrations/lfs_getobject_test.go +++ b/integrations/lfs_getobject_test.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "gitea.com/macaron/gzip" gzipp "github.com/klauspost/compress/gzip" @@ -49,8 +50,10 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string lfsID++ lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject) assert.NoError(t, err) - contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath} - if !contentStore.Exists(lfsMetaObject) { + contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS} + exist, err := contentStore.Exists(lfsMetaObject) + assert.NoError(t, err) + if !exist { err := contentStore.Put(lfsMetaObject, bytes.NewReader(*content)) assert.NoError(t, err) } diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index 976a59a5791d..19a132054fcd 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -153,6 +153,7 @@ func restoreOldDB(t *testing.T, version string) bool { _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)) assert.NoError(t, err) + db.Close() db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?multiStatements=true", setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name)) @@ -183,6 +184,8 @@ func restoreOldDB(t *testing.T, version string) bool { if !assert.NoError(t, err) { return false } + defer db.Close() + schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) if !assert.NoError(t, err) || !assert.NotEmpty(t, schrows) { return false @@ -252,12 +255,28 @@ func doMigrationTest(t *testing.T, version string) { } setting.NewXORMLogService(false) - err := models.SetEngine() + + err := models.NewEngine(context.Background(), wrappedMigrate) assert.NoError(t, err) + currentEngine.Close() + + beans, _ := models.NamesToBean() - err = models.NewEngine(context.Background(), wrappedMigrate) + err = models.NewEngine(context.Background(), func(x *xorm.Engine) error { + currentEngine = x + return migrations.RecreateTables(beans...)(x) + }) assert.NoError(t, err) currentEngine.Close() + + // We do this a second time to ensure that there is not a problem with retained indices + err = models.NewEngine(context.Background(), func(x *xorm.Engine) error { + currentEngine = x + return migrations.RecreateTables(beans...)(x) + }) + assert.NoError(t, err) + + currentEngine.Close() } func TestMigrations(t *testing.T) { diff --git a/integrations/mssql.ini.tmpl b/integrations/mssql.ini.tmpl index a8fbbe7fe5ae..7a51871fc0cb 100644 --- a/integrations/mssql.ini.tmpl +++ b/integrations/mssql.ini.tmpl @@ -14,6 +14,12 @@ ISSUE_INDEXER_PATH = integrations/indexers-mssql/issues.bleve REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/indexers-mssql/repos.bleve +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}integrations/gitea-integration-mssql/gitea-repositories @@ -84,6 +90,7 @@ COLORIZE = true LEVEL = Debug [security] +DISABLE_GIT_HOOKS = false INSTALL_LOCK = true SECRET_KEY = 9pCviYTWSb INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ diff --git a/integrations/mysql.ini.tmpl b/integrations/mysql.ini.tmpl index 5691311660c8..b546748d174f 100644 --- a/integrations/mysql.ini.tmpl +++ b/integrations/mysql.ini.tmpl @@ -16,6 +16,12 @@ ISSUE_INDEXER_PATH = integrations/indexers-mysql/issues.bleve REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/indexers-mysql/repos.bleve +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}integrations/gitea-integration-mysql/gitea-repositories @@ -33,16 +39,26 @@ ROOT_URL = http://localhost:3001/ DISABLE_SSH = false SSH_LISTEN_HOST = localhost SSH_PORT = 2201 +APP_DATA_PATH = integrations/gitea-integration-mysql/data +BUILTIN_SSH_SERVER_USER = git START_SSH_SERVER = true +OFFLINE_MODE = false + LFS_START_SERVER = true LFS_CONTENT_PATH = integrations/gitea-integration-mysql/datalfs-mysql -OFFLINE_MODE = false LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w -APP_DATA_PATH = integrations/gitea-integration-mysql/data -BUILTIN_SSH_SERVER_USER = git +LFS_STORE_TYPE = minio +LFS_SERVE_DIRECT = false +LFS_MINIO_ENDPOINT = minio:9000 +LFS_MINIO_ACCESS_KEY_ID = 123456 +LFS_MINIO_SECRET_ACCESS_KEY = 12345678 +LFS_MINIO_BUCKET = gitea +LFS_MINIO_LOCATION = us-east-1 +LFS_MINIO_BASE_PATH = lfs/ +LFS_MINIO_USE_SSL = false [attachment] -STORE_TYPE = minio +STORAGE_TYPE = minio SERVE_DIRECT = false MINIO_ENDPOINT = minio:9000 MINIO_ACCESS_KEY_ID = 123456 @@ -71,6 +87,7 @@ ENABLE_NOTIFY_MAIL = true [picture] DISABLE_GRAVATAR = false ENABLE_FEDERATED_AVATAR = false + AVATAR_UPLOAD_PATH = integrations/gitea-integration-mysql/data/avatars REPOSITORY_AVATAR_UPLOAD_PATH = integrations/gitea-integration-mysql/data/repo-avatars @@ -94,6 +111,7 @@ COLORIZE = true LEVEL = Debug [security] +DISABLE_GIT_HOOKS = false INSTALL_LOCK = true SECRET_KEY = 9pCviYTWSb INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ diff --git a/integrations/mysql8.ini.tmpl b/integrations/mysql8.ini.tmpl index a135ecb9812b..d6fcbc1dd51c 100644 --- a/integrations/mysql8.ini.tmpl +++ b/integrations/mysql8.ini.tmpl @@ -14,6 +14,12 @@ ISSUE_INDEXER_PATH = integrations/indexers-mysql8/issues.bleve REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/indexers-mysql8/repos.bleve +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}integrations/gitea-integration-mysql8/gitea-repositories @@ -77,6 +83,8 @@ LEVEL = Debug [security] +DISABLE_GIT_HOOKS = false INSTALL_LOCK = true SECRET_KEY = 9pCviYTWSb INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ + diff --git a/integrations/pgsql.ini.tmpl b/integrations/pgsql.ini.tmpl index 4cac2585fbef..523def788f74 100644 --- a/integrations/pgsql.ini.tmpl +++ b/integrations/pgsql.ini.tmpl @@ -15,6 +15,12 @@ ISSUE_INDEXER_PATH = integrations/indexers-pgsql/issues.bleve REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/indexers-pgsql/repos.bleve +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}integrations/gitea-integration-pgsql/gitea-repositories @@ -85,6 +91,7 @@ COLORIZE = true LEVEL = Debug [security] +DISABLE_GIT_HOOKS = false INSTALL_LOCK = true SECRET_KEY = 9pCviYTWSb INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ diff --git a/integrations/repo_migrate_test.go b/integrations/repo_migrate_test.go index a9970655ef59..5a02b4ba03c0 100644 --- a/integrations/repo_migrate_test.go +++ b/integrations/repo_migrate_test.go @@ -5,15 +5,17 @@ package integrations import ( + "fmt" "net/http" "net/http/httptest" "testing" + "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", "/repo/migrate") + req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", structs.PlainGitService)) // render plain git migration page resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -28,8 +30,8 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str "clone_addr": cloneAddr, "uid": uid, "repo_name": repoName, - }, - ) + "service": fmt.Sprintf("%d", structs.PlainGitService), + }) resp = session.MakeRequest(t, req, http.StatusFound) return resp diff --git a/integrations/repo_search_test.go b/integrations/repo_search_test.go index 701013735c9b..6f2ee3746002 100644 --- a/integrations/repo_search_test.go +++ b/integrations/repo_search_test.go @@ -7,7 +7,6 @@ package integrations import ( "net/http" "testing" - "time" "code.gitea.io/gitea/models" code_indexer "code.gitea.io/gitea/modules/indexer/code" @@ -62,14 +61,6 @@ func testSearch(t *testing.T, url string, expected []string) { assert.EqualValues(t, expected, filenames) } -func executeIndexer(t *testing.T, repo *models.Repository, op func(*models.Repository, ...chan<- error)) { - waiter := make(chan error, 1) - op(repo, waiter) - - select { - case err := <-waiter: - assert.NoError(t, err) - case <-time.After(1 * time.Minute): - assert.Fail(t, "Repository indexer took too long") - } +func executeIndexer(t *testing.T, repo *models.Repository, op func(*models.Repository)) { + op(repo) } diff --git a/integrations/repo_test.go b/integrations/repo_test.go index 3121b5135f49..c1652aeb1d32 100644 --- a/integrations/repo_test.go +++ b/integrations/repo_test.go @@ -148,7 +148,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name") + files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name > SPAN.truncate") items := files.Map(func(i int, s *goquery.Selection) string { cls, _ := s.Find("SVG").Attr("class") file := strings.Trim(s.Find("A").Text(), " \t\n") diff --git a/integrations/sqlite.ini.tmpl b/integrations/sqlite.ini.tmpl index e899328c81fe..4c9dce4bb183 100644 --- a/integrations/sqlite.ini.tmpl +++ b/integrations/sqlite.ini.tmpl @@ -10,6 +10,12 @@ ISSUE_INDEXER_PATH = integrations/indexers-sqlite/issues.bleve REPO_INDEXER_ENABLED = true REPO_INDEXER_PATH = integrations/indexers-sqlite/repos.bleve +[queue.code_indexer] +TYPE = immediate + +[queue.push_update] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}integrations/gitea-integration-sqlite/gitea-repositories @@ -81,6 +87,7 @@ COLORIZE = true LEVEL = Debug [security] +DISABLE_GIT_HOOKS = false INSTALL_LOCK = true SECRET_KEY = 9pCviYTWSb INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.OQkH5UmzID2XBdwQ9TAI6Jj2t1X-wElVTjbE7aoN4I8 diff --git a/main.go b/main.go index 74e6911bb663..429314acc1df 100644 --- a/main.go +++ b/main.go @@ -189,5 +189,5 @@ func formatBuiltWith() string { return " built with " + version } - return " built with " + version + " : " + strings.Replace(Tags, " ", ", ", -1) + return " built with " + version + " : " + strings.ReplaceAll(Tags, " ", ", ") } diff --git a/models/admin.go b/models/admin.go index 9ed9c44ebb39..903e35b0c989 100644 --- a/models/admin.go +++ b/models/admin.go @@ -70,6 +70,10 @@ func RemoveAllWithNotice(title, path string) { // RemoveStorageWithNotice removes a file from the storage and // creates a system notice when error occurs. func RemoveStorageWithNotice(bucket storage.ObjectStorage, title, path string) { + removeStorageWithNotice(x, bucket, title, path) +} + +func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path string) { if err := bucket.Delete(path); err != nil { desc := fmt.Sprintf("%s [%s]: %v", title, path, err) log.Warn(title+" [%s]: %v", path, err) diff --git a/models/attachment.go b/models/attachment.go index 26f466a4008b..55a6cfc014b9 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" gouuid "github.com/google/uuid" @@ -43,19 +42,6 @@ func (a *Attachment) IncreaseDownloadCount() error { return nil } -// APIFormat converts models.Attachment to api.Attachment -func (a *Attachment) APIFormat() *api.Attachment { - return &api.Attachment{ - ID: a.ID, - Name: a.Name, - Created: a.CreatedUnix.AsTime(), - DownloadCount: a.DownloadCount, - Size: a.Size, - UUID: a.UUID, - DownloadURL: a.DownloadURL(), - } -} - // AttachmentRelativePath returns the relative path func AttachmentRelativePath(uuid string) string { return path.Join(uuid[0:1], uuid[1:2], uuid) diff --git a/models/avatar.go b/models/avatar.go index 311d71462993..c9ba2961ef7b 100644 --- a/models/avatar.go +++ b/models/avatar.go @@ -41,7 +41,18 @@ func AvatarLink(email string) string { Email: lowerEmail, Hash: sum, } - _, _ = x.Insert(emailHash) + // OK we're going to open a session just because I think that that might hide away any problems with postgres reporting errors + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + // we don't care about any DB problem just return the lowerEmail + return lowerEmail, nil + } + _, _ = sess.Insert(emailHash) + if err := sess.Commit(); err != nil { + // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time + return lowerEmail, nil + } return lowerEmail, nil }) return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) diff --git a/models/branches.go b/models/branches.go index 38aa79d2dc75..420a4b663a4e 100644 --- a/models/branches.go +++ b/models/branches.go @@ -19,13 +19,6 @@ import ( "github.com/unknwon/com" ) -const ( - // ProtectedBranchRepoID protected Repo ID - ProtectedBranchRepoID = "GITEA_REPO_ID" - // ProtectedBranchPRID protected Repo PR ID - ProtectedBranchPRID = "GITEA_PR_ID" -) - // ProtectedBranch struct type ProtectedBranch struct { ID int64 `xorm:"pk autoincr"` @@ -216,6 +209,38 @@ func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob { return extarr } +// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change +func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(pr *PullRequest) bool { + glob := protectBranch.GetProtectedFilePatterns() + if len(glob) == 0 { + return false + } + + return len(pr.ChangedProtectedFiles) > 0 +} + +// IsProtectedFile return if path is protected +func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool { + if len(patterns) == 0 { + patterns = protectBranch.GetProtectedFilePatterns() + if len(patterns) == 0 { + return false + } + } + + lpath := strings.ToLower(strings.TrimSpace(path)) + + r := false + for _, pat := range patterns { + if pat.Match(lpath) { + r = true + break + } + } + + return r +} + // GetProtectedBranchByRepoID getting protected branch by repo ID func GetProtectedBranchByRepoID(repoID int64) ([]*ProtectedBranch, error) { protectedBranches := make([]*ProtectedBranch, 0) diff --git a/models/commit_status.go b/models/commit_status.go index ed6f8702c5ce..15fcbff6f919 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -61,27 +61,6 @@ func (status *CommitStatus) APIURL() string { setting.AppURL, status.Repo.FullName(), status.SHA) } -// APIFormat assumes some fields assigned with values: -// Required - Repo, Creator -func (status *CommitStatus) APIFormat() *api.Status { - _ = status.loadRepo(x) - apiStatus := &api.Status{ - Created: status.CreatedUnix.AsTime(), - Updated: status.CreatedUnix.AsTime(), - State: api.StatusState(status.State), - TargetURL: status.TargetURL, - Description: status.Description, - ID: status.Index, - URL: status.APIURL(), - Context: status.Context, - } - if status.Creator != nil { - apiStatus.Creator = status.Creator.APIFormat() - } - - return apiStatus -} - // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { var lastStatus *CommitStatus diff --git a/models/convert.go b/models/convert.go index 025f88b50387..baa63bb38839 100644 --- a/models/convert.go +++ b/models/convert.go @@ -10,7 +10,7 @@ import ( "code.gitea.io/gitea/modules/setting" ) -// ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql +// ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic func ConvertUtf8ToUtf8mb4() error { _, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", setting.Database.Name)) if err != nil { @@ -22,6 +22,10 @@ func ConvertUtf8ToUtf8mb4() error { return err } for _, table := range tables { + if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic;", table.Name)); err != nil { + return err + } + if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;", table.Name)); err != nil { return err } diff --git a/models/error.go b/models/error.go index 13391e5d870b..b2273f74c91e 100644 --- a/models/error.go +++ b/models/error.go @@ -547,7 +547,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool { } func (err ErrDeployKeyNameAlreadyUsed) Error() string { - return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name) + return fmt.Sprintf("public key with name already exists [repo_id: %d, name: %s]", err.RepoID, err.Name) } // _____ ___________ __ @@ -743,6 +743,22 @@ func (err ErrRepoAlreadyExist) Error() string { return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name) } +// ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error. +type ErrRepoFilesAlreadyExist struct { + Uname string + Name string +} + +// IsErrRepoFilesAlreadyExist checks if an error is a ErrRepoAlreadyExist. +func IsErrRepoFilesAlreadyExist(err error) bool { + _, ok := err.(ErrRepoFilesAlreadyExist) + return ok +} + +func (err ErrRepoFilesAlreadyExist) Error() string { + return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name) +} + // ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error. type ErrForkAlreadyExist struct { Uname string @@ -1978,6 +1994,26 @@ func (err ErrReviewNotExist) Error() string { return fmt.Sprintf("review does not exist [id: %d]", err.ID) } +// ErrNotValidReviewRequest an not allowed review request modify +type ErrNotValidReviewRequest struct { + Reason string + UserID int64 + RepoID int64 +} + +// IsErrNotValidReviewRequest checks if an error is a ErrNotValidReviewRequest. +func IsErrNotValidReviewRequest(err error) bool { + _, ok := err.(ErrNotValidReviewRequest) + return ok +} + +func (err ErrNotValidReviewRequest) Error() string { + return fmt.Sprintf("%s [user_id: %d, repo_id: %d]", + err.Reason, + err.UserID, + err.RepoID) +} + // ________ _____ __ .__ // \_____ \ / _ \ __ ___/ |_| |__ // / | \ / /_\ \| | \ __\ | \ diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index e8a6d531f2b3..eb92aeedbe33 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -5,7 +5,7 @@ act_user_id: 2 repo_id: 2 is_private: true - created_unix: 1571686356 + created_unix: 1603228283 - id: 2 diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 39a96dc55033..3e836bf5d18f 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -135,3 +135,15 @@ is_pull: true created_unix: 1579194806 updated_unix: 1579194806 + +- + id: 12 + repo_id: 3 + index: 2 + poster_id: 2 + name: pull6 + content: content for the a pull request + is_closed: false + is_pull: true + created_unix: 1602935696 + updated_unix: 1602935696 diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml index b555da83ef76..d45baa711c6e 100644 --- a/models/fixtures/pull_request.yml +++ b/models/fixtures/pull_request.yml @@ -63,3 +63,16 @@ base_branch: branch1 merge_base: 1234567890abcdef has_merged: false + +- + id: 6 + type: 0 # gitea pull request + status: 2 # mergable + issue_id: 12 + index: 2 + head_repo_id: 3 + base_repo_id: 3 + head_branch: test_branch + base_branch: master + merge_base: 2a47ca4b614a9f5a + has_merged: false diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index a44e480270ed..c7f55a8f7021 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -41,7 +41,7 @@ is_private: true num_issues: 1 num_closed_issues: 0 - num_pulls: 0 + num_pulls: 1 num_closed_pulls: 0 num_watches: 0 num_projects: 1 diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml index 35d3dee2e6b0..c7c16fb109c3 100644 --- a/models/fixtures/review.yml +++ b/models/fixtures/review.yml @@ -44,6 +44,7 @@ reviewer_id: 2 issue_id: 3 content: "New review 3" + original_author_id: 0 updated_unix: 946684811 created_unix: 946684811 - @@ -52,6 +53,7 @@ reviewer_id: 3 issue_id: 3 content: "New review 4" + original_author_id: 0 updated_unix: 946684812 created_unix: 946684812 - @@ -59,6 +61,7 @@ type: 1 reviewer_id: 4 issue_id: 3 + original_author_id: 0 content: "New review 5" commit_id: 8091a55037cd59e47293aca02981b5a67076b364 stale: true @@ -72,6 +75,7 @@ content: "New review 3 rejected" updated_unix: 946684814 created_unix: 946684814 + original_author_id: 0 - id: 10 @@ -82,3 +86,22 @@ official: true updated_unix: 946684815 created_unix: 946684815 + +- + id: 11 + type: 4 + reviewer_id: 0 + reviewer_team_id: 7 + issue_id: 12 + official: true + updated_unix: 1602936509 + created_unix: 1602936509 + +- + id: 12 + type: 4 + reviewer_id: 1 + issue_id: 12 + official: true + updated_unix: 1603196749 + created_unix: 1603196749 \ No newline at end of file diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 640fd65bffec..7ed7d7ffd133 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -7,7 +7,8 @@ full_name: User One email: user1@example.com email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: true @@ -24,7 +25,8 @@ email: user2@example.com keep_email_private: true email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -43,7 +45,8 @@ full_name: " <<<< >> >> > >> > >>> >> " email: user3@example.com email_notifications_preference: onmention - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -60,7 +63,8 @@ full_name: " " email: user4@example.com email_notifications_preference: onmention - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -77,7 +81,8 @@ full_name: User Five email: user5@example.com email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -95,7 +100,8 @@ full_name: User Six email: user6@example.com email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -112,7 +118,8 @@ full_name: User Seven email: user7@example.com email_notifications_preference: disabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -129,7 +136,8 @@ full_name: User Eight email: user8@example.com email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -147,7 +155,8 @@ full_name: User Nine email: user9@example.com email_notifications_preference: onmention - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -162,7 +171,8 @@ name: user10 full_name: User Ten email: user10@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -177,7 +187,8 @@ name: user11 full_name: User Eleven email: user11@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -192,7 +203,8 @@ name: user12 full_name: User 12 email: user12@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -207,7 +219,8 @@ name: user13 full_name: User 13 email: user13@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -222,7 +235,8 @@ name: user14 full_name: User 14 email: user14@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -237,7 +251,8 @@ name: user15 full_name: User 15 email: user15@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -252,7 +267,8 @@ name: user16 full_name: User 16 email: user16@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -267,7 +283,8 @@ name: user17 full_name: User 17 email: user17@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -284,7 +301,8 @@ name: user18 full_name: User 18 email: user18@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -299,7 +317,8 @@ name: user19 full_name: User 19 email: user19@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -316,7 +335,8 @@ name: user20 full_name: User 20 email: user20@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -331,7 +351,8 @@ name: user21 full_name: User 21 email: user21@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -346,7 +367,8 @@ name: limited_org full_name: Limited Org email: limited_org@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -364,7 +386,8 @@ name: privated_org full_name: Privated Org email: privated_org@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -383,7 +406,8 @@ full_name: "user24" email: user24@example.com keep_email_private: true - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -401,7 +425,8 @@ name: org25 full_name: "org25" email: org25@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -418,7 +443,8 @@ full_name: "Org26" email: org26@example.com email_notifications_preference: onmention - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -436,7 +462,8 @@ full_name: User Twenty-Seven email: user27@example.com email_notifications_preference: enabled - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -451,7 +478,8 @@ full_name: "user27" email: user28@example.com keep_email_private: true - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -469,7 +497,8 @@ name: user29 full_name: User 29 email: user29@example.com - passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false diff --git a/models/gpg_key.go b/models/gpg_key.go index 662eac939b68..b944fdcbffe4 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -831,7 +831,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l newCommits = list.New() e = oldCommits.Front() ) - memberMap := map[int64]bool{} + keyMap := map[string]bool{} for e != nil { c := e.Value.(UserCommit) @@ -840,7 +840,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l Verification: ParseCommitWithSignature(c.Commit), } - _ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap) + _ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap) newCommits.PushBack(signCommit) e = e.Next() @@ -849,31 +849,70 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l } // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository -func CalculateTrustStatus(verification *CommitVerification, repository *Repository, memberMap *map[int64]bool) (err error) { - if verification.Verified { - verification.TrustStatus = "trusted" - if verification.SigningUser.ID != 0 { - var isMember bool - if memberMap != nil { - var has bool - isMember, has = (*memberMap)[verification.SigningUser.ID] - if !has { - isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) - (*memberMap)[verification.SigningUser.ID] = isMember - } - } else { - isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) - } +func CalculateTrustStatus(verification *CommitVerification, repository *Repository, keyMap *map[string]bool) (err error) { + if !verification.Verified { + return + } - if !isMember { - verification.TrustStatus = "untrusted" - if verification.CommittingUser.ID != verification.SigningUser.ID { - // The committing user and the signing user are not the same and are not the default key - // This should be marked as questionable unless the signing user is a collaborator/team member etc. - verification.TrustStatus = "unmatched" - } - } + // There are several trust models in Gitea + trustModel := repository.GetTrustModel() + + // In the Committer trust model a signature is trusted if it matches the committer + // - it doesn't matter if they're a collaborator, the owner, Gitea or Github + // NB: This model is commit verification only + if trustModel == CommitterTrustModel { + // default to "unmatched" + verification.TrustStatus = "unmatched" + + // We can only verify against users in our database but the default key will match + // against by email if it is not in the db. + if (verification.SigningUser.ID != 0 && + verification.CommittingUser.ID == verification.SigningUser.ID) || + (verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 && + verification.SigningUser.Email == verification.CommittingUser.Email) { + verification.TrustStatus = "trusted" } + return } + + // Now we drop to the more nuanced trust models... + verification.TrustStatus = "trusted" + + if verification.SigningUser.ID == 0 { + // This commit is signed by the default key - but this key is not assigned to a user in the DB. + + // However in the CollaboratorCommitterTrustModel we cannot mark this as trusted + // unless the default key matches the email of a non-user. + if trustModel == CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 || + verification.SigningUser.Email != verification.CommittingUser.Email) { + verification.TrustStatus = "untrusted" + } + return + } + + var isMember bool + if keyMap != nil { + var has bool + isMember, has = (*keyMap)[verification.SigningKey.KeyID] + if !has { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + (*keyMap)[verification.SigningKey.KeyID] = isMember + } + } else { + isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID) + } + + if !isMember { + verification.TrustStatus = "untrusted" + if verification.CommittingUser.ID != verification.SigningUser.ID { + // The committing user and the signing user are not the same + // This should be marked as questionable unless the signing user is a collaborator/team member etc. + verification.TrustStatus = "unmatched" + } + } else if trustModel == CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID { + // The committing user and the signing user are not the same and our trustmodel states that they must match + verification.TrustStatus = "unmatched" + } + return } diff --git a/models/helper_environment.go b/models/helper_environment.go index bc9d4c8fced5..8924d0a28331 100644 --- a/models/helper_environment.go +++ b/models/helper_environment.go @@ -8,19 +8,24 @@ import ( "fmt" "os" "strings" + + "code.gitea.io/gitea/modules/setting" ) // env keys for git hooks need const ( EnvRepoName = "GITEA_REPO_NAME" EnvRepoUsername = "GITEA_REPO_USER_NAME" + EnvRepoID = "GITEA_REPO_ID" EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" EnvPusherName = "GITEA_PUSHER_NAME" EnvPusherEmail = "GITEA_PUSHER_EMAIL" EnvPusherID = "GITEA_PUSHER_ID" EnvKeyID = "GITEA_KEY_ID" EnvIsDeployKey = "GITEA_IS_DEPLOY_KEY" + EnvPRID = "GITEA_PR_ID" EnvIsInternal = "GITEA_INTERNAL_PUSH" + EnvAppURL = "GITEA_ROOT_URL" ) // InternalPushingEnvironment returns an os environment to switch off hooks on push @@ -48,9 +53,7 @@ func FullPushingEnvironment(author, committer *User, repo *Repository, repoName authorSig := author.NewGitSig() committerSig := committer.NewGitSig() - // We should add "SSH_ORIGINAL_COMMAND=gitea-internal", - // once we have hook and pushing infrastructure working correctly - return append(os.Environ(), + environ := append(os.Environ(), "GIT_AUTHOR_NAME="+authorSig.Name, "GIT_AUTHOR_EMAIL="+authorSig.Email, "GIT_COMMITTER_NAME="+committerSig.Name, @@ -60,9 +63,16 @@ func FullPushingEnvironment(author, committer *User, repo *Repository, repoName EnvRepoIsWiki+"="+isWiki, EnvPusherName+"="+committer.Name, EnvPusherID+"="+fmt.Sprintf("%d", committer.ID), - ProtectedBranchRepoID+"="+fmt.Sprintf("%d", repo.ID), - ProtectedBranchPRID+"="+fmt.Sprintf("%d", prID), + EnvRepoID+"="+fmt.Sprintf("%d", repo.ID), + EnvPRID+"="+fmt.Sprintf("%d", prID), + EnvAppURL+"="+setting.AppURL, "SSH_ORIGINAL_COMMAND=gitea-internal", ) + if !committer.KeepEmailPrivate { + environ = append(environ, EnvPusherEmail+"="+committer.Email) + } + + return environ + } diff --git a/models/issue.go b/models/issue.go index 2912f7e8ef8e..ee75623f5302 100644 --- a/models/issue.go +++ b/models/issue.go @@ -67,6 +67,9 @@ type Issue struct { // IsLocked limits commenting abilities to users on an issue // with write access IsLocked bool `xorm:"NOT NULL DEFAULT false"` + + // For view issue page. + ShowTag CommentTag `xorm:"-"` } var ( @@ -546,6 +549,11 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) { } } + issue.Labels = nil + if err = issue.loadLabels(sess); err != nil { + return err + } + return sess.Commit() } @@ -709,6 +717,22 @@ func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) { return sess.Commit() } +// ChangeRef changes the branch of this issue, as the given user. +func (issue *Issue) ChangeRef(doer *User, oldRef string) (err error) { + sess := x.NewSession() + defer sess.Close() + + if err = sess.Begin(); err != nil { + return err + } + + if err = updateIssueCols(sess, issue, "ref"); err != nil { + return fmt.Errorf("updateIssueCols: %v", err) + } + + return sess.Commit() +} + // AddDeletePRBranchComment adds delete branch comment for pull request issue func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branchName string) error { issue, err := getIssueByID(x, issueID) @@ -1247,7 +1271,7 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { opts.setupSession(sess) sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID) - issues := make([]*Issue, 0, setting.UI.IssuePagingNum) + issues := make([]*Issue, 0, opts.ListOptions.PageSize) if err := sess.Find(&issues); err != nil { return nil, fmt.Errorf("Find: %v", err) } @@ -1260,6 +1284,27 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { return issues, nil } +// CountIssues number return of issues by given conditions. +func CountIssues(opts *IssuesOptions) (int64, error) { + sess := x.NewSession() + defer sess.Close() + + countsSlice := make([]*struct { + RepoID int64 + Count int64 + }, 0, 1) + + sess.Select("COUNT(issue.id) AS count").Table("issue") + opts.setupSession(sess) + if err := sess.Find(&countsSlice); err != nil { + return 0, fmt.Errorf("Find: %v", err) + } + if len(countsSlice) < 1 { + return 0, fmt.Errorf("there is less than one result sql record") + } + return countsSlice[0].Count, nil +} + // GetParticipantsIDsByIssueID returns the IDs of all users who participated in comments of an issue, // but skips joining with `user` for performance reasons. // User permissions must be verified elsewhere if required. @@ -1978,6 +2023,11 @@ func deleteIssuesByRepoID(sess Engine, repoID int64) (attachmentPaths []string, return } + if _, err = sess.In("dependent_issue_id", deleteCond). + Delete(&Comment{}); err != nil { + return + } + var attachments []*Attachment if err = sess.In("issue_id", deleteCond). Find(&attachments); err != nil { diff --git a/models/issue_comment.go b/models/issue_comment.go index 726ed7472bcf..5c053ec02a28 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -20,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/structs" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "github.com/unknwon/com" @@ -137,6 +136,8 @@ type Comment struct { AssigneeID int64 RemovedAssignee bool Assignee *User `xorm:"-"` + AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + AssigneeTeam *Team `xorm:"-"` ResolveDoerID int64 ResolveDoer *User `xorm:"-"` OldTitle string @@ -352,20 +353,6 @@ func (c *Comment) PRURL() string { return c.Issue.HTMLURL() } -// APIFormat converts a Comment to the api.Comment format -func (c *Comment) APIFormat() *api.Comment { - return &api.Comment{ - ID: c.ID, - Poster: c.Poster.APIFormat(), - HTMLURL: c.HTMLURL(), - IssueURL: c.IssueURL(), - PRURL: c.PRURL(), - Body: c.Content, - Created: c.CreatedUnix.AsTime(), - Updated: c.UpdatedUnix.AsTime(), - } -} - // CommentHashTag returns unique hash tag for comment id. func CommentHashTag(id int64) string { return fmt.Sprintf("issuecomment-%d", id) @@ -487,11 +474,11 @@ func (c *Comment) UpdateAttachments(uuids []string) error { return sess.Commit() } -// LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees -func (c *Comment) LoadAssigneeUser() error { +// LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees +func (c *Comment) LoadAssigneeUserAndTeam() error { var err error - if c.AssigneeID > 0 { + if c.AssigneeID > 0 && c.Assignee == nil { c.Assignee, err = getUserByID(x, c.AssigneeID) if err != nil { if !IsErrUserNotExist(err) { @@ -499,6 +486,25 @@ func (c *Comment) LoadAssigneeUser() error { } c.Assignee = NewGhostUser() } + } else if c.AssigneeTeamID > 0 && c.AssigneeTeam == nil { + if err = c.LoadIssue(); err != nil { + return err + } + + if err = c.Issue.LoadRepo(); err != nil { + return err + } + + if err = c.Issue.Repo.GetOwner(); err != nil { + return err + } + + if c.Issue.Repo.Owner.IsOrganization() { + c.AssigneeTeam, err = GetTeamByID(c.AssigneeTeamID) + if err != nil && !IsErrTeamNotExist(err) { + return err + } + } } return nil } @@ -685,6 +691,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err ProjectID: opts.ProjectID, RemovedAssignee: opts.RemovedAssignee, AssigneeID: opts.AssigneeID, + AssigneeTeamID: opts.AssigneeTeamID, CommitID: opts.CommitID, CommitSHA: opts.CommitSHA, Line: opts.LineNum, @@ -849,6 +856,7 @@ type CreateCommentOptions struct { OldProjectID int64 ProjectID int64 AssigneeID int64 + AssigneeTeamID int64 RemovedAssignee bool OldTitle string NewTitle string diff --git a/models/issue_label.go b/models/issue_label.go index 2b519ee71deb..ea12b42ae6c0 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -12,6 +12,8 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/builder" "xorm.io/xorm" ) @@ -21,14 +23,17 @@ var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") // Label represents a label of repository for issues. type Label struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - OrgID int64 `xorm:"INDEX"` - Name string - Description string - Color string `xorm:"VARCHAR(7)"` - NumIssues int - NumClosedIssues int + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + OrgID int64 `xorm:"INDEX"` + Name string + Description string + Color string `xorm:"VARCHAR(7)"` + NumIssues int + NumClosedIssues int + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + NumOpenIssues int `xorm:"-"` NumOpenRepoIssues int64 `xorm:"-"` IsChecked bool `xorm:"-"` @@ -670,6 +675,11 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) { return err } + issue.Labels = nil + if err = issue.loadLabels(sess); err != nil { + return err + } + return sess.Commit() } @@ -699,6 +709,11 @@ func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) { return err } + issue.Labels = nil + if err = issue.loadLabels(sess); err != nil { + return err + } + return sess.Commit() } @@ -742,5 +757,10 @@ func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) { return err } + issue.Labels = nil + if err = issue.loadLabels(sess); err != nil { + return err + } + return sess.Commit() } diff --git a/models/issue_label_test.go b/models/issue_label_test.go index 982f6b165a89..b3fe4d877332 100644 --- a/models/issue_label_test.go +++ b/models/issue_label_test.go @@ -263,7 +263,10 @@ func TestUpdateLabel(t *testing.T) { label.Name = update.Name assert.NoError(t, UpdateLabel(update)) newLabel := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) - assert.Equal(t, *label, *newLabel) + assert.EqualValues(t, label.ID, newLabel.ID) + assert.EqualValues(t, label.Color, newLabel.Color) + assert.EqualValues(t, label.Name, newLabel.Name) + assert.EqualValues(t, label.Description, newLabel.Description) CheckConsistencyFor(t, &Label{}, &Repository{}) } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index f4fba84ec0bc..5c34834e2a50 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -7,6 +7,7 @@ package models import ( "fmt" "strings" + "time" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -31,11 +32,14 @@ type Milestone struct { Completeness int // Percentage(1-100). IsOverdue bool `xorm:"-"` - DeadlineString string `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` DeadlineUnix timeutil.TimeStamp ClosedDateUnix timeutil.TimeStamp + DeadlineString string `xorm:"-"` TotalTrackedTime int64 `xorm:"-"` + TimeSinceUpdate int64 `xorm:"-"` } // BeforeUpdate is invoked from XORM before updating this object. @@ -50,6 +54,9 @@ func (m *Milestone) BeforeUpdate() { // AfterLoad is invoked from XORM after setting the value of a field of // this object. func (m *Milestone) AfterLoad() { + if !m.UpdatedUnix.IsZero() { + m.TimeSinceUpdate = time.Now().Unix() - m.UpdatedUnix.AsTime().Unix() + } m.NumOpenIssues = m.NumIssues - m.NumClosedIssues if m.DeadlineUnix.Year() == 9999 { return diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 79ce48c4cd1d..4b2bf1505d4d 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" ) @@ -20,9 +19,6 @@ type Stopwatch struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` } -// Stopwatches is a List ful of Stopwatch -type Stopwatches []Stopwatch - func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { sw = new(Stopwatch) exists, err = e. @@ -33,14 +29,14 @@ func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, } // GetUserStopwatches return list of all stopwatches of a user -func GetUserStopwatches(userID int64, listOptions ListOptions) (*Stopwatches, error) { - sws := new(Stopwatches) +func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, error) { + sws := make([]*Stopwatch, 0, 8) sess := x.Where("stopwatch.user_id = ?", userID) if listOptions.Page != 0 { sess = listOptions.setSessionPagination(sess) } - err := sess.Find(sws) + err := sess.Find(&sws) if err != nil { return nil, err } @@ -194,28 +190,3 @@ func SecToTime(duration int64) string { return hrs } - -// APIFormat convert Stopwatch type to api.StopWatch type -func (sw *Stopwatch) APIFormat() (api.StopWatch, error) { - issue, err := getIssueByID(x, sw.IssueID) - if err != nil { - return api.StopWatch{}, err - } - return api.StopWatch{ - Created: sw.CreatedUnix.AsTime(), - IssueIndex: issue.Index, - }, nil -} - -// APIFormat convert Stopwatches type to api.StopWatches type -func (sws Stopwatches) APIFormat() (api.StopWatches, error) { - result := api.StopWatches(make([]api.StopWatch, 0, len(sws))) - for _, sw := range sws { - apiSW, err := sw.APIFormat() - if err != nil { - return nil, err - } - result = append(result, apiSW) - } - return result, nil -} diff --git a/models/lfs.go b/models/lfs.go index 7a04f799c0f7..274b32a73675 100644 --- a/models/lfs.go +++ b/models/lfs.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "path" "code.gitea.io/gitea/modules/timeutil" @@ -26,6 +27,15 @@ type LFSMetaObject struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` } +// RelativePath returns the relative path of the lfs object +func (m *LFSMetaObject) RelativePath() string { + if len(m.Oid) < 5 { + return m.Oid + } + + return path.Join(m.Oid[0:2], m.Oid[2:4], m.Oid[4:]) +} + // Pointer returns the string representation of an LFS pointer file func (m *LFSMetaObject) Pointer() string { return fmt.Sprintf("%s\n%s%s\nsize %d\n", LFSMetaFileIdentifier, LFSMetaFileOidPrefix, m.Oid, m.Size) @@ -202,3 +212,25 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *User, repoID int64) error { return sess.Commit() } + +// IterateLFS iterates lfs object +func IterateLFS(f func(mo *LFSMetaObject) error) error { + var start int + const batchSize = 100 + for { + var mos = make([]*LFSMetaObject, 0, batchSize) + if err := x.Limit(batchSize, start).Find(&mos); err != nil { + return err + } + if len(mos) == 0 { + return nil + } + start += len(mos) + + for _, mo := range mos { + if err := f(mo); err != nil { + return err + } + } + } +} diff --git a/models/list_options.go b/models/list_options.go index 62c0944cc80c..0912355352d6 100644 --- a/models/list_options.go +++ b/models/list_options.go @@ -37,6 +37,14 @@ func (opts ListOptions) setEnginePagination(e Engine) Engine { return e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) } +// GetStartEnd returns the start and end of the ListOptions +func (opts ListOptions) GetStartEnd() (start, end int) { + opts.setDefaultValues() + start = (opts.Page - 1) * opts.PageSize + end = start + opts.Page + return +} + func (opts ListOptions) setDefaultValues() { if opts.PageSize <= 0 { opts.PageSize = setting.API.DefaultPagingNum diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 721b045fdce6..ee3dac362106 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -7,6 +7,8 @@ package migrations import ( "fmt" + "os" + "reflect" "regexp" "strings" @@ -228,6 +230,24 @@ var migrations = []Migration{ NewMigration("Add projects info to repository table", addProjectsInfo), // v147 -> v148 NewMigration("create review for 0 review id code comments", createReviewsForCodeComments), + // v148 -> v149 + NewMigration("remove issue dependency comments who refer to non existing issues", purgeInvalidDependenciesComments), + // v149 -> v150 + NewMigration("Add Created and Updated to Milestone table", addCreatedAndUpdatedToMilestones), + // v150 -> v151 + NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic), + // v151 -> v152 + NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2), + // v152 -> v153 + NewMigration("add TrustModel field to Repository", addTrustModelToRepository), + // v153 > v154 + NewMigration("add Team review request support", addTeamReviewRequestSupport), + // v154 > v155 + NewMigration("add timestamps to Star, Label, Follow, Watch and Collaboration", addTimeStamps), + // v155 -> v156 + NewMigration("add changed_protected_files column for pull_request table", addChangedProtectedFilesPullRequestColumn), + // v156 -> v157 + NewMigration("fix publisher ID for tag releases", fixPublisherIDforTagReleases), } // GetCurrentDBVersion returns the current db version @@ -304,12 +324,16 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t return nil } + // Downgrading Gitea's database version not supported if int(v-minDBVersion) > len(migrations) { - // User downgraded Gitea. - currentVersion.Version = int64(len(migrations) + minDBVersion) - _, err = x.ID(1).Update(currentVersion) - return err + msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n", + v, minDBVersion+len(migrations), minDBVersion+len(migrations)) + fmt.Fprint(os.Stderr, msg) + log.Fatal(msg) + return nil } + + // Migrate for i, m := range migrations[v-minDBVersion:] { log.Info("Migration[%d]: %s", v+int64(i), m.Description()) if err = m.Migrate(x); err != nil { @@ -323,6 +347,221 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t return nil } +// RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table +// WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION +func RecreateTables(beans ...interface{}) func(*xorm.Engine) error { + return func(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + sess = sess.StoreEngine("InnoDB") + for _, bean := range beans { + log.Info("Recreating Table: %s for Bean: %s", x.TableName(bean), reflect.Indirect(reflect.ValueOf(bean)).Type().Name()) + if err := recreateTable(sess, bean); err != nil { + return err + } + } + return sess.Commit() + } +} + +// recreateTable will recreate the table using the newly provided bean definition and move all data to that new table +// WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION +// WARNING: YOU MUST COMMIT THE SESSION AT THE END +func recreateTable(sess *xorm.Session, bean interface{}) error { + // TODO: This will not work if there are foreign keys + + tableName := sess.Engine().TableName(bean) + tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName) + + // We need to move the old table away and create a new one with the correct columns + // We will need to do this in stages to prevent data loss + // + // First create the temporary table + if err := sess.Table(tempTableName).CreateTable(bean); err != nil { + log.Error("Unable to create table %s. Error: %v", tempTableName, err) + return err + } + + if err := sess.Table(tempTableName).CreateUniques(bean); err != nil { + log.Error("Unable to create uniques for table %s. Error: %v", tempTableName, err) + return err + } + + if err := sess.Table(tempTableName).CreateIndexes(bean); err != nil { + log.Error("Unable to create indexes for table %s. Error: %v", tempTableName, err) + return err + } + + // Work out the column names from the bean - these are the columns to select from the old table and install into the new table + table, err := sess.Engine().TableInfo(bean) + if err != nil { + log.Error("Unable to get table info. Error: %v", err) + + return err + } + newTableColumns := table.Columns() + if len(newTableColumns) == 0 { + return fmt.Errorf("no columns in new table") + } + hasID := false + for _, column := range newTableColumns { + hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement) + } + + if hasID && setting.Database.UseMSSQL { + if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil { + log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err) + return err + } + } + + sqlStringBuilder := &strings.Builder{} + _, _ = sqlStringBuilder.WriteString("INSERT INTO `") + _, _ = sqlStringBuilder.WriteString(tempTableName) + _, _ = sqlStringBuilder.WriteString("` (`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`") + for _, column := range newTableColumns[1:] { + _, _ = sqlStringBuilder.WriteString(", `") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`") + } + _, _ = sqlStringBuilder.WriteString(")") + _, _ = sqlStringBuilder.WriteString(" SELECT ") + if newTableColumns[0].Default != "" { + _, _ = sqlStringBuilder.WriteString("COALESCE(`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`, ") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default) + _, _ = sqlStringBuilder.WriteString(")") + } else { + _, _ = sqlStringBuilder.WriteString("`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`") + } + + for _, column := range newTableColumns[1:] { + if column.Default != "" { + _, _ = sqlStringBuilder.WriteString(", COALESCE(`") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`, ") + _, _ = sqlStringBuilder.WriteString(column.Default) + _, _ = sqlStringBuilder.WriteString(")") + } else { + _, _ = sqlStringBuilder.WriteString(", `") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`") + } + } + _, _ = sqlStringBuilder.WriteString(" FROM `") + _, _ = sqlStringBuilder.WriteString(tableName) + _, _ = sqlStringBuilder.WriteString("`") + + if _, err := sess.Exec(sqlStringBuilder.String()); err != nil { + log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err) + return err + } + + if hasID && setting.Database.UseMSSQL { + if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil { + log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err) + return err + } + } + + switch { + case setting.Database.UseSQLite3: + // SQLite will drop all the constraints on the old table + if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { + log.Error("Unable to drop old table %s. Error: %v", tableName, err) + return err + } + + if err := sess.Table(tempTableName).DropIndexes(bean); err != nil { + log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err) + return err + } + + if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + + if err := sess.Table(tableName).CreateIndexes(bean); err != nil { + log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err) + return err + } + + if err := sess.Table(tableName).CreateUniques(bean); err != nil { + log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) + return err + } + + case setting.Database.UseMySQL: + // MySQL will drop all the constraints on the old table + if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { + log.Error("Unable to drop old table %s. Error: %v", tableName, err) + return err + } + + // SQLite and MySQL will move all the constraints from the temporary table to the new table + if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + case setting.Database.UsePostgreSQL: + // CASCADE causes postgres to drop all the constraints on the old table + if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil { + log.Error("Unable to drop old table %s. Error: %v", tableName, err) + return err + } + + // CASCADE causes postgres to move all the constraints from the temporary table to the new table + if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + + var indices []string + schema := sess.Engine().Dialect().URI().Schema + sess.Engine().SetSchema("") + if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + sess.Engine().SetSchema(schema) + + for _, index := range indices { + newIndexName := strings.Replace(index, "tmp_recreate__", "", 1) + if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err) + return err + } + } + + case setting.Database.UseMSSQL: + // MSSQL will drop all the constraints on the old table + if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { + log.Error("Unable to drop old table %s. Error: %v", tableName, err) + return err + } + + // MSSQL sp_rename will move all the constraints from the temporary table to the new table + if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + + default: + log.Fatal("Unrecognized DB") + } + return nil +} + +// WARNING: YOU MUST COMMIT THE SESSION AT THE END func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...string) (err error) { if tableName == "" || len(columnNames) == 0 { return nil @@ -454,7 +693,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "`" + strings.ToLower(col) + "`" } sql := fmt.Sprintf("SELECT Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('%[1]s') AND PARENT_COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))", - tableName, strings.Replace(cols, "`", "'", -1)) + tableName, strings.ReplaceAll(cols, "`", "'")) constraints := make([]string, 0) if err := sess.SQL(sql).Find(&constraints); err != nil { return fmt.Errorf("Find constraints: %v", err) diff --git a/models/migrations/v111.go b/models/migrations/v111.go index 6a94298ddcad..93831de94a96 100644 --- a/models/migrations/v111.go +++ b/models/migrations/v111.go @@ -357,21 +357,18 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { return sess.Where("uid=?", reviewer.ID).In("team_id", protectedBranch.ApprovalsWhitelistTeamIDs).Exist(new(TeamUser)) } - sess := x.NewSession() - defer sess.Close() - - if _, err := sess.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil { + if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil { return err } - if _, err := sess.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil { + if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil { return err } - if _, err := sess.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil { + if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil { return err } var pageSize int64 = 20 - qresult, err := sess.QueryInterface("SELECT max(id) as max_id FROM issue") + qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue") if err != nil { return err } @@ -383,16 +380,28 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { } totalPages := totalIssues / pageSize - // Find latest review of each user in each pull request, and set official field if appropriate - reviews := []*Review{} - var page int64 - for page = 0; page <= totalPages; page++ { - if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)", + var executeBody = func(page, pageSize int64) error { + // Find latest review of each user in each pull request, and set official field if appropriate + reviews := []*Review{} + + if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)", page*pageSize, (page+1)*pageSize, ReviewTypeApprove, ReviewTypeReject). Find(&reviews); err != nil { return err } + if len(reviews) == 0 { + return nil + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + var updated int for _, review := range reviews { reviewer := new(User) has, err := sess.ID(review.ReviewerID).Get(reviewer) @@ -407,13 +416,24 @@ func addBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { continue } review.Official = official - + updated++ if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil { return err } } + if updated > 0 { + return sess.Commit() + } + return nil + } + + var page int64 + for page = 0; page <= totalPages; page++ { + if err := executeBody(page, pageSize); err != nil { + return err + } } - return sess.Commit() + return nil } diff --git a/models/migrations/v115.go b/models/migrations/v115.go index fe3b086119cf..fcec1f549523 100644 --- a/models/migrations/v115.go +++ b/models/migrations/v115.go @@ -61,7 +61,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error { for _, user := range users { oldAvatar := user.Avatar - if stat, err := os.Stat(filepath.Join(setting.AvatarUploadPath, oldAvatar)); err != nil || !stat.Mode().IsRegular() { + if stat, err := os.Stat(filepath.Join(setting.Avatar.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() { if err == nil { err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar) } @@ -86,7 +86,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error { return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err) } - deleteList[filepath.Join(setting.AvatarUploadPath, oldAvatar)] = struct{}{} + deleteList[filepath.Join(setting.Avatar.Path, oldAvatar)] = struct{}{} migrated++ select { case <-ticker.C: @@ -135,7 +135,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error { // copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation // and returns newAvatar location func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) { - fr, err := os.Open(filepath.Join(setting.AvatarUploadPath, oldAvatar)) + fr, err := os.Open(filepath.Join(setting.Avatar.Path, oldAvatar)) if err != nil { return "", fmt.Errorf("os.Open: %v", err) } @@ -151,7 +151,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) return newAvatar, nil } - if err := ioutil.WriteFile(filepath.Join(setting.AvatarUploadPath, newAvatar), data, 0666); err != nil { + if err := ioutil.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0666); err != nil { return "", fmt.Errorf("ioutil.WriteFile: %v", err) } diff --git a/models/migrations/v148.go b/models/migrations/v148.go new file mode 100644 index 000000000000..35d17f5b2cc0 --- /dev/null +++ b/models/migrations/v148.go @@ -0,0 +1,14 @@ +// 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 purgeInvalidDependenciesComments(x *xorm.Engine) error { + _, err := x.Exec("DELETE FROM comment WHERE dependent_issue_id != 0 AND dependent_issue_id NOT IN (SELECT id FROM issue)") + return err +} diff --git a/models/migrations/v149.go b/models/migrations/v149.go new file mode 100644 index 000000000000..60c0fae8bcdb --- /dev/null +++ b/models/migrations/v149.go @@ -0,0 +1,25 @@ +// 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 ( + "fmt" + + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func addCreatedAndUpdatedToMilestones(x *xorm.Engine) error { + type Milestone struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + if err := x.Sync2(new(Milestone)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/migrations/v150.go b/models/migrations/v150.go new file mode 100644 index 000000000000..41d6a90401ad --- /dev/null +++ b/models/migrations/v150.go @@ -0,0 +1,39 @@ +// 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 ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func addPrimaryKeyToRepoTopic(x *xorm.Engine) error { + // Topic represents a topic of repositories + type Topic struct { + ID int64 `xorm:"pk autoincr"` + Name string `xorm:"UNIQUE VARCHAR(25)"` + RepoCount int + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + // RepoTopic represents associated repositories and topics + type RepoTopic struct { + RepoID int64 `xorm:"pk"` + TopicID int64 `xorm:"pk"` + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + recreateTable(sess, &Topic{}) + recreateTable(sess, &RepoTopic{}) + + return sess.Commit() +} diff --git a/models/migrations/v151.go b/models/migrations/v151.go new file mode 100644 index 000000000000..ba6eee344026 --- /dev/null +++ b/models/migrations/v151.go @@ -0,0 +1,194 @@ +// 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 ( + "fmt" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func setDefaultPasswordToArgon2(x *xorm.Engine) error { + switch { + case setting.Database.UseMySQL: + _, err := x.Exec("ALTER TABLE `user` ALTER passwd_hash_algo SET DEFAULT 'argon2';") + return err + case setting.Database.UsePostgreSQL: + _, err := x.Exec("ALTER TABLE `user` ALTER COLUMN passwd_hash_algo SET DEFAULT 'argon2';") + return err + case setting.Database.UseMSSQL: + // need to find the constraint and drop it, then recreate it. + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + res, err := sess.QueryString("SELECT [name] FROM sys.default_constraints WHERE parent_object_id=OBJECT_ID(?) AND COL_NAME(parent_object_id, parent_column_id)=?;", "user", "passwd_hash_algo") + if err != nil { + return err + } + if len(res) > 0 { + constraintName := res[0]["name"] + log.Error("Results of select constraint: %s", constraintName) + _, err := sess.Exec("ALTER TABLE [user] DROP CONSTRAINT " + constraintName) + if err != nil { + return err + } + _, err = sess.Exec("ALTER TABLE [user] ADD CONSTRAINT " + constraintName + " DEFAULT 'argon2' FOR passwd_hash_algo") + if err != nil { + return err + } + } else { + _, err := sess.Exec("ALTER TABLE [user] ADD DEFAULT('argon2') FOR passwd_hash_algo") + if err != nil { + return err + } + } + return sess.Commit() + + case setting.Database.UseSQLite3: + // drop through + default: + log.Fatal("Unrecognized DB") + } + + tables, err := x.DBMetas() + if err != nil { + return err + } + + // Now for SQLite we have to recreate the table + var table *schemas.Table + tableName := "user" + + for _, table = range tables { + if table.Name == tableName { + break + } + } + if table == nil || table.Name != tableName { + type User struct { + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` + } + return x.Sync2(new(User)) + } + column := table.GetColumn("passwd_hash_algo") + if column == nil { + type User struct { + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` + } + return x.Sync2(new(User)) + } + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + tempTableName := "tmp_recreate__user" + column.Default = "'argon2'" + + createTableSQL, _ := x.Dialect().CreateTableSQL(table, tempTableName) + for _, sql := range createTableSQL { + if _, err := sess.Exec(sql); err != nil { + log.Error("Unable to create table %s. Error: %v\n", tempTableName, err, createTableSQL) + return err + } + } + for _, index := range table.Indexes { + if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tempTableName, index)); err != nil { + log.Error("Unable to create indexes on temporary table %s. Error: %v", tempTableName, err) + return err + } + } + + newTableColumns := table.Columns() + if len(newTableColumns) == 0 { + return fmt.Errorf("no columns in new table") + } + hasID := false + for _, column := range newTableColumns { + hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement) + } + + sqlStringBuilder := &strings.Builder{} + _, _ = sqlStringBuilder.WriteString("INSERT INTO `") + _, _ = sqlStringBuilder.WriteString(tempTableName) + _, _ = sqlStringBuilder.WriteString("` (`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`") + for _, column := range newTableColumns[1:] { + _, _ = sqlStringBuilder.WriteString(", `") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`") + } + _, _ = sqlStringBuilder.WriteString(")") + _, _ = sqlStringBuilder.WriteString(" SELECT ") + if newTableColumns[0].Default != "" { + _, _ = sqlStringBuilder.WriteString("COALESCE(`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`, ") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default) + _, _ = sqlStringBuilder.WriteString(")") + } else { + _, _ = sqlStringBuilder.WriteString("`") + _, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name) + _, _ = sqlStringBuilder.WriteString("`") + } + + for _, column := range newTableColumns[1:] { + if column.Default != "" { + _, _ = sqlStringBuilder.WriteString(", COALESCE(`") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`, ") + _, _ = sqlStringBuilder.WriteString(column.Default) + _, _ = sqlStringBuilder.WriteString(")") + } else { + _, _ = sqlStringBuilder.WriteString(", `") + _, _ = sqlStringBuilder.WriteString(column.Name) + _, _ = sqlStringBuilder.WriteString("`") + } + } + _, _ = sqlStringBuilder.WriteString(" FROM `") + _, _ = sqlStringBuilder.WriteString(tableName) + _, _ = sqlStringBuilder.WriteString("`") + + if _, err := sess.Exec(sqlStringBuilder.String()); err != nil { + log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err) + return err + } + + // SQLite will drop all the constraints on the old table + if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { + log.Error("Unable to drop old table %s. Error: %v", tableName, err) + return err + } + + for _, index := range table.Indexes { + if _, err := sess.Exec(x.Dialect().DropIndexSQL(tempTableName, index)); err != nil { + log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err) + return err + } + } + + if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { + log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) + return err + } + + for _, index := range table.Indexes { + if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tableName, index)); err != nil { + log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err) + return err + } + } + + return sess.Commit() +} diff --git a/models/migrations/v152.go b/models/migrations/v152.go new file mode 100644 index 000000000000..f71f71e22f60 --- /dev/null +++ b/models/migrations/v152.go @@ -0,0 +1,14 @@ +// 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 addTrustModelToRepository(x *xorm.Engine) error { + type Repository struct { + TrustModel int + } + return x.Sync2(new(Repository)) +} diff --git a/models/migrations/v153.go b/models/migrations/v153.go new file mode 100644 index 000000000000..1e5ae9f7da47 --- /dev/null +++ b/models/migrations/v153.go @@ -0,0 +1,25 @@ +// 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 addTeamReviewRequestSupport(x *xorm.Engine) error { + type Review struct { + ReviewerTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + } + + type Comment struct { + AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync2(new(Review)); err != nil { + return err + } + + return x.Sync2(new(Comment)) +} diff --git a/models/migrations/v154.go b/models/migrations/v154.go new file mode 100644 index 000000000000..11407c30ee14 --- /dev/null +++ b/models/migrations/v154.go @@ -0,0 +1,56 @@ +// 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 ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func addTimeStamps(x *xorm.Engine) error { + // this will add timestamps where it is useful to have + + // Star represents a starred repo by an user. + type Star struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + } + if err := x.Sync2(new(Star)); err != nil { + return err + } + + // Label represents a label of repository for issues. + type Label struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync2(new(Label)); err != nil { + return err + } + + // Follow represents relations of user and his/her followers. + type Follow struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + } + if err := x.Sync2(new(Follow)); err != nil { + return err + } + + // Watch is connection request for receiving repository notification. + type Watch struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync2(new(Watch)); err != nil { + return err + } + + // Collaboration represent the relation between an individual and a repository. + type Collaboration struct { + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + return x.Sync2(new(Collaboration)) +} diff --git a/models/migrations/v155.go b/models/migrations/v155.go new file mode 100644 index 000000000000..58d78b6cfbd9 --- /dev/null +++ b/models/migrations/v155.go @@ -0,0 +1,22 @@ +// 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 ( + "fmt" + + "xorm.io/xorm" +) + +func addChangedProtectedFilesPullRequestColumn(x *xorm.Engine) error { + type PullRequest struct { + ChangedProtectedFiles []string `xorm:"TEXT JSON"` + } + + if err := x.Sync2(new(PullRequest)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/migrations/v156.go b/models/migrations/v156.go new file mode 100644 index 000000000000..6092a37d554a --- /dev/null +++ b/models/migrations/v156.go @@ -0,0 +1,139 @@ +// 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 ( + "fmt" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +// Copy paste from models/repo.go because we cannot import models package +func repoPath(userName, repoName string) string { + return filepath.Join(userPath(userName), strings.ToLower(repoName)+".git") +} + +func userPath(userName string) string { + return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) +} + +func fixPublisherIDforTagReleases(x *xorm.Engine) error { + + type Release struct { + ID int64 + RepoID int64 + Sha1 string + TagName string + PublisherID int64 + } + + type Repository struct { + ID int64 + OwnerID int64 + OwnerName string + Name string + } + + type User struct { + ID int64 + Name string + Email string + } + + const batchSize = 100 + sess := x.NewSession() + defer sess.Close() + + var ( + repo *Repository + gitRepo *git.Repository + ) + defer func() { + if gitRepo != nil { + gitRepo.Close() + } + }() + for start := 0; ; start += batchSize { + releases := make([]*Release, 0, batchSize) + + if err := sess.Begin(); err != nil { + return err + } + + if err := sess.Limit(batchSize, start).Asc("repo_id", "id").Where("is_tag=?", true).Find(&releases); err != nil { + return err + } + + if len(releases) == 0 { + break + } + + for _, release := range releases { + if repo == nil || repo.ID != release.RepoID { + if gitRepo != nil { + gitRepo.Close() + gitRepo = nil + } + repo = new(Repository) + has, err := sess.ID(release.RepoID).Get(repo) + if err != nil { + return err + } else if !has { + log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID) + log.Warn("This release should be deleted") + continue + } + + if repo.OwnerName == "" { + // v120.go migration may not have been run correctly - we'll just replicate it here + // because this appears to be a common-ish problem. + if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil { + return err + } + + if _, err := sess.ID(release.RepoID).Get(repo); err != nil { + return err + } + } + gitRepo, err = git.OpenRepository(repoPath(repo.OwnerName, repo.Name)) + if err != nil { + return err + } + } + + commit, err := gitRepo.GetTagCommit(release.TagName) + if err != nil { + return fmt.Errorf("GetTagCommit: %v", err) + } + + u := new(User) + exists, err := sess.Where("email=?", commit.Author.Email).Get(u) + if err != nil { + return err + } + + if !exists { + continue + } + + release.PublisherID = u.ID + if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil { + return err + } + } + + if err := sess.Commit(); err != nil { + return err + } + } + + return nil +} diff --git a/models/models.go b/models/models.go index e0dd3ed2a423..6aaa26d6279d 100644 --- a/models/models.go +++ b/models/models.go @@ -10,6 +10,8 @@ import ( "database/sql" "errors" "fmt" + "reflect" + "strings" "code.gitea.io/gitea/modules/setting" @@ -153,6 +155,16 @@ func getEngine() (*xorm.Engine, error) { engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"}) } engine.SetSchema(setting.Database.Schema) + if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 { + // Add the schema to the search path + if _, err := engine.Exec(`SELECT set_config( + 'search_path', + ? || ',' || current_setting('search_path'), + false)`, + setting.Database.Schema); err != nil { + return nil, err + } + } return engine, nil } @@ -214,6 +226,36 @@ func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err e return nil } +// NamesToBean return a list of beans or an error +func NamesToBean(names ...string) ([]interface{}, error) { + beans := []interface{}{} + if len(names) == 0 { + beans = append(beans, tables...) + return beans, nil + } + // Need to map provided names to beans... + beanMap := make(map[string]interface{}) + for _, bean := range tables { + + beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean + beanMap[strings.ToLower(x.TableName(bean))] = bean + beanMap[strings.ToLower(x.TableName(bean, true))] = bean + } + + gotBean := make(map[interface{}]bool) + for _, name := range names { + bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))] + if !ok { + return nil, fmt.Errorf("No table found that matches: %s", name) + } + if !gotBean[bean] { + beans = append(beans, bean) + gotBean[bean] = true + } + } + return beans, nil +} + // Statistic contains the database statistics type Statistic struct { Counter struct { @@ -270,6 +312,17 @@ func DumpDatabase(filePath string, dbType string) error { } tbs = append(tbs, t) } + + type Version struct { + ID int64 `xorm:"pk autoincr"` + Version int64 + } + t, err := x.TableInfo(Version{}) + if err != nil { + return err + } + tbs = append(tbs, t) + if len(dbType) > 0 { return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType)) } diff --git a/models/models_test.go b/models/models_test.go index 37e9a352f8af..2441ad7fb064 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -21,6 +21,12 @@ func TestDumpDatabase(t *testing.T) { dir, err := ioutil.TempDir(os.TempDir(), "dump") assert.NoError(t, err) + type Version struct { + ID int64 `xorm:"pk autoincr"` + Version int64 + } + assert.NoError(t, x.Sync2(Version{})) + for _, dbName := range setting.SupportedDatabases { dbType := setting.GetDBTypeByName(dbName) assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType)) diff --git a/models/notification.go b/models/notification.go index 9258b68f22b1..1f51c99c4c25 100644 --- a/models/notification.go +++ b/models/notification.go @@ -199,10 +199,18 @@ func createOrUpdateIssueNotifications(e Engine, issueID, commentID, notification // notify for userID := range toNotify { issue.Repo.Units = nil - if issue.IsPull && !issue.Repo.checkUnitUser(e, userID, false, UnitTypePullRequests) { + user, err := getUserByID(e, userID) + if err != nil { + if IsErrUserNotExist(err) { + continue + } + + return err + } + if issue.IsPull && !issue.Repo.checkUnitUser(e, user, UnitTypePullRequests) { continue } - if !issue.IsPull && !issue.Repo.checkUnitUser(e, userID, false, UnitTypeIssues) { + if !issue.IsPull && !issue.Repo.checkUnitUser(e, user, UnitTypeIssues) { continue } @@ -346,6 +354,7 @@ func (n *Notification) APIFormat() *api.NotificationThread { if n.Issue != nil { result.Subject.Title = n.Issue.Title result.Subject.URL = n.Issue.APIURL() + result.Subject.State = n.Issue.State() comment, err := n.Issue.GetLastComment() if err == nil && comment != nil { result.Subject.LatestCommentURL = comment.APIURL() @@ -356,6 +365,7 @@ func (n *Notification) APIFormat() *api.NotificationThread { if n.Issue != nil { result.Subject.Title = n.Issue.Title result.Subject.URL = n.Issue.APIURL() + result.Subject.State = n.Issue.State() comment, err := n.Issue.GetLastComment() if err == nil && comment != nil { result.Subject.LatestCommentURL = comment.APIURL() diff --git a/models/org.go b/models/org.go index 31e5cf81c99a..84f2892e4a17 100644 --- a/models/org.go +++ b/models/org.go @@ -11,10 +11,10 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" - "github.com/unknwon/com" "xorm.io/builder" "xorm.io/xorm" ) @@ -310,11 +310,9 @@ func deleteOrg(e *xorm.Session, u *User) error { } if len(u.Avatar) > 0 { - avatarPath := u.CustomAvatarPath() - if com.IsExist(avatarPath) { - if err := util.Remove(avatarPath); err != nil { - return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) - } + avatarPath := u.CustomAvatarRelativePath() + if err := storage.Avatars.Delete(avatarPath); err != nil { + return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) } } @@ -478,7 +476,8 @@ func GetOrgsCanCreateRepoByUserID(userID int64) ([]*User, error) { Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). Where(builder.Eq{"`team_user`.uid": userID}). And(builder.Eq{"`team`.authorize": AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). - Desc("`user`.updated_unix").Find(&orgs) + Asc("`user`.name"). + Find(&orgs) } // GetOrgUsersByUserID returns all organization-user relations by user ID. diff --git a/models/pull.go b/models/pull.go index b8b574af063e..a21fa023a3a3 100644 --- a/models/pull.go +++ b/models/pull.go @@ -46,6 +46,8 @@ type PullRequest struct { CommitsAhead int CommitsBehind int + ChangedProtectedFiles []string `xorm:"TEXT JSON"` + IssueID int64 `xorm:"INDEX"` Issue *Issue `xorm:"-"` Index int64 diff --git a/models/pull_sign.go b/models/pull_sign.go index ab61232d652c..10a6522ebef7 100644 --- a/models/pull_sign.go +++ b/models/pull_sign.go @@ -11,16 +11,16 @@ import ( ) // SignMerge determines if we should sign a PR merge commit to the base repository -func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, error) { +func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) { if err := pr.LoadBaseRepo(); err != nil { log.Error("Unable to get Base Repo for pull request") - return false, "", err + return false, "", nil, err } repo := pr.BaseRepo - signingKey := signingKey(repo.RepoPath()) + signingKey, signer := SigningKey(repo.RepoPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } rules := signingModeFromStrings(setting.Repository.Signing.Merges) @@ -31,101 +31,101 @@ Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case approved: protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch) if err != nil { - return false, "", err + return false, "", nil, err } if protectedBranch == nil { - return false, "", &ErrWontSign{approved} + return false, "", nil, &ErrWontSign{approved} } if protectedBranch.GetGrantedApprovalsCount(pr) < 1 { - return false, "", &ErrWontSign{approved} + return false, "", nil, &ErrWontSign{approved} } case baseSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(baseCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{baseSigned} + return false, "", nil, &ErrWontSign{baseSigned} } case headSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{headSigned} + return false, "", nil, &ErrWontSign{headSigned} } case commitsSigned: if gitRepo == nil { gitRepo, err = git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() } commit, err := gitRepo.GetCommit(headCommit) if err != nil { - return false, "", err + return false, "", nil, err } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{commitsSigned} + return false, "", nil, &ErrWontSign{commitsSigned} } // need to work out merge-base mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) if err != nil { - return false, "", err + return false, "", nil, err } commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) if err != nil { - return false, "", err + return false, "", nil, err } for e := commitList.Front(); e != nil; e = e.Next() { commit = e.Value.(*git.Commit) verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{commitsSigned} + return false, "", nil, &ErrWontSign{commitsSigned} } } } } - return true, signingKey, nil + return true, signingKey, signer, nil } diff --git a/models/release.go b/models/release.go index f55341b86bb1..b8ffb257a29a 100644 --- a/models/release.go +++ b/models/release.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" @@ -86,31 +85,6 @@ func (r *Release) HTMLURL() string { return fmt.Sprintf("%s/releases/tag/%s", r.Repo.HTMLURL(), r.TagName) } -// APIFormat convert a Release to api.Release -func (r *Release) APIFormat() *api.Release { - assets := make([]*api.Attachment, 0) - for _, att := range r.Attachments { - assets = append(assets, att.APIFormat()) - } - return &api.Release{ - ID: r.ID, - TagName: r.TagName, - Target: r.Target, - Title: r.Title, - Note: r.Note, - URL: r.APIURL(), - HTMLURL: r.HTMLURL(), - TarURL: r.TarURL(), - ZipURL: r.ZipURL(), - IsDraft: r.IsDraft, - IsPrerelease: r.IsPrerelease, - CreatedAt: r.CreatedUnix.AsTime(), - PublishedAt: r.CreatedUnix.AsTime(), - Publisher: r.Publisher.APIFormat(), - Attachments: assets, - } -} - // IsReleaseExist returns true if release with given tag name already exists. func IsReleaseExist(repoID int64, tagName string) (bool, error) { if len(tagName) == 0 { diff --git a/models/repo.go b/models/repo.go index b9ebec8be932..519802632902 100644 --- a/models/repo.go +++ b/models/repo.go @@ -7,7 +7,6 @@ package models import ( "context" - "crypto/md5" "errors" "fmt" "html/template" @@ -15,7 +14,6 @@ import ( // Needed for jpeg support _ "image/jpeg" - "image/png" "io/ioutil" "net" "net/url" @@ -27,7 +25,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/options" @@ -143,6 +140,47 @@ const ( RepositoryBeingMigrated // repository is migrating ) +// TrustModelType defines the types of trust model for this repository +type TrustModelType int + +// kinds of TrustModel +const ( + DefaultTrustModel TrustModelType = iota // default trust model + CommitterTrustModel + CollaboratorTrustModel + CollaboratorCommitterTrustModel +) + +// String converts a TrustModelType to a string +func (t TrustModelType) String() string { + switch t { + case DefaultTrustModel: + return "default" + case CommitterTrustModel: + return "committer" + case CollaboratorTrustModel: + return "collaborator" + case CollaboratorCommitterTrustModel: + return "collaboratorcommitter" + } + return "default" +} + +// ToTrustModel converts a string to a TrustModelType +func ToTrustModel(model string) TrustModelType { + switch strings.ToLower(strings.TrimSpace(model)) { + case "default": + return DefaultTrustModel + case "collaborator": + return CollaboratorTrustModel + case "committer": + return CommitterTrustModel + case "collaboratorcommitter": + return CollaboratorCommitterTrustModel + } + return DefaultTrustModel +} + // Repository represents a git repository. type Repository struct { ID int64 `xorm:"pk autoincr"` @@ -198,6 +236,8 @@ type Repository struct { CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` Topics []string `xorm:"TEXT JSON"` + TrustModel TrustModelType + // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` @@ -235,7 +275,7 @@ func (repo *Repository) IsBeingCreated() bool { func (repo *Repository) AfterLoad() { // FIXME: use models migration to solve all at once. if len(repo.DefaultBranch) == 0 { - repo.DefaultBranch = "master" + repo.DefaultBranch = setting.Repository.DefaultBranch } repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues @@ -370,8 +410,17 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) numReleases, _ := GetReleaseCountByRepoID(repo.ID, FindReleasesOptions{IncludeDrafts: false, IncludeTags: true}) return &api.Repository{ - ID: repo.ID, - Owner: repo.Owner.APIFormat(), + ID: repo.ID, + // TODO use convert.ToUser(repo.Owner) + Owner: &api.User{ + ID: repo.Owner.ID, + UserName: repo.Owner.Name, + FullName: repo.Owner.FullName, + Email: repo.Owner.GetEmail(), + AvatarURL: repo.Owner.AvatarLink(), + LastLogin: repo.Owner.LastLoginUnix.AsTime(), + Created: repo.Owner.CreatedUnix.AsTime(), + }, Name: repo.Name, FullName: repo.FullName(), Description: repo.Description, @@ -425,20 +474,17 @@ func (repo *Repository) getUnits(e Engine) (err error) { } // CheckUnitUser check whether user could visit the unit of this repository -func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitType) bool { - return repo.checkUnitUser(x, userID, isAdmin, unitType) +func (repo *Repository) CheckUnitUser(user *User, unitType UnitType) bool { + return repo.checkUnitUser(x, user, unitType) } -func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { - if isAdmin { +func (repo *Repository) checkUnitUser(e Engine, user *User, unitType UnitType) bool { + if user.IsAdmin { return true } - user, err := getUserByID(e, userID) - if err != nil { - return false - } perm, err := getUserRepoPermission(e, repo, user) if err != nil { + log.Error("getUserRepoPermission(): %v", err) return false } @@ -654,32 +700,37 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) { return repo.getAssignees(x) } -func (repo *Repository) getReviewersPrivate(e Engine, doerID, posterID int64) (users []*User, err error) { - users = make([]*User, 0, 20) - - if err = e. - SQL("SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name", - repo.ID, AccessModeRead, - doerID, posterID). - Find(&users); err != nil { +func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) ([]*User, error) { + // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries + if err := repo.getOwner(e); err != nil { return nil, err } - return users, nil -} - -func (repo *Repository) getReviewersPublic(e Engine, doerID, posterID int64) (_ []*User, err error) { + var users []*User - users := make([]*User, 0) + if repo.IsPrivate || + (repo.Owner.IsOrganization() && repo.Owner.Visibility == api.VisibleTypePrivate) { + // This a private repository: + // Anyone who can read the repository is a requestable reviewer + if err := e. + SQL("SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name", + repo.ID, AccessModeRead, + doerID, posterID). + Find(&users); err != nil { + return nil, err + } - const SQLCmd = "SELECT * FROM `user` WHERE id IN ( " + - "SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?) " + - "UNION " + - "SELECT user_id FROM `watch` WHERE repo_id = ? AND user_id NOT IN ( ?, ?) AND mode IN (?, ?) " + - ") ORDER BY name" + return users, nil + } - if err = e. - SQL(SQLCmd, + // This is a "public" repository: + // Any user that has write access or who is a watcher can be requested to review + if err := e. + SQL("SELECT * FROM `user` WHERE id IN ( "+ + "SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?) "+ + "UNION "+ + "SELECT user_id FROM `watch` WHERE repo_id = ? AND user_id NOT IN ( ?, ?) AND mode IN (?, ?) "+ + ") ORDER BY name", repo.ID, AccessModeRead, doerID, posterID, repo.ID, doerID, posterID, RepoWatchModeNormal, RepoWatchModeAuto). Find(&users); err != nil { @@ -689,27 +740,30 @@ func (repo *Repository) getReviewersPublic(e Engine, doerID, posterID int64) (_ return users, nil } -func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) (users []*User, err error) { - if err = repo.getOwner(e); err != nil { +// GetReviewers get all users can be requested to review: +// * for private repositories this returns all users that have read access or higher to the repository. +// * for public repositories this returns all users that have write access or higher to the repository, +// and all repo watchers. +// TODO: may be we should hava a busy choice for users to block review request to them. +func (repo *Repository) GetReviewers(doerID, posterID int64) ([]*User, error) { + return repo.getReviewers(x, doerID, posterID) +} + +// GetReviewerTeams get all teams can be requested to review +func (repo *Repository) GetReviewerTeams() ([]*Team, error) { + if err := repo.GetOwner(); err != nil { return nil, err } + if !repo.Owner.IsOrganization() { + return nil, nil + } - if repo.IsPrivate || - (repo.Owner.IsOrganization() && repo.Owner.Visibility == api.VisibleTypePrivate) { - users, err = repo.getReviewersPrivate(x, doerID, posterID) - } else { - users, err = repo.getReviewersPublic(x, doerID, posterID) + teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, AccessModeRead) + if err != nil { + return nil, err } - return -} -// GetReviewers get all users can be requested to review -// for private rpo , that return all users that have read access or higher to the repository. -// but for public rpo, that return all users that have write access or higher to the repository, -// and all repo watchers. -// TODO: may be we should hava a busy choice for users to block review request to them. -func (repo *Repository) GetReviewers(doerID, posterID int64) (_ []*User, err error) { - return repo.getReviewers(x, doerID, posterID) + return teams, err } // GetMilestoneByID returns the milestone belongs to repository by given ID. @@ -1008,7 +1062,7 @@ func (repo *Repository) CloneLink() (cl *CloneLink) { } // CheckCreateRepository check if could created a repository -func CheckCreateRepository(doer, u *User, name string) error { +func CheckCreateRepository(doer, u *User, name string, overwriteOrAdopt bool) error { if !doer.CanCreateRepo() { return ErrReachLimitOfRepo{u.MaxRepoCreation} } @@ -1023,6 +1077,10 @@ func CheckCreateRepository(doer, u *User, name string) error { } else if has { return ErrRepoAlreadyExist{u.Name, name} } + + if !overwriteOrAdopt && com.IsExist(RepoPath(u.Name, name)) { + return ErrRepoFilesAlreadyExist{u.Name, name} + } return nil } @@ -1039,8 +1097,10 @@ type CreateRepoOptions struct { DefaultBranch string IsPrivate bool IsMirror bool + IsTemplate bool AutoInit bool Status RepositoryStatus + TrustModel TrustModelType } // GetRepoInitFile returns repository init files @@ -1075,11 +1135,15 @@ var ( // IsUsableRepoName returns true when repository is usable func IsUsableRepoName(name string) error { + if alphaDashDotPattern.MatchString(name) { + // Note: usually this error is normally caught up earlier in the UI + return ErrNameCharsNotAllowed{Name: name} + } return isUsableName(reservedRepoNames, reservedRepoPatterns, name) } // CreateRepository creates a repository for the user/organization. -func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) { +func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteOrAdopt bool) (err error) { if err = IsUsableRepoName(repo.Name); err != nil { return err } @@ -1091,6 +1155,15 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error return ErrRepoAlreadyExist{u.Name, repo.Name} } + repoPath := RepoPath(u.Name, repo.Name) + if !overwriteOrAdopt && com.IsExist(repoPath) { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) + return ErrRepoFilesAlreadyExist{ + Uname: u.Name, + Name: repo.Name, + } + } + if _, err = ctx.e.Insert(repo); err != nil { return err } @@ -1689,8 +1762,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { continue } - oidPath := filepath.Join(setting.LFS.ContentPath, v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)]) - removeAllWithNotice(sess, "Delete orphaned LFS file", oidPath) + removeStorageWithNotice(sess, storage.LFS, "Delete orphaned LFS file", v.RelativePath()) } if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { @@ -1730,11 +1802,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error { } if len(repo.Avatar) > 0 { - avatarPath := repo.CustomAvatarPath() - if com.IsExist(avatarPath) { - if err := util.Remove(avatarPath); err != nil { - return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) - } + if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil { + return fmt.Errorf("Failed to remove %s: %v", repo.Avatar, err) } } @@ -1815,6 +1884,10 @@ func GetUserRepositories(opts *SearchRepoOptions) ([]*Repository, int64, error) cond = cond.And(builder.Eq{"is_private": false}) } + if opts.LowerNames != nil && len(opts.LowerNames) > 0 { + cond = cond.And(builder.In("lower_name", opts.LowerNames)) + } + sess := x.NewSession() defer sess.Close() @@ -2169,182 +2242,6 @@ func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { return &forkedRepo, nil } -// CustomAvatarPath returns repository custom avatar file path. -func (repo *Repository) CustomAvatarPath() string { - // Avatar empty by default - if len(repo.Avatar) == 0 { - return "" - } - return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar) -} - -// generateRandomAvatar generates a random avatar for repository. -func (repo *Repository) generateRandomAvatar(e Engine) error { - idToString := fmt.Sprintf("%d", repo.ID) - - seed := idToString - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %v", err) - } - - repo.Avatar = idToString - if err = os.MkdirAll(filepath.Dir(repo.CustomAvatarPath()), os.ModePerm); err != nil { - return fmt.Errorf("MkdirAll: %v", err) - } - fw, err := os.Create(repo.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("Create: %v", err) - } - defer fw.Close() - - if err = png.Encode(fw, img); err != nil { - return fmt.Errorf("Encode: %v", err) - } - log.Info("New random avatar created for repository: %d", repo.ID) - - if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil { - return err - } - - return nil -} - -// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories -func RemoveRandomAvatars(ctx context.Context) error { - return x. - Where("id > 0").BufferSize(setting.Database.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repository := bean.(*Repository) - select { - case <-ctx.Done(): - return ErrCancelledf("before random avatars removed for %s", repository.FullName()) - default: - } - stringifiedID := strconv.FormatInt(repository.ID, 10) - if repository.Avatar == stringifiedID { - return repository.DeleteAvatar() - } - return nil - }) -} - -// RelAvatarLink returns a relative link to the repository's avatar. -func (repo *Repository) RelAvatarLink() string { - return repo.relAvatarLink(x) -} - -func (repo *Repository) relAvatarLink(e Engine) string { - // If no avatar - path is empty - avatarPath := repo.CustomAvatarPath() - if len(avatarPath) == 0 || !com.IsFile(avatarPath) { - switch mode := setting.RepositoryAvatarFallback; mode { - case "image": - return setting.RepositoryAvatarFallbackImage - case "random": - if err := repo.generateRandomAvatar(e); err != nil { - log.Error("generateRandomAvatar: %v", err) - } - default: - // default behaviour: do not display avatar - return "" - } - } - return setting.AppSubURL + "/repo-avatars/" + repo.Avatar -} - -// avatarLink returns user avatar absolute link. -func (repo *Repository) avatarLink(e Engine) string { - link := repo.relAvatarLink(e) - // link may be empty! - if len(link) > 0 { - if link[0] == '/' && link[1] != '/' { - return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] - } - } - return link -} - -// UploadAvatar saves custom avatar for repository. -// FIXME: split uploads to different subdirs in case we have massive number of repos. -func (repo *Repository) UploadAvatar(data []byte) error { - m, err := avatar.Prepare(data) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - oldAvatarPath := repo.CustomAvatarPath() - - // Users can upload the same image to other repo - prefix it with ID - // Then repo will be removed - only it avatar file will be removed - repo.Avatar = fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data)) - if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil { - return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err) - } - - if err := os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm); err != nil { - return fmt.Errorf("UploadAvatar: Failed to create dir %s: %v", setting.RepositoryAvatarUploadPath, err) - } - - fw, err := os.Create(repo.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("UploadAvatar: Create file: %v", err) - } - defer fw.Close() - - if err = png.Encode(fw, *m); err != nil { - return fmt.Errorf("UploadAvatar: Encode png: %v", err) - } - - if len(oldAvatarPath) > 0 && oldAvatarPath != repo.CustomAvatarPath() { - if err := util.Remove(oldAvatarPath); err != nil { - return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err) - } - } - - return sess.Commit() -} - -// DeleteAvatar deletes the repos's custom avatar. -func (repo *Repository) DeleteAvatar() error { - - // Avatar not exists - if len(repo.Avatar) == 0 { - return nil - } - - avatarPath := repo.CustomAvatarPath() - log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - repo.Avatar = "" - if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil { - return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err) - } - - if _, err := os.Stat(avatarPath); err == nil { - if err := util.Remove(avatarPath); err != nil { - return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err) - } - } else { - // // Schrodinger: file may or may not exist. See err for details. - log.Trace("DeleteAvatar[%d]: %v", err) - } - return sess.Commit() -} - // GetOriginalURLHostname returns the hostname of a URL or the URL func (repo *Repository) GetOriginalURLHostname() string { u, err := url.Parse(repo.OriginalURL) @@ -2381,6 +2278,18 @@ func UpdateRepositoryCols(repo *Repository, cols ...string) error { return updateRepositoryCols(x, repo, cols...) } +// GetTrustModel will get the TrustModel for the repo or the default trust model +func (repo *Repository) GetTrustModel() TrustModelType { + trustModel := repo.TrustModel + if trustModel == DefaultTrustModel { + trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel) + if trustModel == DefaultTrustModel { + return CollaboratorTrustModel + } + } + return trustModel +} + // DoctorUserStarNum recalculate Stars number for all user func DoctorUserStarNum() (err error) { const batchSize = 100 @@ -2415,3 +2324,25 @@ func DoctorUserStarNum() (err error) { return } + +// IterateRepository iterate repositories +func IterateRepository(f func(repo *Repository) error) error { + var start int + var batchSize = setting.Database.IterateBufferSize + for { + var repos = make([]*Repository, 0, batchSize) + if err := x.Limit(batchSize, start).Find(&repos); err != nil { + return err + } + if len(repos) == 0 { + return nil + } + start += len(repos) + + for _, repo := range repos { + if err := f(repo); err != nil { + return err + } + } + } +} diff --git a/models/repo_avatar.go b/models/repo_avatar.go new file mode 100644 index 000000000000..6f8f55f9e3ec --- /dev/null +++ b/models/repo_avatar.go @@ -0,0 +1,190 @@ +// 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 models + +import ( + "context" + "crypto/md5" + "fmt" + "image/png" + "io" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" +) + +// CustomAvatarRelativePath returns repository custom avatar file path. +func (repo *Repository) CustomAvatarRelativePath() string { + return repo.Avatar +} + +// generateRandomAvatar generates a random avatar for repository. +func (repo *Repository) generateRandomAvatar(e Engine) error { + idToString := fmt.Sprintf("%d", repo.ID) + + seed := idToString + img, err := avatar.RandomImage([]byte(seed)) + if err != nil { + return fmt.Errorf("RandomImage: %v", err) + } + + repo.Avatar = idToString + + if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { + if err := png.Encode(w, img); err != nil { + log.Error("Encode: %v", err) + } + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", repo.CustomAvatarRelativePath(), err) + } + + log.Info("New random avatar created for repository: %d", repo.ID) + + if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil { + return err + } + + return nil +} + +// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories +func RemoveRandomAvatars(ctx context.Context) error { + return x. + Where("id > 0").BufferSize(setting.Database.IterateBufferSize). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repository := bean.(*Repository) + select { + case <-ctx.Done(): + return ErrCancelledf("before random avatars removed for %s", repository.FullName()) + default: + } + stringifiedID := strconv.FormatInt(repository.ID, 10) + if repository.Avatar == stringifiedID { + return repository.DeleteAvatar() + } + return nil + }) +} + +// RelAvatarLink returns a relative link to the repository's avatar. +func (repo *Repository) RelAvatarLink() string { + return repo.relAvatarLink(x) +} + +func (repo *Repository) relAvatarLink(e Engine) string { + // If no avatar - path is empty + avatarPath := repo.CustomAvatarRelativePath() + if len(avatarPath) == 0 { + switch mode := setting.RepoAvatar.Fallback; mode { + case "image": + return setting.RepoAvatar.FallbackImage + case "random": + if err := repo.generateRandomAvatar(e); err != nil { + log.Error("generateRandomAvatar: %v", err) + } + default: + // default behaviour: do not display avatar + return "" + } + } + return setting.AppSubURL + "/repo-avatars/" + repo.Avatar +} + +// AvatarLink returns a link to the repository's avatar. +func (repo *Repository) AvatarLink() string { + return repo.avatarLink(x) +} + +// avatarLink returns user avatar absolute link. +func (repo *Repository) avatarLink(e Engine) string { + link := repo.relAvatarLink(e) + // link may be empty! + if len(link) > 0 { + if link[0] == '/' && link[1] != '/' { + return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] + } + } + return link +} + +// UploadAvatar saves custom avatar for repository. +// FIXME: split uploads to different subdirs in case we have massive number of repos. +func (repo *Repository) UploadAvatar(data []byte) error { + m, err := avatar.Prepare(data) + if err != nil { + return err + } + + newAvatar := fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data)) + if repo.Avatar == newAvatar { // upload the same picture + return nil + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + oldAvatarPath := repo.CustomAvatarRelativePath() + + // Users can upload the same image to other repo - prefix it with ID + // Then repo will be removed - only it avatar file will be removed + repo.Avatar = newAvatar + if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil { + return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err) + } + + if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { + if err := png.Encode(w, *m); err != nil { + log.Error("Encode: %v", err) + } + return err + }); err != nil { + return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %v", repo.RepoPath(), newAvatar, err) + } + + if len(oldAvatarPath) > 0 { + if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { + return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err) + } + } + + return sess.Commit() +} + +// DeleteAvatar deletes the repos's custom avatar. +func (repo *Repository) DeleteAvatar() error { + // Avatar not exists + if len(repo.Avatar) == 0 { + return nil + } + + avatarPath := repo.CustomAvatarRelativePath() + log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + repo.Avatar = "" + if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil { + return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err) + } + + if err := storage.RepoAvatars.Delete(avatarPath); err != nil { + return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err) + } + + return sess.Commit() +} diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index 4bb95cd05c21..b9488f5e2e3e 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -8,15 +8,19 @@ package models import ( "fmt" + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/builder" ) // Collaboration represent the relation between an individual and a repository. type Collaboration struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } func (repo *Repository) addCollaborator(e Engine, u *User) error { diff --git a/models/repo_generate.go b/models/repo_generate.go index 480683cd4a5b..0b234d8e3434 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -10,10 +10,10 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" - "github.com/unknwon/com" ) // GenerateRepoOptions contains the template units to generate @@ -139,7 +139,7 @@ func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) err // GenerateAvatar generates the avatar from a template repository func GenerateAvatar(ctx DBContext, templateRepo, generateRepo *Repository) error { generateRepo.Avatar = strings.Replace(templateRepo.Avatar, strconv.FormatInt(templateRepo.ID, 10), strconv.FormatInt(generateRepo.ID, 10), 1) - if err := com.Copy(templateRepo.CustomAvatarPath(), generateRepo.CustomAvatarPath()); err != nil { + if _, err := storage.Copy(storage.RepoAvatars, generateRepo.CustomAvatarRelativePath(), storage.RepoAvatars, templateRepo.CustomAvatarRelativePath()); err != nil { return err } diff --git a/models/repo_list.go b/models/repo_list.go index dea88d8816c0..355b801a7ef0 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -175,6 +175,8 @@ type SearchRepoOptions struct { // True -> include just has milestones // False -> include just has no milestone HasMilestones util.OptionalBool + // LowerNames represents valid lower names to restrict to + LowerNames []string } //SearchOrderBy is used to sort the result diff --git a/models/repo_permission.go b/models/repo_permission.go index 2061f9770d71..7768bb6257f6 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -178,7 +178,7 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss // Prevent strangers from checking out public repo of private orginization // Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself - if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator { + if repo.Owner.IsOrganization() && !hasOrgVisible(e, repo.Owner, user) && !isCollaborator { perm.AccessMode = AccessModeNone return } diff --git a/models/repo_sign.go b/models/repo_sign.go index c9dd5ea4dc80..be9309ed4eac 100644 --- a/models/repo_sign.go +++ b/models/repo_sign.go @@ -31,7 +31,7 @@ const ( func signingModeFromStrings(modeStrings []string) []signingMode { returnable := make([]signingMode, 0, len(modeStrings)) for _, mode := range modeStrings { - signMode := signingMode(strings.ToLower(mode)) + signMode := signingMode(strings.ToLower(strings.TrimSpace(mode))) switch signMode { case never: return []signingMode{never} @@ -59,9 +59,10 @@ func signingModeFromStrings(modeStrings []string) []signingMode { return returnable } -func signingKey(repoPath string) string { +// SigningKey returns the KeyID and git Signature for the repo +func SigningKey(repoPath string) (string, *git.Signature) { if setting.Repository.Signing.SigningKey == "none" { - return "" + return "", nil } if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { @@ -69,19 +70,27 @@ func signingKey(repoPath string) string { value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { - return "" + return "", nil } signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath) - return strings.TrimSpace(signingKey) + signingName, _ := git.NewCommand("config", "--get", "user.name").RunInDir(repoPath) + signingEmail, _ := git.NewCommand("config", "--get", "user.email").RunInDir(repoPath) + return strings.TrimSpace(signingKey), &git.Signature{ + Name: strings.TrimSpace(signingName), + Email: strings.TrimSpace(signingEmail), + } } - return setting.Repository.Signing.SigningKey + return setting.Repository.Signing.SigningKey, &git.Signature{ + Name: setting.Repository.Signing.SigningName, + Email: setting.Repository.Signing.SigningEmail, + } } // PublicSigningKey gets the public signing key within a provided repository directory func PublicSigningKey(repoPath string) (string, error) { - signingKey := signingKey(repoPath) + signingKey, _ := SigningKey(repoPath) if signingKey == "" { return "", nil } @@ -96,143 +105,143 @@ func PublicSigningKey(repoPath string) (string, error) { } // SignInitialCommit determines if we should sign the initial commit to this repository -func SignInitialCommit(repoPath string, u *User) (bool, string, error) { +func SignInitialCommit(repoPath string, u *User) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) - signingKey := signingKey(repoPath) + signingKey, sig := SigningKey(repoPath) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } // SignWikiCommit determines if we should sign the commits to this repository wiki -func (repo *Repository) SignWikiCommit(u *User) (bool, string, error) { +func (repo *Repository) SignWikiCommit(u *User) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.Wiki) - signingKey := signingKey(repo.WikiPath()) + signingKey, sig := SigningKey(repo.WikiPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := git.OpenRepository(repo.WikiPath()) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit("HEAD") if err != nil { - return false, "", err + return false, "", nil, err } if commit.Signature == nil { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } // SignCRUDAction determines if we should sign a CRUD commit to this repository -func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, error) { +func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) { rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) - signingKey := signingKey(repo.RepoPath()) + signingKey, sig := SigningKey(repo.RepoPath()) if signingKey == "" { - return false, "", &ErrWontSign{noKey} + return false, "", nil, &ErrWontSign{noKey} } Loop: for _, rule := range rules { switch rule { case never: - return false, "", &ErrWontSign{never} + return false, "", nil, &ErrWontSign{never} case always: break Loop case pubkey: keys, err := ListGPGKeys(u.ID, ListOptions{}) if err != nil { - return false, "", err + return false, "", nil, err } if len(keys) == 0 { - return false, "", &ErrWontSign{pubkey} + return false, "", nil, &ErrWontSign{pubkey} } case twofa: twofaModel, err := GetTwoFactorByUID(u.ID) if err != nil && !IsErrTwoFactorNotEnrolled(err) { - return false, "", err + return false, "", nil, err } if twofaModel == nil { - return false, "", &ErrWontSign{twofa} + return false, "", nil, &ErrWontSign{twofa} } case parentSigned: gitRepo, err := git.OpenRepository(tmpBasePath) if err != nil { - return false, "", err + return false, "", nil, err } defer gitRepo.Close() commit, err := gitRepo.GetCommit(parentCommit) if err != nil { - return false, "", err + return false, "", nil, err } if commit.Signature == nil { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } verification := ParseCommitWithSignature(commit) if !verification.Verified { - return false, "", &ErrWontSign{parentSigned} + return false, "", nil, &ErrWontSign{parentSigned} } } } - return true, signingKey, nil + return true, signingKey, sig, nil } diff --git a/models/repo_test.go b/models/repo_test.go index 045f94670bde..a366772d5c25 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -193,3 +193,34 @@ func TestDoctorUserStarNum(t *testing.T) { assert.NoError(t, DoctorUserStarNum()) } + +func TestRepoGetReviewers(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + // test public repo + repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + + reviewers, err := repo1.GetReviewers(2, 2) + assert.NoError(t, err) + assert.Equal(t, 4, len(reviewers)) + + // test private repo + repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + reviewers, err = repo2.GetReviewers(2, 2) + assert.NoError(t, err) + assert.Equal(t, 0, len(reviewers)) +} + +func TestRepoGetReviewerTeams(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + teams, err := repo2.GetReviewerTeams() + assert.NoError(t, err) + assert.Empty(t, teams) + + repo3 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + teams, err = repo3.GetReviewerTeams() + assert.NoError(t, err) + assert.Equal(t, 2, len(teams)) +} diff --git a/models/repo_watch.go b/models/repo_watch.go index 6cdf9b2af5f5..0e4645f26f0e 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -8,6 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" ) // RepoWatchMode specifies what kind of watch the user has on a repository @@ -26,10 +27,12 @@ const ( // Watch is connection request for receiving repository notification. type Watch struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` - Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(watch)"` + RepoID int64 `xorm:"UNIQUE(watch)"` + Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found diff --git a/models/review.go b/models/review.go index 5f27e2b7fd20..aeb5f21ea907 100644 --- a/models/review.go +++ b/models/review.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" @@ -54,6 +55,8 @@ type Review struct { Type ReviewType Reviewer *User `xorm:"-"` ReviewerID int64 `xorm:"index"` + ReviewerTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + ReviewerTeam *Team `xorm:"-"` OriginalAuthor string OriginalAuthorID int64 Issue *Issue `xorm:"-"` @@ -98,18 +101,32 @@ func (r *Review) loadIssue(e Engine) (err error) { } func (r *Review) loadReviewer(e Engine) (err error) { - if r.Reviewer != nil || r.ReviewerID == 0 { - return nil + if r.ReviewerID == 0 || r.Reviewer != nil { + return } r.Reviewer, err = getUserByID(e, r.ReviewerID) return } +func (r *Review) loadReviewerTeam(e Engine) (err error) { + if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil { + return + } + + r.ReviewerTeam, err = getTeamByID(e, r.ReviewerTeamID) + return +} + // LoadReviewer loads reviewer func (r *Review) LoadReviewer() error { return r.loadReviewer(x) } +// LoadReviewerTeam loads reviewer team +func (r *Review) LoadReviewerTeam() error { + return r.loadReviewerTeam(x) +} + func (r *Review) loadAttributes(e Engine) (err error) { if err = r.loadIssue(e); err != nil { return @@ -120,6 +137,9 @@ func (r *Review) loadAttributes(e Engine) (err error) { if err = r.loadReviewer(e); err != nil { return } + if err = r.loadReviewerTeam(e); err != nil { + return + } return } @@ -189,21 +209,49 @@ func FindReviews(opts FindReviewOptions) ([]*Review, error) { // CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required. type CreateReviewOptions struct { - Content string - Type ReviewType - Issue *Issue - Reviewer *User - Official bool - CommitID string - Stale bool + Content string + Type ReviewType + Issue *Issue + Reviewer *User + ReviewerTeam *Team + Official bool + CommitID string + Stale bool +} + +// IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals) +func IsOfficialReviewer(issue *Issue, reviewers ...*User) (bool, error) { + return isOfficialReviewer(x, issue, reviewers...) +} + +func isOfficialReviewer(e Engine, issue *Issue, reviewers ...*User) (bool, error) { + pr, err := getPullRequestByIssueID(e, issue.ID) + if err != nil { + return false, err + } + if err = pr.loadProtectedBranch(e); err != nil { + return false, err + } + if pr.ProtectedBranch == nil { + return false, nil + } + + for _, reviewer := range reviewers { + official, err := pr.ProtectedBranch.isUserOfficialReviewer(e, reviewer) + if official || err != nil { + return official, err + } + } + + return false, nil } -// IsOfficialReviewer check if reviewer can make official reviews in issue (counts towards required approvals) -func IsOfficialReviewer(issue *Issue, reviewer *User) (bool, error) { - return isOfficialReviewer(x, issue, reviewer) +// IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals) +func IsOfficialReviewerTeam(issue *Issue, team *Team) (bool, error) { + return isOfficialReviewerTeam(x, issue, team) } -func isOfficialReviewer(e Engine, issue *Issue, reviewer *User) (bool, error) { +func isOfficialReviewerTeam(e Engine, issue *Issue, team *Team) (bool, error) { pr, err := getPullRequestByIssueID(e, issue.ID) if err != nil { return false, err @@ -215,20 +263,32 @@ func isOfficialReviewer(e Engine, issue *Issue, reviewer *User) (bool, error) { return false, nil } - return pr.ProtectedBranch.isUserOfficialReviewer(e, reviewer) + if !pr.ProtectedBranch.EnableApprovalsWhitelist { + return team.Authorize >= AccessModeWrite, nil + } + + return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil } func createReview(e Engine, opts CreateReviewOptions) (*Review, error) { review := &Review{ - Type: opts.Type, - Issue: opts.Issue, - IssueID: opts.Issue.ID, - Reviewer: opts.Reviewer, - ReviewerID: opts.Reviewer.ID, - Content: opts.Content, - Official: opts.Official, - CommitID: opts.CommitID, - Stale: opts.Stale, + Type: opts.Type, + Issue: opts.Issue, + IssueID: opts.Issue.ID, + Reviewer: opts.Reviewer, + ReviewerTeam: opts.ReviewerTeam, + Content: opts.Content, + Official: opts.Official, + CommitID: opts.CommitID, + Stale: opts.Stale, + } + if opts.Reviewer != nil { + review.ReviewerID = opts.Reviewer.ID + } else { + if review.Type != ReviewTypeRequest { + review.Type = ReviewTypeRequest + } + review.ReviewerTeamID = opts.ReviewerTeam.ID } if _, err := e.Insert(review); err != nil { return nil, err @@ -311,14 +371,13 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, doer.ID); err != nil { return nil, nil, err } - official, err = isOfficialReviewer(sess, issue, doer) - if err != nil { + if official, err = isOfficialReviewer(sess, issue, doer); err != nil { return nil, nil, err } } // No current review. Create a new one! - review, err = createReview(sess, CreateReviewOptions{ + if review, err = createReview(sess, CreateReviewOptions{ Type: reviewType, Issue: issue, Reviewer: doer, @@ -326,8 +385,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm Official: official, CommitID: commitID, Stale: stale, - }) - if err != nil { + }); err != nil { return nil, nil, err } } else { @@ -343,8 +401,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, doer.ID); err != nil { return nil, nil, err } - official, err = isOfficialReviewer(sess, issue, doer) - if err != nil { + if official, err = isOfficialReviewer(sess, issue, doer); err != nil { return nil, nil, err } } @@ -373,13 +430,34 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm return nil, nil, err } + // try to remove team review request if need + if issue.Repo.Owner.IsOrganization() && (reviewType == ReviewTypeApprove || reviewType == ReviewTypeReject) { + teamReviewRequests := make([]*Review, 0, 10) + if err := sess.SQL("SELECT * FROM review WHERE reviewer_team_id > 0 AND type = ?", ReviewTypeRequest).Find(&teamReviewRequests); err != nil { + return nil, nil, err + } + + for _, teamReviewRequest := range teamReviewRequests { + ok, err := isTeamMember(sess, issue.Repo.OwnerID, teamReviewRequest.ReviewerTeamID, doer.ID) + if err != nil { + return nil, nil, err + } else if !ok { + continue + } + + if _, err := sess.Delete(teamReviewRequest); err != nil { + return nil, nil, err + } + } + } + comm.Review = review return review, comm, sess.Commit() } // GetReviewersByIssueID gets the latest review of each reviewer for a pull request -func GetReviewersByIssueID(issueID int64) (reviews []*Review, err error) { - reviewsUnfiltered := []*Review{} +func GetReviewersByIssueID(issueID int64) ([]*Review, error) { + reviews := make([]*Review, 0, 10) sess := x.NewSession() defer sess.Close() @@ -388,40 +466,81 @@ func GetReviewersByIssueID(issueID int64) (reviews []*Review, err error) { } // Get latest review of each reviwer, sorted in order they were made - if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND type in (?, ?, ?) GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC", + if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC", issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). - Find(&reviewsUnfiltered); err != nil { + Find(&reviews); err != nil { return nil, err } - // Load reviewer and skip if user is deleted - for _, review := range reviewsUnfiltered { - if err = review.loadReviewer(sess); err != nil { - if !IsErrUserNotExist(err) { - return nil, err - } - } else { - reviews = append(reviews, review) - } + teamReviewRequests := make([]*Review, 0, 5) + if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id <> 0 AND original_author_id = 0 GROUP BY issue_id, reviewer_team_id) ORDER BY review.updated_unix ASC", + issueID). + Find(&teamReviewRequests); err != nil { + return nil, err + } + + if len(teamReviewRequests) > 0 { + reviews = append(reviews, teamReviewRequests...) } return reviews, nil } -// GetReviewerByIssueIDAndUserID get the latest review of reviewer for a pull request -func GetReviewerByIssueIDAndUserID(issueID, userID int64) (review *Review, err error) { - return getReviewerByIssueIDAndUserID(x, issueID, userID) +// GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request +func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) ([]*Review, error) { + reviews := make([]*Review, 0, 10) + + // Get latest review of each reviwer, sorted in order they were made + if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", + issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). + Find(&reviews); err != nil { + return nil, err + } + + return reviews, nil } -func getReviewerByIssueIDAndUserID(e Engine, issueID, userID int64) (review *Review, err error) { - review = new(Review) +// GetReviewByIssueIDAndUserID get the latest review of reviewer for a pull request +func GetReviewByIssueIDAndUserID(issueID, userID int64) (*Review, error) { + return getReviewByIssueIDAndUserID(x, issueID, userID) +} + +func getReviewByIssueIDAndUserID(e Engine, issueID, userID int64) (*Review, error) { + review := new(Review) - if _, err := e.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND type in (?, ?, ?))", + has, err := e.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))", issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). + Get(review) + if err != nil { + return nil, err + } + + if !has { + return nil, ErrReviewNotExist{} + } + + return review, nil +} + +// GetTeamReviewerByIssueIDAndTeamID get the latest review requst of reviewer team for a pull request +func GetTeamReviewerByIssueIDAndTeamID(issueID, teamID int64) (review *Review, err error) { + return getTeamReviewerByIssueIDAndTeamID(x, issueID, teamID) +} + +func getTeamReviewerByIssueIDAndTeamID(e Engine, issueID, teamID int64) (review *Review, err error) { + review = new(Review) + + has := false + if has, err = e.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)", + issueID, teamID). Get(review); err != nil { return nil, err } + if !has { + return nil, ErrReviewNotExist{0} + } + return } @@ -482,65 +601,52 @@ func InsertReviews(reviews []*Review) error { } // AddReviewRequest add a review request from one reviewer -func AddReviewRequest(issue *Issue, reviewer *User, doer *User) (comment *Comment, err error) { - review, err := GetReviewerByIssueIDAndUserID(issue.ID, reviewer.ID) - if err != nil { - return - } - - // skip it when reviewer hase been request to review - if review != nil && review.Type == ReviewTypeRequest { - return nil, nil - } - +func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return nil, err } - var official bool - official, err = isOfficialReviewer(sess, issue, reviewer) - - if err != nil { + review, err := getReviewByIssueIDAndUserID(sess, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { return nil, err } - if !official { - official, err = isOfficialReviewer(sess, issue, doer) - - if err != nil { - return nil, err - } + // skip it when reviewer hase been request to review + if review != nil && review.Type == ReviewTypeRequest { + return nil, nil } - if official { + official, err := isOfficialReviewer(sess, issue, reviewer, doer) + if err != nil { + return nil, err + } else if official { if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, reviewer.ID); err != nil { return nil, err } } - _, err = createReview(sess, CreateReviewOptions{ + review, err = createReview(sess, CreateReviewOptions{ Type: ReviewTypeRequest, Issue: issue, Reviewer: reviewer, Official: official, Stale: false, }) - if err != nil { - return + return nil, err } - comment, err = createComment(sess, &CreateCommentOptions{ + comment, err := createComment(sess, &CreateCommentOptions{ Type: CommentTypeReviewRequest, Doer: doer, Repo: issue.Repo, Issue: issue, RemovedAssignee: false, // Use RemovedAssignee as !isRequest AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + ReviewID: review.ID, }) - if err != nil { return nil, err } @@ -549,39 +655,147 @@ func AddReviewRequest(issue *Issue, reviewer *User, doer *User) (comment *Commen } //RemoveReviewRequest remove a review request from one reviewer -func RemoveReviewRequest(issue *Issue, reviewer *User, doer *User) (comment *Comment, err error) { - review, err := GetReviewerByIssueIDAndUserID(issue.ID, reviewer.ID) - if err != nil { - return +func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return nil, err + } + + review, err := getReviewByIssueIDAndUserID(sess, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err } - if review.Type != ReviewTypeRequest { + if review == nil || review.Type != ReviewTypeRequest { return nil, nil } + if _, err = sess.Delete(review); err != nil { + return nil, err + } + + official, err := isOfficialReviewer(sess, issue, reviewer) + if err != nil { + return nil, err + } else if official { + // recalculate the latest official review for reviewer + review, err := getReviewByIssueIDAndUserID(sess, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err + } + + if review != nil { + if _, err := sess.Exec("UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil { + return nil, err + } + } + } + + comment, err := createComment(sess, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: true, // Use RemovedAssignee as !isRequest + AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + }) + if err != nil { + return nil, err + } + + return comment, sess.Commit() +} + +// AddTeamReviewRequest add a review request from one team +func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return nil, err } - _, err = sess.Delete(review) - if err != nil { + review, err := getTeamReviewerByIssueIDAndTeamID(sess, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { return nil, err } - var official bool - official, err = isOfficialReviewer(sess, issue, reviewer) + // This team already has been requested to review - therefore skip this. + if review != nil { + return nil, nil + } + + official, err := isOfficialReviewerTeam(sess, issue, reviewer) if err != nil { - return + return nil, fmt.Errorf("isOfficialReviewerTeam(): %v", err) + } else if !official { + if official, err = isOfficialReviewer(sess, issue, doer); err != nil { + return nil, fmt.Errorf("isOfficialReviewer(): %v", err) + } + } + + if review, err = createReview(sess, CreateReviewOptions{ + Type: ReviewTypeRequest, + Issue: issue, + ReviewerTeam: reviewer, + Official: official, + Stale: false, + }); err != nil { + return nil, err } if official { - // recalculate which is the latest official review from that user - var review *Review + if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?", false, issue.ID, reviewer.ID); err != nil { + return nil, err + } + } - review, err = getReviewerByIssueIDAndUserID(sess, issue.ID, reviewer.ID) - if err != nil { + comment, err := createComment(sess, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: false, // Use RemovedAssignee as !isRequest + AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID + ReviewID: review.ID, + }) + if err != nil { + return nil, fmt.Errorf("createComment(): %v", err) + } + + return comment, sess.Commit() +} + +//RemoveTeamReviewRequest remove a review request from one team +func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return nil, err + } + + review, err := getTeamReviewerByIssueIDAndTeamID(sess, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err + } + + if review == nil { + return nil, nil + } + + if _, err = sess.Delete(review); err != nil { + return nil, err + } + + official, err := isOfficialReviewerTeam(sess, issue, reviewer) + if err != nil { + return nil, fmt.Errorf("isOfficialReviewerTeam(): %v", err) + } + + if official { + // recalculate which is the latest official review from that team + review, err := getReviewByIssueIDAndUserID(sess, issue.ID, -reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { return nil, err } @@ -592,21 +806,20 @@ func RemoveReviewRequest(issue *Issue, reviewer *User, doer *User) (comment *Com } } - if err != nil { - return nil, err + if doer == nil { + return nil, sess.Commit() } - comment, err = createComment(sess, &CreateCommentOptions{ + comment, err := createComment(sess, &CreateCommentOptions{ Type: CommentTypeReviewRequest, Doer: doer, Repo: issue.Repo, Issue: issue, RemovedAssignee: true, // Use RemovedAssignee as !isRequest - AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID }) - if err != nil { - return nil, err + return nil, fmt.Errorf("createComment(): %v", err) } return comment, sess.Commit() @@ -684,6 +897,10 @@ func DeleteReview(r *Review) error { return fmt.Errorf("review is not allowed to be 0") } + if r.Type == ReviewTypeRequest { + return fmt.Errorf("review request can not be deleted using this method") + } + opts := FindCommentsOptions{ Type: CommentTypeCode, IssueID: r.IssueID, diff --git a/models/review_test.go b/models/review_test.go index 7103962ce9a9..702e21682404 100644 --- a/models/review_test.go +++ b/models/review_test.go @@ -130,6 +130,9 @@ func TestGetReviewersByIssueID(t *testing.T) { }) allReviews, err := GetReviewersByIssueID(issue.ID) + for _, reviewer := range allReviews { + assert.NoError(t, reviewer.LoadReviewer()) + } assert.NoError(t, err) if assert.Len(t, allReviews, 3) { for i, review := range allReviews { diff --git a/models/ssh_key.go b/models/ssh_key.go index 753ad57934c9..d67981398bc0 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -38,8 +38,10 @@ import ( const ( tplCommentPrefix = `# gitea public key` - tplCommand = "%s --config=%q serv key-%d" - tplPublicKey = tplCommentPrefix + "\n" + `command=%q,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" + tplCommand = "%s --config=%s serv key-%d" + tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" + + authorizedPrincipalsFile = "authorized_principals" ) var sshOpLocker sync.Mutex @@ -52,6 +54,8 @@ const ( KeyTypeUser = iota + 1 // KeyTypeDeploy specifies the deploy key KeyTypeDeploy + // KeyTypePrincipal specifies the authorized principal key + KeyTypePrincipal ) // PublicKey represents a user or deploy SSH public key. @@ -84,7 +88,7 @@ func (key *PublicKey) OmitEmail() string { // AuthorizedString returns formatted public key string for authorized_keys file. func (key *PublicKey) AuthorizedString() string { - return fmt.Sprintf(tplPublicKey, fmt.Sprintf(tplCommand, setting.AppPath, setting.CustomConf, key.ID), key.Content) + return fmt.Sprintf(tplPublicKey, util.ShellEscape(fmt.Sprintf(tplCommand, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf), key.ID)), key.Content) } func extractTypeFromBase64Key(key string) (string, error) { @@ -401,6 +405,9 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { } for _, key := range keys { + if key.Type == KeyTypePrincipal { + continue + } if _, err = f.WriteString(key.AuthorizedString()); err != nil { return err } @@ -571,6 +578,25 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) { return searchPublicKeyByContentWithEngine(x, content) } +func searchPublicKeyByContentExactWithEngine(e Engine, content string) (*PublicKey, error) { + key := new(PublicKey) + has, err := e. + Where("content = ?", content). + Get(key) + if err != nil { + return nil, err + } else if !has { + return nil, ErrKeyNotExist{} + } + return key, nil +} + +// SearchPublicKeyByContentExact searches content +// and returns public key found. +func SearchPublicKeyByContentExact(content string) (*PublicKey, error) { + return searchPublicKeyByContentExactWithEngine(x, content) +} + // SearchPublicKey returns a list of public keys matching the provided arguments. func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) @@ -586,7 +612,7 @@ func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) { // ListPublicKeys returns a list of public keys belongs to given user. func ListPublicKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { - sess := x.Where("owner_id = ?", uid) + sess := x.Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal) if listOptions.Page != 0 { sess = listOptions.setSessionPagination(sess) @@ -662,6 +688,10 @@ func DeletePublicKey(doer *User, id int64) (err error) { } sess.Close() + if key.Type == KeyTypePrincipal { + return RewriteAllPrincipalKeys() + } + return RewriteAllPublicKeys() } @@ -727,11 +757,10 @@ func RegeneratePublicKeys(t io.StringWriter) error { } func regeneratePublicKeys(e Engine, t io.StringWriter) error { - err := e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { + if err := e.Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) return err - }) - if err != nil { + }); err != nil { return err } @@ -1041,3 +1070,204 @@ func SearchDeployKeys(repoID int64, keyID int64, fingerprint string) ([]*DeployK } return keys, x.Where(cond).Find(&keys) } + +// __________ .__ .__ .__ +// \______ _______|__| ____ ____ |_____________ | | ______ +// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/ +// | | | | \| | | \ \___| | |_> / __ \| |__\___ \ +// |____| |__| |__|___| /\___ |__| __(____ |____/____ > +// \/ \/ |__| \/ \/ + +// AddPrincipalKey adds new principal to database and authorized_principals file. +func AddPrincipalKey(ownerID int64, content string, loginSourceID int64) (*PublicKey, error) { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return nil, err + } + + // Principals cannot be duplicated. + has, err := sess. + Where("content = ? AND type = ?", content, KeyTypePrincipal). + Get(new(PublicKey)) + if err != nil { + return nil, err + } else if has { + return nil, ErrKeyAlreadyExist{0, "", content} + } + + key := &PublicKey{ + OwnerID: ownerID, + Name: content, + Content: content, + Mode: AccessModeWrite, + Type: KeyTypePrincipal, + LoginSourceID: loginSourceID, + } + if err = addPrincipalKey(sess, key); err != nil { + return nil, fmt.Errorf("addKey: %v", err) + } + + if err = sess.Commit(); err != nil { + return nil, err + } + + sess.Close() + + return key, RewriteAllPrincipalKeys() +} + +func addPrincipalKey(e Engine, key *PublicKey) (err error) { + // Save Key representing a principal. + if _, err = e.Insert(key); err != nil { + return err + } + + return nil +} + +// CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines +func CheckPrincipalKeyString(user *User, content string) (_ string, err error) { + if setting.SSH.Disabled { + return "", ErrSSHDisabled{} + } + + content = strings.TrimSpace(content) + if strings.ContainsAny(content, "\r\n") { + return "", errors.New("only a single line with a single principal please") + } + + // check all the allowed principals, email, username or anything + // if any matches, return ok + for _, v := range setting.SSH.AuthorizedPrincipalsAllow { + switch v { + case "anything": + return content, nil + case "email": + emails, err := GetEmailAddresses(user.ID) + if err != nil { + return "", err + } + for _, email := range emails { + if !email.IsActivated { + continue + } + if content == email.Email { + return content, nil + } + } + + case "username": + if content == user.Name { + return content, nil + } + } + } + + return "", fmt.Errorf("didn't match allowed principals: %s", setting.SSH.AuthorizedPrincipalsAllow) +} + +// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again. +// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function +// outside any session scope independently. +func RewriteAllPrincipalKeys() error { + return rewriteAllPrincipalKeys(x) +} + +func rewriteAllPrincipalKeys(e Engine) error { + // Don't rewrite key if internal server + if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedPrincipalsFile { + return nil + } + + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + + if setting.SSH.RootPath != "" { + // First of ensure that the RootPath is present, and if not make it with 0700 permissions + // This of course doesn't guarantee that this is the right directory for authorized_keys + // but at least if it's supposed to be this directory and it doesn't exist and we're the + // right user it will at least be created properly. + err := os.MkdirAll(setting.SSH.RootPath, 0700) + if err != nil { + log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err) + return err + } + } + + fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile) + tmpPath := fPath + ".tmp" + t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer func() { + t.Close() + os.Remove(tmpPath) + }() + + if setting.SSH.AuthorizedPrincipalsBackup && com.IsExist(fPath) { + bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix()) + if err = com.Copy(fPath, bakPath); err != nil { + return err + } + } + + if err := regeneratePrincipalKeys(e, t); err != nil { + return err + } + + t.Close() + return os.Rename(tmpPath, fPath) +} + +// ListPrincipalKeys returns a list of principals belongs to given user. +func ListPrincipalKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { + sess := x.Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) + if listOptions.Page != 0 { + sess = listOptions.setSessionPagination(sess) + + keys := make([]*PublicKey, 0, listOptions.PageSize) + return keys, sess.Find(&keys) + } + + keys := make([]*PublicKey, 0, 5) + return keys, sess.Find(&keys) +} + +// RegeneratePrincipalKeys regenerates the authorized_principals file +func RegeneratePrincipalKeys(t io.StringWriter) error { + return regeneratePrincipalKeys(x, t) +} + +func regeneratePrincipalKeys(e Engine, t io.StringWriter) error { + if err := e.Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) { + _, err = t.WriteString((bean.(*PublicKey)).AuthorizedString()) + return err + }); err != nil { + return err + } + + fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile) + if com.IsExist(fPath) { + f, err := os.Open(fPath) + if err != nil { + return err + } + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, tplCommentPrefix) { + scanner.Scan() + continue + } + _, err = t.WriteString(line + "\n") + if err != nil { + f.Close() + return err + } + } + f.Close() + } + return nil +} diff --git a/models/ssh_key_test.go b/models/ssh_key_test.go index 95cd4eeb1a2f..282c26e736a7 100644 --- a/models/ssh_key_test.go +++ b/models/ssh_key_test.go @@ -57,6 +57,8 @@ func Test_SSHParsePublicKey(t *testing.T) { } func Test_CheckPublicKeyString(t *testing.T) { + oldValue := setting.SSH.MinimumKeySizeCheck + setting.SSH.MinimumKeySizeCheck = false for _, test := range []struct { content string }{ @@ -131,7 +133,7 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf _, err := CheckPublicKeyString(test.content) assert.NoError(t, err) } - + setting.SSH.MinimumKeySizeCheck = oldValue for _, invalidKeys := range []struct { content string }{ diff --git a/models/star.go b/models/star.go index 4e84a6e4d570..2d9496caf504 100644 --- a/models/star.go +++ b/models/star.go @@ -4,11 +4,16 @@ package models +import ( + "code.gitea.io/gitea/modules/timeutil" +) + // Star represents a starred repo by an user. type Star struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // StarRepo or unstar repository. @@ -39,7 +44,7 @@ func StarRepo(userID, repoID int64, star bool) error { return nil } - if _, err := sess.Delete(&Star{0, userID, repoID}); err != nil { + if _, err := sess.Delete(&Star{UID: userID, RepoID: repoID}); err != nil { return err } if _, err := sess.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoID); err != nil { @@ -59,7 +64,7 @@ func IsStaring(userID, repoID int64) bool { } func isStaring(e Engine, userID, repoID int64) bool { - has, _ := e.Get(&Star{0, userID, repoID}) + has, _ := e.Get(&Star{UID: userID, RepoID: repoID}) return has } diff --git a/models/task.go b/models/task.go index f4fce058c056..43cb2d4d9a63 100644 --- a/models/task.go +++ b/models/task.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" + migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -101,9 +102,9 @@ func (task *Task) UpdateCols(cols ...string) error { } // MigrateConfig returns task config when migrate repository -func (task *Task) MigrateConfig() (*structs.MigrateRepoOption, error) { +func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) { if task.Type == structs.TaskTypeMigrateRepo { - var opts structs.MigrateRepoOption + var opts migration.MigrateOptions err := json.Unmarshal([]byte(task.PayloadContent), &opts) if err != nil { return nil, err diff --git a/models/token.go b/models/token.go index a18f15f32523..1245098df0bb 100644 --- a/models/token.go +++ b/models/token.go @@ -82,16 +82,27 @@ func AccessTokenByNameExists(token *AccessToken) (bool, error) { return x.Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() } +// ListAccessTokensOptions contain filter options +type ListAccessTokensOptions struct { + ListOptions + Name string + UserID int64 +} + // ListAccessTokens returns a list of access tokens belongs to given user. -func ListAccessTokens(uid int64, listOptions ListOptions) ([]*AccessToken, error) { - sess := x. - Where("uid=?", uid). - Desc("id") +func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { + sess := x.Where("uid=?", opts.UserID) + + if len(opts.Name) != 0 { + sess = sess.Where("name=?", opts.Name) + } + + sess = sess.Desc("id") - if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + if opts.Page != 0 { + sess = opts.setSessionPagination(sess) - tokens := make([]*AccessToken, 0, listOptions.PageSize) + tokens := make([]*AccessToken, 0, opts.PageSize) return tokens, sess.Find(&tokens) } diff --git a/models/token_test.go b/models/token_test.go index 572a5de609d6..23d902adbc67 100644 --- a/models/token_test.go +++ b/models/token_test.go @@ -83,7 +83,7 @@ func TestGetAccessTokenBySHA(t *testing.T) { func TestListAccessTokens(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - tokens, err := ListAccessTokens(1, ListOptions{}) + tokens, err := ListAccessTokens(ListAccessTokensOptions{UserID: 1}) assert.NoError(t, err) if assert.Len(t, tokens, 2) { assert.Equal(t, int64(1), tokens[0].UID) @@ -92,14 +92,14 @@ func TestListAccessTokens(t *testing.T) { assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B") } - tokens, err = ListAccessTokens(2, ListOptions{}) + tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 2}) assert.NoError(t, err) if assert.Len(t, tokens, 1) { assert.Equal(t, int64(2), tokens[0].UID) assert.Equal(t, "Token A", tokens[0].Name) } - tokens, err = ListAccessTokens(100, ListOptions{}) + tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 100}) assert.NoError(t, err) assert.Empty(t, tokens) } diff --git a/models/topic.go b/models/topic.go index 4a5bffa08a5c..0075c1702de4 100644 --- a/models/topic.go +++ b/models/topic.go @@ -25,7 +25,7 @@ var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) // Topic represents a topic of repositories type Topic struct { - ID int64 + ID int64 `xorm:"pk autoincr"` Name string `xorm:"UNIQUE VARCHAR(25)"` RepoCount int CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` @@ -34,8 +34,8 @@ type Topic struct { // RepoTopic represents associated repositories and topics type RepoTopic struct { - RepoID int64 `xorm:"UNIQUE(s)"` - TopicID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"pk"` + TopicID int64 `xorm:"pk"` } // ErrTopicNotExist represents an error that a topic is not exist diff --git a/models/twofactor.go b/models/twofactor.go index 888c910b942c..a84da8cdb52b 100644 --- a/models/twofactor.go +++ b/models/twofactor.go @@ -5,18 +5,14 @@ package models import ( - "crypto/aes" - "crypto/cipher" "crypto/md5" - "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" - "errors" "fmt" - "io" "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -67,8 +63,8 @@ func (t *TwoFactor) getEncryptionKey() []byte { } // SetSecret sets the 2FA secret. -func (t *TwoFactor) SetSecret(secret string) error { - secretBytes, err := aesEncrypt(t.getEncryptionKey(), []byte(secret)) +func (t *TwoFactor) SetSecret(secretString string) error { + secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString)) if err != nil { return err } @@ -82,51 +78,14 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) { if err != nil { return false, err } - secret, err := aesDecrypt(t.getEncryptionKey(), decodedStoredSecret) + secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret) if err != nil { return false, err } - secretStr := string(secret) + secretStr := string(secretBytes) return totp.Validate(passcode, secretStr), nil } -// aesEncrypt encrypts text and given key with AES. -func aesEncrypt(key, text []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - b := base64.StdEncoding.EncodeToString(text) - ciphertext := make([]byte, aes.BlockSize+len(b)) - iv := ciphertext[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return nil, err - } - cfb := cipher.NewCFBEncrypter(block, iv) - cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) - return ciphertext, nil -} - -// aesDecrypt decrypts text and given key with AES. -func aesDecrypt(key, text []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - if len(text) < aes.BlockSize { - return nil, errors.New("ciphertext too short") - } - iv := text[:aes.BlockSize] - text = text[aes.BlockSize:] - cfb := cipher.NewCFBDecrypter(block, iv) - cfb.XORKeyStream(text, text) - data, err := base64.StdEncoding.DecodeString(string(text)) - if err != nil { - return nil, err - } - return data, nil -} - // NewTwoFactor creates a new two-factor authentication token. func NewTwoFactor(t *TwoFactor) error { _, err := x.Insert(t) diff --git a/models/unit_tests.go b/models/unit_tests.go index 11b7708ffd87..7254cbf66b5c 100644 --- a/models/unit_tests.go +++ b/models/unit_tests.go @@ -67,8 +67,14 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { if err != nil { fatalTestError("url.Parse: %v\n", err) } + setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") + + setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") + + setting.Avatar.Storage.Path = filepath.Join(setting.AppDataPath, "avatars") + + setting.RepoAvatar.Storage.Path = filepath.Join(setting.AppDataPath, "repo-avatars") - setting.Attachment.Path = filepath.Join(setting.AppDataPath, "attachments") if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } diff --git a/models/user.go b/models/user.go index 1c1745393041..2e38502c5b0d 100644 --- a/models/user.go +++ b/models/user.go @@ -8,31 +8,27 @@ package models import ( "container/list" "context" - "crypto/md5" "crypto/sha256" "crypto/subtle" "encoding/hex" "errors" "fmt" _ "image/jpeg" // Needed for jpeg support - "image/png" "os" "path/filepath" "regexp" - "strconv" "strings" "time" "unicode/utf8" - "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/structs" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -105,7 +101,7 @@ type User struct { KeepEmailPrivate bool EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` Passwd string `xorm:"NOT NULL"` - PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` // MustChangePassword is an attribute that determines if a user // is to change his/her password after registration. @@ -238,22 +234,10 @@ func (u *User) GetEmail() string { return u.Email } -// APIFormat converts a User to api.User -func (u *User) APIFormat() *api.User { - if u == nil { - return nil - } - return &api.User{ - ID: u.ID, - UserName: u.Name, - FullName: u.FullName, - Email: u.GetEmail(), - AvatarURL: u.AvatarLink(), - Language: u.Language, - IsAdmin: u.IsAdmin, - LastLogin: u.LastLoginUnix.AsTime(), - Created: u.CreatedUnix.AsTime(), - } +// GetAllUsers returns a slice of all users found in DB. +func GetAllUsers() ([]*User, error) { + users := make([]*User, 0) + return users, x.OrderBy("id").Find(&users) } // IsLocal returns true if user login type is LoginPlain. @@ -347,104 +331,6 @@ func (u *User) GenerateActivateCode() string { return u.GenerateEmailActivateCode(u.Email) } -// CustomAvatarPath returns user custom avatar file path. -func (u *User) CustomAvatarPath() string { - return filepath.Join(setting.AvatarUploadPath, u.Avatar) -} - -// GenerateRandomAvatar generates a random avatar for user. -func (u *User) GenerateRandomAvatar() error { - return u.generateRandomAvatar(x) -} - -func (u *User) generateRandomAvatar(e Engine) error { - seed := u.Email - if len(seed) == 0 { - seed = u.Name - } - - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %v", err) - } - // NOTICE for random avatar, it still uses id as avatar name, but custom avatar use md5 - // since random image is not a user's photo, there is no security for enumable - if u.Avatar == "" { - u.Avatar = fmt.Sprintf("%d", u.ID) - } - if err = os.MkdirAll(filepath.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil { - return fmt.Errorf("MkdirAll: %v", err) - } - fw, err := os.Create(u.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("Create: %v", err) - } - defer fw.Close() - - if _, err := e.ID(u.ID).Cols("avatar").Update(u); err != nil { - return err - } - - if err = png.Encode(fw, img); err != nil { - return fmt.Errorf("Encode: %v", err) - } - - log.Info("New random avatar created: %d", u.ID) - return nil -} - -// SizedRelAvatarLink returns a link to the user's avatar via -// the local explore page. Function returns immediately. -// When applicable, the link is for an avatar of the indicated size (in pixels). -func (u *User) SizedRelAvatarLink(size int) string { - return strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + u.Name + "/" + strconv.Itoa(size) -} - -// RealSizedAvatarLink returns a link to the user's avatar. When -// applicable, the link is for an avatar of the indicated size (in pixels). -// -// This function make take time to return when federated avatars -// are in use, due to a DNS lookup need -// -func (u *User) RealSizedAvatarLink(size int) string { - if u.ID == -1 { - return base.DefaultAvatarLink() - } - - switch { - case u.UseCustomAvatar: - if !com.IsFile(u.CustomAvatarPath()) { - return base.DefaultAvatarLink() - } - return setting.AppSubURL + "/avatars/" + u.Avatar - case setting.DisableGravatar, setting.OfflineMode: - if !com.IsFile(u.CustomAvatarPath()) { - if err := u.GenerateRandomAvatar(); err != nil { - log.Error("GenerateRandomAvatar: %v", err) - } - } - - return setting.AppSubURL + "/avatars/" + u.Avatar - } - return base.SizedAvatarLink(u.AvatarEmail, size) -} - -// RelAvatarLink returns a relative link to the user's avatar. The link -// may either be a sub-URL to this site, or a full URL to an external avatar -// service. -func (u *User) RelAvatarLink() string { - return u.SizedRelAvatarLink(base.DefaultAvatarSize) -} - -// AvatarLink returns user avatar absolute link. -func (u *User) AvatarLink() string { - link := u.RelAvatarLink() - if link[0] == '/' && link[1] != '/' { - return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] - } - return link -} - // GetFollowers returns range of user's followers. func (u *User) GetFollowers(listOptions ListOptions) ([]*User, error) { sess := x. @@ -537,64 +423,6 @@ func (u *User) IsPasswordSet() bool { return !u.ValidatePassword("") } -// UploadAvatar saves custom avatar for user. -// FIXME: split uploads to different subdirs in case we have massive users. -func (u *User) UploadAvatar(data []byte) error { - m, err := avatar.Prepare(data) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - u.UseCustomAvatar = true - // Different users can upload same image as avatar - // If we prefix it with u.ID, it will be separated - // Otherwise, if any of the users delete his avatar - // Other users will lose their avatars too. - u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) - if err = updateUser(sess, u); err != nil { - return fmt.Errorf("updateUser: %v", err) - } - - if err := os.MkdirAll(setting.AvatarUploadPath, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err) - } - - fw, err := os.Create(u.CustomAvatarPath()) - if err != nil { - return fmt.Errorf("Create: %v", err) - } - defer fw.Close() - - if err = png.Encode(fw, *m); err != nil { - return fmt.Errorf("Encode: %v", err) - } - - return sess.Commit() -} - -// DeleteAvatar deletes the user's custom avatar. -func (u *User) DeleteAvatar() error { - log.Trace("DeleteAvatar[%d]: %s", u.ID, u.CustomAvatarPath()) - if len(u.Avatar) > 0 { - if err := util.Remove(u.CustomAvatarPath()); err != nil { - return fmt.Errorf("Failed to remove %s: %v", u.CustomAvatarPath(), err) - } - } - - u.UseCustomAvatar = false - u.Avatar = "" - if _, err := x.ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { - return fmt.Errorf("UpdateUser: %v", err) - } - return nil -} - // IsOrganization returns true if user is actually a organization. func (u *User) IsOrganization() bool { return u.Type == UserTypeOrganization @@ -646,8 +474,8 @@ func (u *User) GetOrganizationCount() (int64, error) { } // GetRepositories returns repositories that user owns, including private repositories. -func (u *User) GetRepositories(listOpts ListOptions) (err error) { - u.Repos, _, err = GetUserRepositories(&SearchRepoOptions{Actor: u, Private: true, ListOptions: listOpts}) +func (u *User) GetRepositories(listOpts ListOptions, names ...string) (err error) { + u.Repos, _, err = GetUserRepositories(&SearchRepoOptions{Actor: u, Private: true, ListOptions: listOpts, LowerNames: names}) return err } @@ -1254,6 +1082,10 @@ func deleteUser(e *xorm.Session, u *User) error { if err != nil { return err } + err = rewriteAllPrincipalKeys(e) + if err != nil { + return err + } // ***** END: PublicKey ***** // ***** START: GPGPublicKey ***** @@ -1281,17 +1113,14 @@ func deleteUser(e *xorm.Session, u *User) error { // Note: There are something just cannot be roll back, // so just keep error logs of those operations. path := UserPath(u.Name) - if err := util.RemoveAll(path); err != nil { return fmt.Errorf("Failed to RemoveAll %s: %v", path, err) } if len(u.Avatar) > 0 { - avatarPath := u.CustomAvatarPath() - if com.IsExist(avatarPath) { - if err := util.Remove(avatarPath); err != nil { - return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) - } + avatarPath := u.CustomAvatarRelativePath() + if err := storage.Avatars.Delete(avatarPath); err != nil { + return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) } } @@ -1419,11 +1248,21 @@ func getUserEmailsByNames(e Engine, names []string) []string { } // GetMaileableUsersByIDs gets users from ids, but only if they can receive mails -func GetMaileableUsersByIDs(ids []int64) ([]*User, error) { +func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) { if len(ids) == 0 { return nil, nil } ous := make([]*User, 0, len(ids)) + + if isMention { + return ous, x.In("id", ids). + Where("`type` = ?", UserTypeIndividual). + And("`prohibit_login` = ?", false). + And("`is_active` = ?", true). + And("`email_notifications_preference` IN ( ?, ?)", EmailNotificationsEnabled, EmailNotificationsOnMention). + Find(&ous) + } + return ous, x.In("id", ids). Where("`type` = ?", UserTypeIndividual). And("`prohibit_login` = ?", false). @@ -2020,3 +1859,25 @@ func SyncExternalUsers(ctx context.Context, updateExisting bool) error { } return nil } + +// IterateUser iterate users +func IterateUser(f func(user *User) error) error { + var start int + var batchSize = setting.Database.IterateBufferSize + for { + var users = make([]*User, 0, batchSize) + if err := x.Limit(batchSize, start).Find(&users); err != nil { + return err + } + if len(users) == 0 { + return nil + } + start += len(users) + + for _, user := range users { + if err := f(user); err != nil { + return err + } + } + } +} diff --git a/models/user_avatar.go b/models/user_avatar.go new file mode 100644 index 000000000000..0a03ca770759 --- /dev/null +++ b/models/user_avatar.go @@ -0,0 +1,169 @@ +// 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 models + +import ( + "crypto/md5" + "fmt" + "image/png" + "io" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" +) + +// CustomAvatarRelativePath returns user custom avatar relative path. +func (u *User) CustomAvatarRelativePath() string { + return u.Avatar +} + +// GenerateRandomAvatar generates a random avatar for user. +func (u *User) GenerateRandomAvatar() error { + return u.generateRandomAvatar(x) +} + +func (u *User) generateRandomAvatar(e Engine) error { + seed := u.Email + if len(seed) == 0 { + seed = u.Name + } + + img, err := avatar.RandomImage([]byte(seed)) + if err != nil { + return fmt.Errorf("RandomImage: %v", err) + } + // NOTICE for random avatar, it still uses id as avatar name, but custom avatar use md5 + // since random image is not a user's photo, there is no security for enumable + if u.Avatar == "" { + u.Avatar = fmt.Sprintf("%d", u.ID) + } + + if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { + if err := png.Encode(w, img); err != nil { + log.Error("Encode: %v", err) + } + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", u.CustomAvatarRelativePath(), err) + } + + if _, err := e.ID(u.ID).Cols("avatar").Update(u); err != nil { + return err + } + + log.Info("New random avatar created: %d", u.ID) + return nil +} + +// SizedRelAvatarLink returns a link to the user's avatar via +// the local explore page. Function returns immediately. +// When applicable, the link is for an avatar of the indicated size (in pixels). +func (u *User) SizedRelAvatarLink(size int) string { + return strings.TrimSuffix(setting.AppSubURL, "/") + "/user/avatar/" + u.Name + "/" + strconv.Itoa(size) +} + +// RealSizedAvatarLink returns a link to the user's avatar. When +// applicable, the link is for an avatar of the indicated size (in pixels). +// +// This function make take time to return when federated avatars +// are in use, due to a DNS lookup need +// +func (u *User) RealSizedAvatarLink(size int) string { + if u.ID == -1 { + return base.DefaultAvatarLink() + } + + switch { + case u.UseCustomAvatar: + if u.Avatar == "" { + return base.DefaultAvatarLink() + } + return setting.AppSubURL + "/avatars/" + u.Avatar + case setting.DisableGravatar, setting.OfflineMode: + if u.Avatar == "" { + if err := u.GenerateRandomAvatar(); err != nil { + log.Error("GenerateRandomAvatar: %v", err) + } + } + + return setting.AppSubURL + "/avatars/" + u.Avatar + } + return base.SizedAvatarLink(u.AvatarEmail, size) +} + +// RelAvatarLink returns a relative link to the user's avatar. The link +// may either be a sub-URL to this site, or a full URL to an external avatar +// service. +func (u *User) RelAvatarLink() string { + return u.SizedRelAvatarLink(base.DefaultAvatarSize) +} + +// AvatarLink returns user avatar absolute link. +func (u *User) AvatarLink() string { + link := u.RelAvatarLink() + if link[0] == '/' && link[1] != '/' { + return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:] + } + return link +} + +// UploadAvatar saves custom avatar for user. +// FIXME: split uploads to different subdirs in case we have massive users. +func (u *User) UploadAvatar(data []byte) error { + m, err := avatar.Prepare(data) + if err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + u.UseCustomAvatar = true + // Different users can upload same image as avatar + // If we prefix it with u.ID, it will be separated + // Otherwise, if any of the users delete his avatar + // Other users will lose their avatars too. + u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) + if err = updateUser(sess, u); err != nil { + return fmt.Errorf("updateUser: %v", err) + } + + if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { + if err := png.Encode(w, *m); err != nil { + log.Error("Encode: %v", err) + } + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", u.CustomAvatarRelativePath(), err) + } + + return sess.Commit() +} + +// DeleteAvatar deletes the user's custom avatar. +func (u *User) DeleteAvatar() error { + aPath := u.CustomAvatarRelativePath() + log.Trace("DeleteAvatar[%d]: %s", u.ID, aPath) + if len(u.Avatar) > 0 { + if err := storage.Avatars.Delete(aPath); err != nil { + return fmt.Errorf("Failed to remove %s: %v", aPath, err) + } + } + + u.UseCustomAvatar = false + u.Avatar = "" + if _, err := x.ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { + return fmt.Errorf("UpdateUser: %v", err) + } + return nil +} diff --git a/models/user_follow.go b/models/user_follow.go index 4bde71cb91c6..8321d950774d 100644 --- a/models/user_follow.go +++ b/models/user_follow.go @@ -4,11 +4,16 @@ package models +import ( + "code.gitea.io/gitea/modules/timeutil" +) + // Follow represents relations of user and his/her followers. type Follow struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(follow)"` - FollowID int64 `xorm:"UNIQUE(follow)"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(follow)"` + FollowID int64 `xorm:"UNIQUE(follow)"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // IsFollowing returns true if user is following followID. diff --git a/models/user_heatmap_test.go b/models/user_heatmap_test.go index c2825d9ff0a2..c9d33db29bcf 100644 --- a/models/user_heatmap_test.go +++ b/models/user_heatmap_test.go @@ -17,7 +17,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { CountResult int JSONResult string }{ - {2, 1, `[{"timestamp":1571616000,"contributions":1}]`}, + {2, 1, `[{"timestamp":1603152000,"contributions":1}]`}, {3, 0, `[]`}, } // Prepare diff --git a/models/user_test.go b/models/user_test.go index 02b1893c43de..7a6f5aa5122b 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -78,23 +78,6 @@ func TestGetUserEmailsByNames(t *testing.T) { assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user7"})) } -func TestUser_APIFormat(t *testing.T) { - - user, err := GetUserByID(1) - assert.NoError(t, err) - assert.True(t, user.IsAdmin) - - apiUser := user.APIFormat() - assert.True(t, apiUser.IsAdmin) - - user, err = GetUserByID(2) - assert.NoError(t, err) - assert.False(t, user.IsAdmin) - - apiUser = user.APIFormat() - assert.False(t, apiUser.IsAdmin) -} - func TestCanCreateOrganization(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) @@ -239,7 +222,7 @@ func TestHashPasswordDeterministic(t *testing.T) { b := make([]byte, 16) rand.Read(b) u := &User{Salt: string(b)} - algos := []string{"pbkdf2", "argon2", "scrypt", "bcrypt"} + algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"} for j := 0; j < len(algos); j++ { u.PasswdHashAlgo = algos[j] for i := 0; i < 50; i++ { @@ -389,3 +372,20 @@ func TestGetUserIDsByNames(t *testing.T) { assert.Error(t, err) assert.Equal(t, []int64(nil), IDs) } + +func TestGetMaileableUsersByIDs(t *testing.T) { + results, err := GetMaileableUsersByIDs([]int64{1, 4}, false) + assert.NoError(t, err) + assert.Equal(t, 1, len(results)) + if len(results) > 1 { + assert.Equal(t, results[0].ID, 1) + } + + results, err = GetMaileableUsersByIDs([]int64{1, 4}, true) + assert.NoError(t, err) + assert.Equal(t, 2, len(results)) + if len(results) > 2 { + assert.Equal(t, results[0].ID, 1) + assert.Equal(t, results[1].ID, 4) + } +} diff --git a/models/userlist.go b/models/userlist.go index 7e6cab50baf1..a2a424848227 100644 --- a/models/userlist.go +++ b/models/userlist.go @@ -8,7 +8,6 @@ import ( "fmt" "code.gitea.io/gitea/modules/log" - api "code.gitea.io/gitea/modules/structs" ) //UserList is a list of user. @@ -94,12 +93,3 @@ func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error } return tokenMaps, nil } - -//APIFormat return list of users in api format -func (users UserList) APIFormat() []*api.User { - result := make([]*api.User, 0, len(users)) - for _, u := range users { - result = append(result, u.APIFormat()) - } - return result -} diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 7fc62607e521..1d02c7acf364 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -30,6 +30,11 @@ type AuthenticationForm struct { SearchPageSize int Filter string AdminFilter string + GroupsEnabled bool + GroupDN string + GroupFilter string + GroupMemberUID string + UserUID string RestrictedFilter string AllowDeactivateAll bool IsActive bool diff --git a/modules/auth/ldap/README.md b/modules/auth/ldap/README.md index 4f7961da6b0d..76841f44aefd 100644 --- a/modules/auth/ldap/README.md +++ b/modules/auth/ldap/README.md @@ -103,3 +103,21 @@ share the following fields: matching parameter will be substituted with the user's username. * Example: (&(objectClass=posixAccount)(cn=%s)) * Example: (&(objectClass=posixAccount)(uid=%s)) + +**Verify group membership in LDAP** uses the following fields: + +* Group Search Base (optional) + * The LDAP DN used for groups. + * Example: ou=group,dc=mydomain,dc=com + +* Group Name Filter (optional) + * An LDAP filter declaring how to find valid groups in the above DN. + * Example: (|(cn=gitea_users)(cn=admins)) + +* User Attribute in Group (optional) + * Which user LDAP attribute is listed in the group. + * Example: uid + +* Group Attribute for User (optional) + * Which group LDAP attribute contains an array above user attribute names. + * Example: memberUid diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 66676f2829d5..6c557de018c4 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.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. @@ -13,7 +14,7 @@ import ( "code.gitea.io/gitea/modules/log" - ldap "gopkg.in/ldap.v3" + "github.com/go-ldap/ldap/v3" ) // SecurityProtocol protocol type @@ -49,6 +50,11 @@ type Source struct { RestrictedFilter string // Query filter to check if user is restricted Enabled bool // if this source is disabled AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source + GroupsEnabled bool // if the group checking is enabled + GroupDN string // Group Search Base + GroupFilter string // Group Name Filter + GroupMemberUID string // Group Attribute containing array of UserUID + UserUID string // User Attribute listed in Group } // SearchResult : user data @@ -84,6 +90,28 @@ func (ls *Source) sanitizedUserDN(username string) (string, bool) { return fmt.Sprintf(ls.UserDN, username), true } +func (ls *Source) sanitizedGroupFilter(group string) (string, bool) { + // See http://tools.ietf.org/search/rfc4515 + badCharacters := "\x00*\\" + if strings.ContainsAny(group, badCharacters) { + log.Trace("Group filter invalid query characters: %s", group) + return "", false + } + + return group, true +} + +func (ls *Source) sanitizedGroupDN(groupDn string) (string, bool) { + // See http://tools.ietf.org/search/rfc4514: "special characters" + badCharacters := "\x00()*\\'\"#+;<>" + if strings.ContainsAny(groupDn, badCharacters) || strings.HasPrefix(groupDn, " ") || strings.HasSuffix(groupDn, " ") { + log.Trace("Group DN contains invalid query characters: %s", groupDn) + return "", false + } + + return groupDn, true +} + func (ls *Source) findUserDN(l *ldap.Conn, name string) (string, bool) { log.Trace("Search for LDAP user: %s", name) @@ -279,11 +307,14 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} + if len(strings.TrimSpace(ls.UserUID)) > 0 { + attribs = append(attribs, ls.UserUID) + } if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.UserUID, userFilter, userDN) search := ldap.NewSearchRequest( userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) @@ -308,6 +339,51 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail) + uid := sr.Entries[0].GetAttributeValue(ls.UserUID) + + // Check group membership + if ls.GroupsEnabled { + groupFilter, ok := ls.sanitizedGroupFilter(ls.GroupFilter) + if !ok { + return nil + } + groupDN, ok := ls.sanitizedGroupDN(ls.GroupDN) + if !ok { + return nil + } + + log.Trace("Fetching groups '%v' with filter '%s' and base '%s'", ls.GroupMemberUID, groupFilter, groupDN) + groupSearch := ldap.NewSearchRequest( + groupDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, groupFilter, + []string{ls.GroupMemberUID}, + nil) + + srg, err := l.Search(groupSearch) + if err != nil { + log.Error("LDAP group search failed: %v", err) + return nil + } else if len(srg.Entries) < 1 { + log.Error("LDAP group search failed: 0 entries") + return nil + } + + isMember := false + Entries: + for _, group := range srg.Entries { + for _, member := range group.GetAttributeValues(ls.GroupMemberUID) { + if (ls.UserUID == "dn" && member == sr.Entries[0].DN) || member == uid { + isMember = true + break Entries + } + } + } + + if !isMember { + log.Error("LDAP group membership test failed") + return nil + } + } + if isAttributeSSHPublicKeySet { sshPublicKey = sr.Entries[0].GetAttributeValues(ls.AttributeSSHPublicKey) } diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index 78be3954ed5d..2c982e1dcaa4 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -6,6 +6,7 @@ package oauth2 import ( "net/http" + "net/url" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -119,7 +120,7 @@ func RemoveProvider(providerName string) { // used to create different types of goth providers func createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) (goth.Provider, error) { - callbackURL := setting.AppURL + "user/oauth2/" + providerName + "/callback" + callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback" var provider goth.Provider var err error diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 9d27e9495c75..f459c15f450d 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/utils" "gitea.com/macaron/binding" @@ -37,6 +38,7 @@ type CreateRepoForm struct { IssueLabels string License string Readme string + Template bool RepoTemplate int64 GitContent bool @@ -45,6 +47,7 @@ type CreateRepoForm struct { Webhooks bool Avatar bool Labels bool + TrustModel string } // Validate validates the fields @@ -53,11 +56,14 @@ func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bin } // MigrateRepoForm form for migrating repository +// this is used to interact with web ui type MigrateRepoForm struct { // required: true - CloneAddr string `json:"clone_addr" binding:"Required"` - AuthUsername string `json:"auth_username"` - AuthPassword string `json:"auth_password"` + CloneAddr string `json:"clone_addr" binding:"Required"` + Service structs.GitServiceType `json:"service"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` // required: true UID int64 `json:"uid" binding:"Required"` // required: true @@ -82,9 +88,8 @@ func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bi // and returns composed URL with needed username and password. // It also checks if given user has permission when remote address // is actually a local path. -func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { - remoteAddr := strings.TrimSpace(f.CloneAddr) - +func ParseRemoteAddr(remoteAddr, authUsername, authPassword string, user *models.User) (string, error) { + remoteAddr = strings.TrimSpace(remoteAddr) // Remote address can be HTTP/HTTPS/Git URL or local path. if strings.HasPrefix(remoteAddr, "http://") || strings.HasPrefix(remoteAddr, "https://") || @@ -93,8 +98,8 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { if err != nil { return "", models.ErrInvalidCloneAddr{IsURLError: true} } - if len(f.AuthUsername)+len(f.AuthPassword) > 0 { - u.User = url.UserPassword(f.AuthUsername, f.AuthPassword) + if len(authUsername)+len(authPassword) > 0 { + u.User = url.UserPassword(authUsername, authPassword) } remoteAddr = u.String() } else if !user.CanImportLocal() { @@ -142,6 +147,9 @@ type RepoSettingForm struct { EnableIssueDependencies bool IsArchived bool + // Signing Settings + TrustModel string + // Admin settings EnableHealthCheck bool EnableCloseIssuesViaCommitInAnyBranch bool diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 999d4cd74df6..e657f78e6de4 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -83,6 +83,7 @@ type RegisterForm struct { Password string `binding:"MaxSize(255)"` Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` + HcaptchaResponse string `form:"h-captcha-response"` } // Validate validates the fields diff --git a/modules/auth/user_form_auth_openid.go b/modules/auth/user_form_auth_openid.go index c1d19b9bb41d..841dbd840afc 100644 --- a/modules/auth/user_form_auth_openid.go +++ b/modules/auth/user_form_auth_openid.go @@ -25,6 +25,7 @@ type SignUpOpenIDForm struct { UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` Email string `binding:"Required;Email;MaxSize(254)"` GRecaptchaResponse string `form:"g-recaptcha-response"` + HcaptchaResponse string `form:"h-captcha-response"` } // Validate validates the fields diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index cf3da6df5ed9..44b56c26ce5d 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -9,6 +9,7 @@ import ( "fmt" "image" "image/color/palette" + // Enable PNG support: _ "image/png" "math/rand" @@ -57,11 +58,11 @@ func Prepare(data []byte) (*image.Image, error) { if err != nil { return nil, fmt.Errorf("DecodeConfig: %v", err) } - if imgCfg.Width > setting.AvatarMaxWidth { - return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth) + if imgCfg.Width > setting.Avatar.MaxWidth { + return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.Avatar.MaxWidth) } - if imgCfg.Height > setting.AvatarMaxHeight { - return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight) + if imgCfg.Height > setting.Avatar.MaxHeight { + return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight) } img, _, err := image.Decode(bytes.NewReader(data)) @@ -89,6 +90,6 @@ func Prepare(data []byte) (*image.Image, error) { } } - img = resize.Resize(AvatarSize, AvatarSize, img, resize.NearestNeighbor) + img = resize.Resize(AvatarSize, AvatarSize, img, resize.Bilinear) return &img, nil } diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index 662d50faddb9..85356056529a 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -22,8 +22,8 @@ func Test_RandomImage(t *testing.T) { } func Test_PrepareWithPNG(t *testing.T) { - setting.AvatarMaxWidth = 4096 - setting.AvatarMaxHeight = 4096 + setting.Avatar.MaxWidth = 4096 + setting.Avatar.MaxHeight = 4096 data, err := ioutil.ReadFile("testdata/avatar.png") assert.NoError(t, err) @@ -36,8 +36,8 @@ func Test_PrepareWithPNG(t *testing.T) { } func Test_PrepareWithJPEG(t *testing.T) { - setting.AvatarMaxWidth = 4096 - setting.AvatarMaxHeight = 4096 + setting.Avatar.MaxWidth = 4096 + setting.Avatar.MaxHeight = 4096 data, err := ioutil.ReadFile("testdata/avatar.jpeg") assert.NoError(t, err) @@ -50,15 +50,15 @@ func Test_PrepareWithJPEG(t *testing.T) { } func Test_PrepareWithInvalidImage(t *testing.T) { - setting.AvatarMaxWidth = 5 - setting.AvatarMaxHeight = 5 + setting.Avatar.MaxWidth = 5 + setting.Avatar.MaxHeight = 5 _, err := Prepare([]byte{}) assert.EqualError(t, err, "DecodeConfig: image: unknown format") } func Test_PrepareWithInvalidImageSize(t *testing.T) { - setting.AvatarMaxWidth = 5 - setting.AvatarMaxHeight = 5 + setting.Avatar.MaxWidth = 5 + setting.Avatar.MaxHeight = 5 data, err := ioutil.ReadFile("testdata/avatar.png") assert.NoError(t, err) diff --git a/modules/base/tool.go b/modules/base/tool.go index e1a1b661beca..a21fd9b0f404 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -420,3 +420,27 @@ func SetupGiteaRoot() string { } return giteaRoot } + +// FormatNumberSI format a number +func FormatNumberSI(data interface{}) string { + var num int64 + if num1, ok := data.(int64); ok { + num = num1 + } else if num1, ok := data.(int); ok { + num = int64(num1) + } else { + return "" + } + + if num < 1000 { + return fmt.Sprintf("%d", num) + } else if num < 1000000 { + num2 := float32(num) / float32(1000.0) + return fmt.Sprintf("%.1fk", num2) + } else if num < 1000000000 { + num2 := float32(num) / float32(1000000.0) + return fmt.Sprintf("%.1fM", num2) + } + num2 := float32(num) / float32(1000000000.0) + return fmt.Sprintf("%.1fG", num2) +} diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index cadb851858d3..f765fd0db0e3 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -223,5 +223,13 @@ func TestIsTextFile(t *testing.T) { assert.True(t, IsTextFile([]byte("lorem ipsum"))) } +func TestFormatNumberSI(t *testing.T) { + assert.Equal(t, "125", FormatNumberSI(int(125))) + assert.Equal(t, "1.3k", FormatNumberSI(int64(1317))) + assert.Equal(t, "21.3M", FormatNumberSI(21317675)) + assert.Equal(t, "45.7G", FormatNumberSI(45721317675)) + assert.Equal(t, "", FormatNumberSI("test")) +} + // TODO: IsImageFile(), currently no idea how to test // TODO: IsPDFFile(), currently no idea how to test diff --git a/modules/cache/cache.go b/modules/cache/cache.go index 859f4a4b47d9..60865d8335db 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -13,7 +13,6 @@ import ( mc "gitea.com/macaron/cache" _ "gitea.com/macaron/cache/memcache" // memcache plugin for cache - _ "gitea.com/macaron/cache/redis" ) var ( diff --git a/vendor/gitea.com/macaron/cache/redis/redis.go b/modules/cache/cache_redis.go similarity index 63% rename from vendor/gitea.com/macaron/cache/redis/redis.go rename to modules/cache/cache_redis.go index 892ee28bdc95..96e865a3829d 100644 --- a/vendor/gitea.com/macaron/cache/redis/redis.go +++ b/modules/cache/cache_redis.go @@ -1,35 +1,23 @@ -// Copyright 2013 Beego Authors -// Copyright 2014 The Macaron Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. +// 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 cache import ( "fmt" - "strings" "time" - "github.com/go-redis/redis" - "github.com/unknwon/com" - "gopkg.in/ini.v1" + "code.gitea.io/gitea/modules/nosql" "gitea.com/macaron/cache" + "github.com/go-redis/redis/v7" + "github.com/unknwon/com" ) // RedisCacher represents a redis cache adapter implementation. type RedisCacher struct { - c *redis.Client + c redis.UniversalClient prefix string hsetName string occupyMode bool @@ -112,7 +100,7 @@ func (c *RedisCacher) IsExist(key string) bool { // Flush deletes all cached data. func (c *RedisCacher) Flush() error { if c.occupyMode { - return c.c.FlushDb().Err() + return c.c.FlushDB().Err() } keys, err := c.c.HKeys(c.hsetName).Result() @@ -131,46 +119,20 @@ func (c *RedisCacher) StartAndGC(opts cache.Options) error { c.hsetName = "MacaronCache" c.occupyMode = opts.OccupyMode - cfg, err := ini.Load([]byte(strings.Replace(opts.AdapterConfig, ",", "\n", -1))) - if err != nil { - return err - } + uri := nosql.ToRedisURI(opts.AdapterConfig) - opt := &redis.Options{ - Network: "tcp", - } - for k, v := range cfg.Section("").KeysHash() { + c.c = nosql.GetManager().GetRedisClient(uri.String()) + + for k, v := range uri.Query() { switch k { - case "network": - opt.Network = v - case "addr": - opt.Addr = v - case "password": - opt.Password = v - case "db": - opt.DB = com.StrTo(v).MustInt() - case "pool_size": - opt.PoolSize = com.StrTo(v).MustInt() - case "idle_timeout": - opt.IdleTimeout, err = time.ParseDuration(v + "s") - if err != nil { - return fmt.Errorf("error parsing idle timeout: %v", err) - } case "hset_name": - c.hsetName = v + c.hsetName = v[0] case "prefix": - c.prefix = v - default: - return fmt.Errorf("session/redis: unsupported option '%s'", k) + c.prefix = v[0] } } - c.c = redis.NewClient(opt) - if err = c.c.Ping().Err(); err != nil { - return err - } - - return nil + return c.c.Ping().Err() } func init() { diff --git a/modules/context/api.go b/modules/context/api.go index fdc000e4a5b3..772e1f8f50f5 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -82,7 +82,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) { if status == http.StatusInternalServerError { log.ErrorWithSkip(1, "%s: %s", title, message) - if macaron.Env == macaron.PROD { + if macaron.Env == macaron.PROD && !(ctx.User != nil && ctx.User.IsAdmin) { message = "" } } @@ -99,7 +99,7 @@ func (ctx *APIContext) InternalServerError(err error) { log.ErrorWithSkip(1, "InternalServerError: %v", err) var message string - if macaron.Env != macaron.PROD { + if macaron.Env != macaron.PROD || (ctx.User != nil && ctx.User.IsAdmin) { message = err.Error() } diff --git a/modules/context/auth.go b/modules/context/auth.go index 14dfab734486..02248384e144 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -26,26 +26,40 @@ type ToggleOptions struct { // Toggle returns toggle options as middleware func Toggle(options *ToggleOptions) macaron.Handler { return func(ctx *Context) { - // Cannot view any page before installation. - if !setting.InstallLock { - ctx.Redirect(setting.AppSubURL + "/install") - return - } + isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path) // Check prohibit login users. if ctx.IsSigned { if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { ctx.Data["Title"] = ctx.Tr("auth.active_your_account") + if isAPIPath { + ctx.JSON(403, map[string]string{ + "message": "This account is not activated.", + }) + return + } ctx.HTML(200, "user/auth/activate") return } else if !ctx.User.IsActive || ctx.User.ProhibitLogin { log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr()) ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") + if isAPIPath { + ctx.JSON(403, map[string]string{ + "message": "This account is prohibited from signing in, please contact your site administrator.", + }) + return + } ctx.HTML(200, "user/auth/prohibit_login") return } if ctx.User.MustChangePassword { + if isAPIPath { + ctx.JSON(403, map[string]string{ + "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password", + }) + return + } if ctx.Req.URL.Path != "/user/settings/change_password" { ctx.Data["Title"] = ctx.Tr("auth.must_change_password") ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" @@ -78,7 +92,7 @@ func Toggle(options *ToggleOptions) macaron.Handler { if options.SignInRequired { if !ctx.IsSigned { // Restrict API calls with error message. - if auth.IsAPIPath(ctx.Req.URL.Path) { + if isAPIPath { ctx.JSON(403, map[string]string{ "message": "Only signed in user is allowed to call APIs.", }) @@ -94,7 +108,7 @@ func Toggle(options *ToggleOptions) macaron.Handler { ctx.HTML(200, "user/auth/activate") return } - if ctx.IsSigned && auth.IsAPIPath(ctx.Req.URL.Path) && ctx.IsBasicAuth { + if ctx.IsSigned && isAPIPath && ctx.IsBasicAuth { twofa, err := models.GetTwoFactorByUID(ctx.User.ID) if err != nil { if models.IsErrTwoFactorNotEnrolled(err) { @@ -119,7 +133,7 @@ func Toggle(options *ToggleOptions) macaron.Handler { } // Redirect to log in page if auto-signin info is provided and has not signed in. - if !options.SignOutRequired && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) && + if !options.SignOutRequired && !ctx.IsSigned && !isAPIPath && len(ctx.GetCookie(setting.CookieUserName)) > 0 { if ctx.Req.URL.Path != "/user/events" { ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL) diff --git a/modules/context/repo.go b/modules/context/repo.go index 4aac0c05aa84..9723c8561969 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -16,13 +16,27 @@ import ( "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "gitea.com/macaron/macaron" "github.com/editorconfig/editorconfig-core-go/v2" "github.com/unknwon/com" ) +// IssueTemplateDirCandidates issue templates directory +var IssueTemplateDirCandidates = []string{ + "ISSUE_TEMPLATE", + "issue_template", + ".gitea/ISSUE_TEMPLATE", + ".gitea/issue_template", + ".github/ISSUE_TEMPLATE", + ".github/issue_template", + ".gitlab/ISSUE_TEMPLATE", + ".gitlab/issue_template", +} + // PullRequest contains informations to make a pull request type PullRequest struct { BaseRepo *models.Repository @@ -100,7 +114,7 @@ func (r *Repository) CanCommitToBranch(doer *models.User) (CanCommitToBranchResu requireSigned = protectedBranch.RequireSignedCommits } - sign, keyID, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) + sign, keyID, _, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) canCommit := r.CanEnableEditor() && userCanPush if requireSigned { @@ -208,11 +222,7 @@ func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) { return nil, err } defer reader.Close() - data, err := ioutil.ReadAll(reader) - if err != nil { - return nil, err - } - return editorconfig.ParseBytes(data) + return editorconfig.Parse(reader) } // RetrieveBaseRepo retrieves base repository @@ -821,3 +831,60 @@ func UnitTypes() macaron.Handler { ctx.Data["UnitTypeProjects"] = models.UnitTypeProjects } } + +// IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch +func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate { + var issueTemplates []api.IssueTemplate + if ctx.Repo.Commit == nil { + var err error + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) + if err != nil { + return issueTemplates + } + } + + for _, dirName := range IssueTemplateDirCandidates { + tree, err := ctx.Repo.Commit.SubTree(dirName) + if err != nil { + continue + } + entries, err := tree.ListEntries() + if err != nil { + return issueTemplates + } + for _, entry := range entries { + if strings.HasSuffix(entry.Name(), ".md") { + if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize { + log.Debug("Issue template is too large: %s", entry.Name()) + continue + } + r, err := entry.Blob().DataAsync() + if err != nil { + log.Debug("DataAsync: %v", err) + continue + } + defer r.Close() + data, err := ioutil.ReadAll(r) + if err != nil { + log.Debug("ReadAll: %v", err) + continue + } + var it api.IssueTemplate + content, err := markdown.ExtractMetadata(string(data), &it) + if err != nil { + log.Debug("ExtractMetadata: %v", err) + continue + } + it.Content = content + it.FileName = entry.Name() + if it.Valid() { + issueTemplates = append(issueTemplates, it) + } + } + } + if len(issueTemplates) > 0 { + return issueTemplates + } + } + return issueTemplates +} diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 94ecdd11503b..5d056c37954d 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// 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. @@ -6,12 +7,10 @@ package convert import ( "fmt" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -43,7 +42,7 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models. return &api.Branch{ Name: b.Name, - Commit: ToCommit(repo, c), + Commit: ToPayloadCommit(repo, c), Protected: false, RequiredApprovals: 0, EnableStatusCheck: false, @@ -55,7 +54,7 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models. branch := &api.Branch{ Name: b.Name, - Commit: ToCommit(repo, c), + Commit: ToPayloadCommit(repo, c), Protected: true, RequiredApprovals: bp.RequiredApprovals, EnableStatusCheck: bp.EnableStatusCheck, @@ -142,41 +141,6 @@ func ToTag(repo *models.Repository, t *git.Tag) *api.Tag { } } -// ToCommit convert a git.Commit to api.PayloadCommit -func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit { - authorUsername := "" - if author, err := models.GetUserByEmail(c.Author.Email); err == nil { - authorUsername = author.Name - } else if !models.IsErrUserNotExist(err) { - log.Error("GetUserByEmail: %v", err) - } - - committerUsername := "" - if committer, err := models.GetUserByEmail(c.Committer.Email); err == nil { - committerUsername = committer.Name - } else if !models.IsErrUserNotExist(err) { - log.Error("GetUserByEmail: %v", err) - } - - return &api.PayloadCommit{ - ID: c.ID.String(), - Message: c.Message(), - URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()), - Author: &api.PayloadUser{ - Name: c.Author.Name, - Email: c.Author.Email, - UserName: authorUsername, - }, - Committer: &api.PayloadUser{ - Name: c.Committer.Name, - Email: c.Committer.Email, - UserName: committerUsername, - }, - Timestamp: c.Author.When, - Verification: ToVerification(c), - } -} - // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification func ToVerification(c *git.Commit) *api.PayloadCommitVerification { verif := models.ParseCommitWithSignature(c) @@ -320,6 +284,10 @@ func ToOrganization(org *models.User) *api.Organization { // ToTeam convert models.Team to api.Team func ToTeam(team *models.Team) *api.Team { + if team == nil { + return nil + } + return &api.Team{ ID: team.ID, Name: team.Name, @@ -331,29 +299,6 @@ func ToTeam(team *models.Team) *api.Team { } } -// ToUser convert models.User to api.User -// signed shall only be set if requester is logged in. authed shall only be set if user is site admin or user himself -func ToUser(user *models.User, signed, authed bool) *api.User { - result := &api.User{ - UserName: user.Name, - AvatarURL: user.AvatarLink(), - FullName: markup.Sanitize(user.FullName), - Created: user.CreatedUnix.AsTime(), - } - // hide primary email if API caller is anonymous or user keep email private - if signed && (!user.KeepEmailPrivate || authed) { - result.Email = user.Email - } - // only site admin will get these information and possibly user himself - if authed { - result.ID = user.ID - result.IsAdmin = user.IsAdmin - result.LastLogin = user.LastLoginUnix.AsTime() - result.Language = user.Language - } - return result -} - // ToAnnotatedTag convert git.Tag to api.AnnotatedTag func ToAnnotatedTag(repo *models.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag { return &api.AnnotatedTag{ @@ -376,25 +321,6 @@ func ToAnnotatedTagObject(repo *models.Repository, commit *git.Commit) *api.Anno } } -// ToCommitUser convert a git.Signature to an api.CommitUser -func ToCommitUser(sig *git.Signature) *api.CommitUser { - return &api.CommitUser{ - Identity: api.Identity{ - Name: sig.Name, - Email: sig.Email, - }, - Date: sig.When.UTC().Format(time.RFC3339), - } -} - -// ToCommitMeta convert a git.Tag to an api.CommitMeta -func ToCommitMeta(repo *models.Repository, tag *git.Tag) *api.CommitMeta { - return &api.CommitMeta{ - SHA: tag.Object.String(), - URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()), - } -} - // ToTopicResponse convert from models.Topic to api.TopicResponse func ToTopicResponse(topic *models.Topic) *api.TopicResponse { return &api.TopicResponse{ @@ -417,3 +343,24 @@ func ToOAuth2Application(app *models.OAuth2Application) *api.OAuth2Application { Created: app.CreatedUnix.AsTime(), } } + +// ToCommitStatus converts models.CommitStatus to api.Status +func ToCommitStatus(status *models.CommitStatus) *api.Status { + apiStatus := &api.Status{ + Created: status.CreatedUnix.AsTime(), + Updated: status.CreatedUnix.AsTime(), + State: api.StatusState(status.State), + TargetURL: status.TargetURL, + Description: status.Description, + ID: status.Index, + URL: status.APIURL(), + Context: status.Context, + } + + if status.CreatorID != 0 { + creator, _ := models.GetUserByID(status.CreatorID) + apiStatus.Creator = ToUser(creator, false, false) + } + + return apiStatus +} diff --git a/modules/convert/git_commit.go b/modules/convert/git_commit.go new file mode 100644 index 000000000000..87dfb51e7068 --- /dev/null +++ b/modules/convert/git_commit.go @@ -0,0 +1,166 @@ +// 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 convert + +import ( + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" +) + +// ToCommitUser convert a git.Signature to an api.CommitUser +func ToCommitUser(sig *git.Signature) *api.CommitUser { + return &api.CommitUser{ + Identity: api.Identity{ + Name: sig.Name, + Email: sig.Email, + }, + Date: sig.When.UTC().Format(time.RFC3339), + } +} + +// ToCommitMeta convert a git.Tag to an api.CommitMeta +func ToCommitMeta(repo *models.Repository, tag *git.Tag) *api.CommitMeta { + return &api.CommitMeta{ + SHA: tag.Object.String(), + URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()), + Created: tag.Tagger.When, + } +} + +// ToPayloadCommit convert a git.Commit to api.PayloadCommit +func ToPayloadCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit { + authorUsername := "" + if author, err := models.GetUserByEmail(c.Author.Email); err == nil { + authorUsername = author.Name + } else if !models.IsErrUserNotExist(err) { + log.Error("GetUserByEmail: %v", err) + } + + committerUsername := "" + if committer, err := models.GetUserByEmail(c.Committer.Email); err == nil { + committerUsername = committer.Name + } else if !models.IsErrUserNotExist(err) { + log.Error("GetUserByEmail: %v", err) + } + + return &api.PayloadCommit{ + ID: c.ID.String(), + Message: c.Message(), + URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()), + Author: &api.PayloadUser{ + Name: c.Author.Name, + Email: c.Author.Email, + UserName: authorUsername, + }, + Committer: &api.PayloadUser{ + Name: c.Committer.Name, + Email: c.Committer.Email, + UserName: committerUsername, + }, + Timestamp: c.Author.When, + Verification: ToVerification(c), + } +} + +// ToCommit convert a git.Commit to api.Commit +func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) { + + var apiAuthor, apiCommitter *api.User + + // Retrieve author and committer information + + var cacheAuthor *models.User + var ok bool + if userCache == nil { + cacheAuthor = (*models.User)(nil) + ok = false + } else { + cacheAuthor, ok = userCache[commit.Author.Email] + } + + if ok { + apiAuthor = ToUser(cacheAuthor, false, false) + } else { + author, err := models.GetUserByEmail(commit.Author.Email) + if err != nil && !models.IsErrUserNotExist(err) { + return nil, err + } else if err == nil { + apiAuthor = ToUser(author, false, false) + if userCache != nil { + userCache[commit.Author.Email] = author + } + } + } + + var cacheCommitter *models.User + if userCache == nil { + cacheCommitter = (*models.User)(nil) + ok = false + } else { + cacheCommitter, ok = userCache[commit.Committer.Email] + } + + if ok { + apiCommitter = ToUser(cacheCommitter, false, false) + } else { + committer, err := models.GetUserByEmail(commit.Committer.Email) + if err != nil && !models.IsErrUserNotExist(err) { + return nil, err + } else if err == nil { + apiCommitter = ToUser(committer, false, false) + if userCache != nil { + userCache[commit.Committer.Email] = committer + } + } + } + + // Retrieve parent(s) of the commit + apiParents := make([]*api.CommitMeta, commit.ParentCount()) + for i := 0; i < commit.ParentCount(); i++ { + sha, _ := commit.ParentID(i) + apiParents[i] = &api.CommitMeta{ + URL: repo.APIURL() + "/git/commits/" + sha.String(), + SHA: sha.String(), + } + } + + return &api.Commit{ + CommitMeta: &api.CommitMeta{ + URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), + SHA: commit.ID.String(), + }, + HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(), + RepoCommit: &api.RepoCommit{ + URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: commit.Committer.Name, + Email: commit.Committer.Email, + }, + Date: commit.Author.When.Format(time.RFC3339), + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: commit.Committer.Name, + Email: commit.Committer.Email, + }, + Date: commit.Committer.When.Format(time.RFC3339), + }, + Message: commit.Message(), + Tree: &api.CommitMeta{ + URL: repo.APIURL() + "/git/trees/" + commit.ID.String(), + SHA: commit.ID.String(), + }, + }, + Author: apiAuthor, + Committer: apiCommitter, + Parents: apiParents, + }, nil +} diff --git a/modules/convert/git_commit_test.go b/modules/convert/git_commit_test.go new file mode 100644 index 000000000000..2158d0d77796 --- /dev/null +++ b/modules/convert/git_commit_test.go @@ -0,0 +1,42 @@ +// 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 convert + +import ( + "testing" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/stretchr/testify/assert" +) + +func TestToCommitMeta(t *testing.T) { + assert.NoError(t, models.PrepareTestDatabase()) + headRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000") + signature := &object.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} + tag := &git.Tag{ + Name: "Test Tag", + ID: sha1, + Object: sha1, + Type: "Test Type", + Tagger: signature, + Message: "Test Message", + } + + commitMeta := ToCommitMeta(headRepo, tag) + + assert.NotNil(t, commitMeta) + assert.EqualValues(t, &api.CommitMeta{ + SHA: "0000000000000000000000000000000000000000", + URL: util.URLJoin(headRepo.APIURL(), "git/commits", "0000000000000000000000000000000000000000"), + Created: time.Unix(0, 0), + }, commitMeta) +} diff --git a/modules/convert/issue.go b/modules/convert/issue.go index a335f6326b9e..f34656b47c1c 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -31,7 +31,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { URL: issue.APIURL(), HTMLURL: issue.HTMLURL(), Index: issue.Index, - Poster: issue.Poster.APIFormat(), + Poster: ToUser(issue.Poster, false, false), Title: issue.Title, Body: issue.Content, Labels: ToLabelList(issue.Labels), @@ -65,9 +65,9 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { } if len(issue.Assignees) > 0 { for _, assignee := range issue.Assignees { - apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat()) + apiIssue.Assignees = append(apiIssue.Assignees, ToUser(assignee, false, false)) } - apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee` + apiIssue.Assignee = ToUser(issue.Assignees[0], false, false) // For compatibility, we're keeping the first assignee as `apiIssue.Assignee` } if issue.IsPull { if err := issue.LoadPullRequest(); err != nil { @@ -115,6 +115,46 @@ func ToTrackedTime(t *models.TrackedTime) (apiT *api.TrackedTime) { return } +// ToStopWatches convert Stopwatch list to api.StopWatches +func ToStopWatches(sws []*models.Stopwatch) (api.StopWatches, error) { + result := api.StopWatches(make([]api.StopWatch, 0, len(sws))) + + issueCache := make(map[int64]*models.Issue) + repoCache := make(map[int64]*models.Repository) + var ( + issue *models.Issue + repo *models.Repository + ok bool + err error + ) + + for _, sw := range sws { + issue, ok = issueCache[sw.IssueID] + if !ok { + issue, err = models.GetIssueByID(sw.IssueID) + if err != nil { + return nil, err + } + } + repo, ok = repoCache[issue.RepoID] + if !ok { + repo, err = models.GetRepositoryByID(issue.RepoID) + if err != nil { + return nil, err + } + } + + result = append(result, api.StopWatch{ + Created: sw.CreatedUnix.AsTime(), + IssueIndex: issue.Index, + IssueTitle: issue.Title, + RepoOwnerName: repo.OwnerName, + RepoName: repo.Name, + }) + } + return result, nil +} + // ToTrackedTimeList converts TrackedTimeList to API format func ToTrackedTimeList(tl models.TrackedTimeList) api.TrackedTimeList { result := make([]*api.TrackedTime, 0, len(tl)) @@ -152,6 +192,8 @@ func ToAPIMilestone(m *models.Milestone) *api.Milestone { Description: m.Content, OpenIssues: m.NumOpenIssues, ClosedIssues: m.NumClosedIssues, + Created: m.CreatedUnix.AsTime(), + Updated: m.UpdatedUnix.AsTimePtr(), } if m.IsClosed { apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr() diff --git a/modules/convert/issue_comment.go b/modules/convert/issue_comment.go new file mode 100644 index 000000000000..cf65c876d3d0 --- /dev/null +++ b/modules/convert/issue_comment.go @@ -0,0 +1,24 @@ +// 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 convert + +import ( + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" +) + +// ToComment converts a models.Comment to the api.Comment format +func ToComment(c *models.Comment) *api.Comment { + return &api.Comment{ + ID: c.ID, + Poster: ToUser(c.Poster, false, false), + HTMLURL: c.HTMLURL(), + IssueURL: c.IssueURL(), + PRURL: c.PRURL(), + Body: c.Content, + Created: c.CreatedUnix.AsTime(), + Updated: c.UpdatedUnix.AsTime(), + } +} diff --git a/modules/convert/issue_test.go b/modules/convert/issue_test.go index e5676293f85f..2f8f56e99a64 100644 --- a/modules/convert/issue_test.go +++ b/modules/convert/issue_test.go @@ -34,6 +34,8 @@ func TestMilestone_APIFormat(t *testing.T) { IsClosed: false, NumOpenIssues: 5, NumClosedIssues: 6, + CreatedUnix: timeutil.TimeStamp(time.Date(1999, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), + UpdatedUnix: timeutil.TimeStamp(time.Date(1999, time.March, 1, 0, 0, 0, 0, time.UTC).Unix()), DeadlineUnix: timeutil.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), } assert.Equal(t, api.Milestone{ @@ -43,6 +45,8 @@ func TestMilestone_APIFormat(t *testing.T) { Description: milestone.Content, OpenIssues: milestone.NumOpenIssues, ClosedIssues: milestone.NumClosedIssues, + Created: milestone.CreatedUnix.AsTime(), + Updated: milestone.UpdatedUnix.AsTimePtr(), Deadline: milestone.DeadlineUnix.AsTimePtr(), }, *ToAPIMilestone(milestone)) } diff --git a/modules/convert/pull.go b/modules/convert/pull.go index 2fa22efcb388..e522bee787b3 100644 --- a/modules/convert/pull.go +++ b/modules/convert/pull.go @@ -141,7 +141,7 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { if pr.HasMerged { apiPullRequest.Merged = pr.MergedUnix.AsTimePtr() apiPullRequest.MergedCommitID = &pr.MergedCommitID - apiPullRequest.MergedBy = pr.Merger.APIFormat() + apiPullRequest.MergedBy = ToUser(pr.Merger, false, false) } return apiPullRequest diff --git a/modules/convert/pull_review.go b/modules/convert/pull_review.go index 032d3617fc23..0ef1fec39cd5 100644 --- a/modules/convert/pull_review.go +++ b/modules/convert/pull_review.go @@ -28,6 +28,7 @@ func ToPullReview(r *models.Review, doer *models.User) (*api.PullReview, error) result := &api.PullReview{ ID: r.ID, Reviewer: ToUser(r.Reviewer, doer != nil, auth), + ReviewerTeam: ToTeam(r.ReviewerTeam), State: api.ReviewStateUnknown, Body: r.Content, CommitID: r.CommitID, diff --git a/modules/convert/release.go b/modules/convert/release.go new file mode 100644 index 000000000000..d9def8963782 --- /dev/null +++ b/modules/convert/release.go @@ -0,0 +1,48 @@ +// 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 convert + +import ( + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" +) + +// ToRelease convert a models.Release to api.Release +func ToRelease(r *models.Release) *api.Release { + assets := make([]*api.Attachment, 0) + for _, att := range r.Attachments { + assets = append(assets, ToReleaseAttachment(att)) + } + return &api.Release{ + ID: r.ID, + TagName: r.TagName, + Target: r.Target, + Title: r.Title, + Note: r.Note, + URL: r.APIURL(), + HTMLURL: r.HTMLURL(), + TarURL: r.TarURL(), + ZipURL: r.ZipURL(), + IsDraft: r.IsDraft, + IsPrerelease: r.IsPrerelease, + CreatedAt: r.CreatedUnix.AsTime(), + PublishedAt: r.CreatedUnix.AsTime(), + Publisher: ToUser(r.Publisher, false, false), + Attachments: assets, + } +} + +// ToReleaseAttachment converts models.Attachment to api.Attachment +func ToReleaseAttachment(a *models.Attachment) *api.Attachment { + return &api.Attachment{ + ID: a.ID, + Name: a.Name, + Created: a.CreatedUnix.AsTime(), + DownloadCount: a.DownloadCount, + Size: a.Size, + UUID: a.UUID, + DownloadURL: a.DownloadURL(), + } +} diff --git a/modules/convert/user.go b/modules/convert/user.go new file mode 100644 index 000000000000..010c92b969bb --- /dev/null +++ b/modules/convert/user.go @@ -0,0 +1,38 @@ +// 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 convert + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/markup" + api "code.gitea.io/gitea/modules/structs" +) + +// ToUser convert models.User to api.User +// signed shall only be set if requester is logged in. authed shall only be set if user is site admin or user himself +func ToUser(user *models.User, signed, authed bool) *api.User { + if user == nil { + return nil + } + result := &api.User{ + ID: user.ID, + UserName: user.Name, + FullName: markup.Sanitize(user.FullName), + Email: user.GetEmail(), + AvatarURL: user.AvatarLink(), + Created: user.CreatedUnix.AsTime(), + } + // hide primary email if API caller is anonymous or user keep email private + if signed && (!user.KeepEmailPrivate || authed) { + result.Email = user.Email + } + // only site admin will get these information and possibly user himself + if authed { + result.IsAdmin = user.IsAdmin + result.LastLogin = user.LastLoginUnix.AsTime() + result.Language = user.Language + } + return result +} diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go new file mode 100644 index 000000000000..eff60d51830e --- /dev/null +++ b/modules/convert/user_test.go @@ -0,0 +1,28 @@ +// 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 convert + +import ( + "testing" + + "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" +) + +func TestUser_ToUser(t *testing.T) { + + user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1, IsAdmin: true}).(*models.User) + + apiUser := ToUser(user1, true, true) + assert.True(t, apiUser.IsAdmin) + + user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2, IsAdmin: false}).(*models.User) + + apiUser = ToUser(user2, true, true) + assert.False(t, apiUser.IsAdmin) + + apiUser = ToUser(user1, false, false) + assert.False(t, apiUser.IsAdmin) +} diff --git a/modules/convert/utils.go b/modules/convert/utils.go index ddb8a8820d42..69de306689e0 100644 --- a/modules/convert/utils.go +++ b/modules/convert/utils.go @@ -1,3 +1,4 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2016 The Gogs Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -5,7 +6,10 @@ package convert import ( + "strings" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" ) // ToCorrectPageSize makes sure page size is in allowed range. @@ -17,3 +21,19 @@ func ToCorrectPageSize(size int) int { } return size } + +// ToGitServiceType return GitServiceType based on string +func ToGitServiceType(value string) structs.GitServiceType { + switch strings.ToLower(value) { + case "github": + return structs.GithubService + case "gitea": + return structs.GiteaService + case "gitlab": + return structs.GitlabService + case "gogs": + return structs.GogsService + default: + return structs.PlainGitService + } +} diff --git a/modules/cron/tasks_extended.go b/modules/cron/tasks_extended.go index fa2d6e0c385f..f0742eb471f3 100644 --- a/modules/cron/tasks_extended.go +++ b/modules/cron/tasks_extended.go @@ -67,6 +67,16 @@ func registerRewriteAllPublicKeys() { }) } +func registerRewriteAllPrincipalKeys() { + RegisterTaskFatal("resync_all_sshprincipals", &BaseConfig{ + Enabled: false, + RunAtStart: false, + Schedule: "@every 72h", + }, func(_ context.Context, _ *models.User, _ Config) error { + return models.RewriteAllPrincipalKeys() + }) +} + func registerRepositoryUpdateHook() { RegisterTaskFatal("resync_all_hooks", &BaseConfig{ Enabled: false, @@ -112,6 +122,7 @@ func initExtendedTasks() { registerDeleteRepositoryArchives() registerGarbageCollectRepositories() registerRewriteAllPublicKeys() + registerRewriteAllPrincipalKeys() registerRepositoryUpdateHook() registerReinitMissingRepositories() registerDeleteMissingRepositories() diff --git a/modules/git/command.go b/modules/git/command.go index 1496b0186ed1..d40c0bfa2322 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -1,4 +1,5 @@ // Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2016 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. @@ -130,7 +131,9 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time. } // TODO: verify if this is still needed in golang 1.15 - cmd.Env = append(cmd.Env, "GODEBUG=asyncpreemptoff=1") + if goVersionLessThan115 { + cmd.Env = append(cmd.Env, "GODEBUG=asyncpreemptoff=1") + } cmd.Dir = dir cmd.Stdout = stdout cmd.Stderr = stderr diff --git a/modules/git/commit.go b/modules/git/commit.go index 2ae35c9f58ac..87278af9c7df 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/mcuadros/go-version" ) // Commit represents a git commit. @@ -470,7 +469,7 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) { // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') func (c *Commit) GetBranchName() (string, error) { - binVersion, err := BinVersion() + err := LoadGitVersion() if err != nil { return "", fmt.Errorf("Git version missing: %v", err) } @@ -478,7 +477,7 @@ func (c *Commit) GetBranchName() (string, error) { args := []string{ "name-rev", } - if version.Compare(binVersion, "2.13.0", ">=") { + if CheckGitVersionAtLeast("2.13.0") == nil { args = append(args, "--exclude", "refs/tags/*") } args = append(args, "--name-only", "--no-undefined", c.ID.String()) diff --git a/modules/git/commit_archive.go b/modules/git/commit_archive.go index c7d1d06c4680..d075ba09115f 100644 --- a/modules/git/commit_archive.go +++ b/modules/git/commit_archive.go @@ -6,6 +6,7 @@ package git import ( + "context" "fmt" "path/filepath" "strings" @@ -39,7 +40,7 @@ type CreateArchiveOpts struct { } // CreateArchive create archive content to the target path -func (c *Commit) CreateArchive(target string, opts CreateArchiveOpts) error { +func (c *Commit) CreateArchive(ctx context.Context, target string, opts CreateArchiveOpts) error { if opts.Format.String() == "unknown" { return fmt.Errorf("unknown format: %v", opts.Format) } @@ -58,6 +59,6 @@ func (c *Commit) CreateArchive(target string, opts CreateArchiveOpts) error { c.ID.String(), ) - _, err := NewCommand(args...).RunInDir(c.repo.Path) + _, err := NewCommandContext(ctx, args...).RunInDir(c.repo.Path) return err } diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index f4ba5aceb766..e03ea00fc69b 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -40,7 +40,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom return nil, nil, err } if len(unHitPaths) > 0 { - revs2, err := getLastCommitForPaths(c, treePath, unHitPaths) + revs2, err := GetLastCommitForPaths(c, treePath, unHitPaths) if err != nil { return nil, nil, err } @@ -53,7 +53,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom } } } else { - revs, err = getLastCommitForPaths(c, treePath, entryPaths) + revs, err = GetLastCommitForPaths(c, treePath, entryPaths) } if err != nil { return nil, nil, err @@ -170,7 +170,8 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac return results, unHitEntryPaths, nil } -func getLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) { +// GetLastCommitForPaths returns last commit information +func GetLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) { // We do a tree traversal with nodes sorted by commit time heap := binaryheap.NewWith(func(a, b interface{}) int { if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) { diff --git a/modules/git/git.go b/modules/git/git.go index 1061bdb0d525..cce5cc738da7 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -15,14 +15,9 @@ import ( "code.gitea.io/gitea/modules/process" - "github.com/mcuadros/go-version" + "github.com/hashicorp/go-version" ) -// Version return this package's current version -func Version() string { - return "0.4.2" -} - var ( // Debug enables verbose logging on everything. // This should be false in case Gogs starts in SSH mode. @@ -39,7 +34,10 @@ var ( // DefaultContext is the default context to run git commands in DefaultContext = context.Background() - gitVersion string + gitVersion *version.Version + + // will be checked on Init + goVersionLessThan115 = true ) func log(format string, args ...interface{}) { @@ -55,31 +53,43 @@ func log(format string, args ...interface{}) { } } -// BinVersion returns current Git version from shell. -func BinVersion() (string, error) { - if len(gitVersion) > 0 { - return gitVersion, nil +// LocalVersion returns current Git version from shell. +func LocalVersion() (*version.Version, error) { + if err := LoadGitVersion(); err != nil { + return nil, err + } + return gitVersion, nil +} + +// LoadGitVersion returns current Git version from shell. +func LoadGitVersion() error { + // doesn't need RWMutex because its exec by Init() + if gitVersion != nil { + return nil } stdout, err := NewCommand("version").Run() if err != nil { - return "", err + return err } fields := strings.Fields(stdout) if len(fields) < 3 { - return "", fmt.Errorf("not enough output: %s", stdout) + return fmt.Errorf("not enough output: %s", stdout) } + var versionString string + // Handle special case on Windows. i := strings.Index(fields[2], "windows") if i >= 1 { - gitVersion = fields[2][:i-1] - return gitVersion, nil + versionString = fields[2][:i-1] + } else { + versionString = fields[2] } - gitVersion = fields[2] - return gitVersion, nil + gitVersion, err = version.NewVersion(versionString) + return err } // SetExecutablePath changes the path of git executable and checks the file permission and version. @@ -94,11 +104,17 @@ func SetExecutablePath(path string) error { } GitExecutable = absPath - gitVersion, err := BinVersion() + err = LoadGitVersion() if err != nil { return fmt.Errorf("Git version missing: %v", err) } - if version.Compare(gitVersion, GitVersionRequired, "<") { + + versionRequired, err := version.NewVersion(GitVersionRequired) + if err != nil { + return err + } + + if gitVersion.LessThan(versionRequired) { return fmt.Errorf("Git version not supported. Requires version > %v", GitVersionRequired) } @@ -108,6 +124,20 @@ func SetExecutablePath(path string) error { // Init initializes git module func Init(ctx context.Context) error { DefaultContext = ctx + + // Save current git version on init to gitVersion otherwise it would require an RWMutex + if err := LoadGitVersion(); err != nil { + return err + } + + // Save if the go version used to compile gitea is greater or equal 1.15 + runtimeVersion, err := version.NewVersion(strings.TrimPrefix(runtime.Version(), "go")) + if err != nil { + return err + } + version115, _ := version.NewVersion("1.15") + goVersionLessThan115 = runtimeVersion.LessThan(version115) + // Git requires setting user.name and user.email in order to commit changes - if they're not set just add some defaults for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} { if err := checkAndSetConfig(configKey, defaultValue, false); err != nil { @@ -120,13 +150,13 @@ func Init(ctx context.Context) error { return err } - if version.Compare(gitVersion, "2.10", ">=") { + if CheckGitVersionAtLeast("2.10") == nil { if err := checkAndSetConfig("receive.advertisePushOptions", "true", true); err != nil { return err } } - if version.Compare(gitVersion, "2.18", ">=") { + if CheckGitVersionAtLeast("2.18") == nil { if err := checkAndSetConfig("core.commitGraph", "true", true); err != nil { return err } @@ -143,6 +173,21 @@ func Init(ctx context.Context) error { return nil } +// CheckGitVersionAtLeast check git version is at least the constraint version +func CheckGitVersionAtLeast(atLeast string) error { + if err := LoadGitVersion(); err != nil { + return err + } + atLeastVersion, err := version.NewVersion(atLeast) + if err != nil { + return err + } + if gitVersion.Compare(atLeastVersion) < 0 { + return fmt.Errorf("installed git binary version %s is not at least %s", gitVersion.Original(), atLeast) + } + return nil +} + func checkAndSetConfig(key, defaultValue string, forceToDefault bool) error { stdout, stderr, err := process.GetManager().Exec("git.Init(get setting)", GitExecutable, "config", "--get", key) if err != nil { diff --git a/modules/git/hook.go b/modules/git/hook.go index 2b9f90efd3a5..2de36dbdef66 100644 --- a/modules/git/hook.go +++ b/modules/git/hook.go @@ -96,7 +96,7 @@ func (h *Hook) Update() error { return err } - err := ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm) + err := ioutil.WriteFile(h.path, []byte(strings.ReplaceAll(h.Content, "\r", "")), os.ModePerm) if err != nil { return err } diff --git a/modules/git/notes.go b/modules/git/notes.go index 8184a568faf5..ba19fa489341 100644 --- a/modules/git/notes.go +++ b/modules/git/notes.go @@ -70,7 +70,7 @@ func GetNote(repo *Repository, commitID string, note *Note) error { return err } - lastCommits, err := getLastCommitForPaths(commitNode, "", []string{path}) + lastCommits, err := GetLastCommitForPaths(commitNode, "", []string{path}) if err != nil { return err } diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index c10c96f5584b..aa5e4c10e70d 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -7,8 +7,6 @@ package git import ( "bytes" "fmt" - - "github.com/mcuadros/go-version" ) // CheckAttributeOpts represents the possible options to CheckAttribute @@ -21,7 +19,7 @@ type CheckAttributeOpts struct { // CheckAttribute return the Blame object of file func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) { - binVersion, err := BinVersion() + err := LoadGitVersion() if err != nil { return nil, fmt.Errorf("Git version missing: %v", err) } @@ -42,7 +40,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ } // git check-attr --cached first appears in git 1.7.8 - if opts.CachedOnly && version.Compare(binVersion, "1.7.8", ">=") { + if opts.CachedOnly && CheckGitVersionAtLeast("1.7.8") == nil { cmdArgs = append(cmdArgs, "--cached") } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 8f9c802e016d..cd30c191ea31 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -74,6 +74,11 @@ func (repo *Repository) SetDefaultBranch(name string) error { return err } +// GetDefaultBranch gets default branch of repository. +func (repo *Repository) GetDefaultBranch() (string, error) { + return NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path) +} + // GetBranches returns all branches of the repository. func (repo *Repository) GetBranches() ([]string, error) { var branchNames []string diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index bdc5b73dd84b..dd014e5d7af5 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -14,7 +14,6 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/mcuadros/go-version" ) // GetRefCommitID returns the last commit ID string of given reference (branch or tag). @@ -470,7 +469,7 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) (*list.List, err } func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { - if version.Compare(gitVersion, "2.7.0", ">=") { + if CheckGitVersionAtLeast("2.7.0") == nil { stdout, err := NewCommand("for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path) if err != nil { return nil, err @@ -505,10 +504,6 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) // GetCommitsFromIDs get commits from commit IDs func (repo *Repository) GetCommitsFromIDs(commitIDs []string) (commits *list.List) { - if len(commitIDs) == 0 { - return nil - } - commits = list.New() for _, commitID := range commitIDs { diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go index bfa368b6dfb7..59b8177401cc 100644 --- a/modules/git/repo_stats.go +++ b/modules/git/repo_stats.go @@ -6,8 +6,9 @@ package git import ( "bufio" - "bytes" + "context" "fmt" + "os" "sort" "strconv" "strings" @@ -49,6 +50,15 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) } stats.CommitCountInAllBranches = c + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + return nil, err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)} if len(branch) == 0 { args = append(args, "--branches=*") @@ -56,79 +66,88 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) args = append(args, "--first-parent", branch) } - stdout, err = NewCommand(args...).RunInDirBytes(repo.Path) - if err != nil { - return nil, err - } + stderr := new(strings.Builder) + err = NewCommand(args...).RunInDirTimeoutEnvFullPipelineFunc( + nil, -1, repo.Path, + stdoutWriter, stderr, nil, + func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() - scanner := bufio.NewScanner(bytes.NewReader(stdout)) - scanner.Split(bufio.ScanLines) - stats.CommitCount = 0 - stats.Additions = 0 - stats.Deletions = 0 - authors := make(map[string]*CodeActivityAuthor) - files := make(map[string]bool) - var author string - p := 0 - for scanner.Scan() { - l := strings.TrimSpace(scanner.Text()) - if l == "---" { - p = 1 - } else if p == 0 { - continue - } else { - p++ - } - if p > 4 && len(l) == 0 { - continue - } - switch p { - case 1: // Separator - case 2: // Commit sha-1 - stats.CommitCount++ - case 3: // Author - author = l - case 4: // E-mail - email := strings.ToLower(l) - if _, ok := authors[email]; !ok { - authors[email] = &CodeActivityAuthor{ - Name: author, - Email: email, - Commits: 0, + scanner := bufio.NewScanner(stdoutReader) + scanner.Split(bufio.ScanLines) + stats.CommitCount = 0 + stats.Additions = 0 + stats.Deletions = 0 + authors := make(map[string]*CodeActivityAuthor) + files := make(map[string]bool) + var author string + p := 0 + for scanner.Scan() { + l := strings.TrimSpace(scanner.Text()) + if l == "---" { + p = 1 + } else if p == 0 { + continue + } else { + p++ } - } - authors[email].Commits++ - default: // Changed file - if parts := strings.Fields(l); len(parts) >= 3 { - if parts[0] != "-" { - if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil { - stats.Additions += c - } + if p > 4 && len(l) == 0 { + continue } - if parts[1] != "-" { - if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil { - stats.Deletions += c + switch p { + case 1: // Separator + case 2: // Commit sha-1 + stats.CommitCount++ + case 3: // Author + author = l + case 4: // E-mail + email := strings.ToLower(l) + if _, ok := authors[email]; !ok { + authors[email] = &CodeActivityAuthor{ + Name: author, + Email: email, + Commits: 0, + } + } + authors[email].Commits++ + default: // Changed file + if parts := strings.Fields(l); len(parts) >= 3 { + if parts[0] != "-" { + if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil { + stats.Additions += c + } + } + if parts[1] != "-" { + if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil { + stats.Deletions += c + } + } + if _, ok := files[parts[2]]; !ok { + files[parts[2]] = true + } } - } - if _, ok := files[parts[2]]; !ok { - files[parts[2]] = true } } - } - } - a := make([]*CodeActivityAuthor, 0, len(authors)) - for _, v := range authors { - a = append(a, v) + a := make([]*CodeActivityAuthor, 0, len(authors)) + for _, v := range authors { + a = append(a, v) + } + // Sort authors descending depending on commit count + sort.Slice(a, func(i, j int) bool { + return a[i].Commits > a[j].Commits + }) + + stats.AuthorCount = int64(len(authors)) + stats.ChangedFiles = int64(len(files)) + stats.Authors = a + + _ = stdoutReader.Close() + return nil + }) + if err != nil { + return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr) } - // Sort authors descending depending on commit count - sort.Slice(a, func(i, j int) bool { - return a[i].Commits > a[j].Commits - }) - - stats.AuthorCount = int64(len(authors)) - stats.ChangedFiles = int64(len(files)) - stats.Authors = a return stats, nil } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 7780e3477ddf..376a699502ba 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/go-git/go-git/v5/plumbing" - "github.com/mcuadros/go-version" ) // TagPrefix tags prefix path on the repository @@ -239,8 +238,6 @@ func (repo *Repository) GetTags() ([]string, error) { return nil }) - version.Sort(tagNames) - // Reverse order for i := 0; i < len(tagNames)/2; i++ { j := len(tagNames) - i - 1 diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 8f91f4efaccb..0b08a10d554d 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -11,8 +11,6 @@ import ( "os" "strings" "time" - - "github.com/mcuadros/go-version" ) func (repo *Repository) getTree(id SHA1) (*Tree, error) { @@ -64,8 +62,8 @@ type CommitTreeOpts struct { } // CommitTree creates a commit from a given tree id for the user with provided message -func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { - binVersion, err := BinVersion() +func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { + err := LoadGitVersion() if err != nil { return SHA1{}, err } @@ -74,11 +72,11 @@ func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOp // Because this may call hooks we should pass in the environment env := append(os.Environ(), - "GIT_AUTHOR_NAME="+sig.Name, - "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_AUTHOR_NAME="+author.Name, + "GIT_AUTHOR_EMAIL="+author.Email, "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+sig.Name, - "GIT_COMMITTER_EMAIL="+sig.Email, + "GIT_COMMITTER_NAME="+committer.Name, + "GIT_COMMITTER_EMAIL="+committer.Email, "GIT_COMMITTER_DATE="+commitTimeStr, ) cmd := NewCommand("commit-tree", tree.ID.String()) @@ -91,11 +89,11 @@ func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOp _, _ = messageBytes.WriteString(opts.Message) _, _ = messageBytes.WriteString("\n") - if version.Compare(binVersion, "1.7.9", ">=") && (opts.KeyID != "" || opts.AlwaysSign) { + if CheckGitVersionAtLeast("1.7.9") == nil && (opts.KeyID != "" || opts.AlwaysSign) { cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID)) } - if version.Compare(binVersion, "2.0.0", ">=") && opts.NoGPGSign { + if CheckGitVersionAtLeast("2.0.0") == nil && opts.NoGPGSign { cmd.AddArguments("--no-gpg-sign") } diff --git a/modules/git/submodule.go b/modules/git/submodule.go index bb094bda5df5..231827f1e989 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -39,7 +39,7 @@ func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile { } } -func getRefURL(refURL, urlPrefix, repoFullName string) string { +func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { if refURL == "" { return "" } @@ -76,7 +76,7 @@ func getRefURL(refURL, urlPrefix, repoFullName string) string { pth = "/" + pth } - if urlPrefixHostname == refHostname { + if urlPrefixHostname == refHostname || refHostname == sshDomain { return urlPrefix + path.Clean(path.Join("/", pth)) } return "http://" + refHostname + pth @@ -102,7 +102,7 @@ func getRefURL(refURL, urlPrefix, repoFullName string) string { return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path } return ref.Scheme + "://" + ref.Host + ref.Path - } else if urlPrefixHostname == refHostname { + } else if urlPrefixHostname == refHostname || refHostname == sshDomain { return urlPrefix + path.Clean(path.Join("/", ref.Path)) } else { return "http://" + refHostname + ref.Path @@ -114,8 +114,8 @@ func getRefURL(refURL, urlPrefix, repoFullName string) string { } // RefURL guesses and returns reference URL. -func (sf *SubModuleFile) RefURL(urlPrefix string, repoFullName string) string { - return getRefURL(sf.refURL, urlPrefix, repoFullName) +func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string { + return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain) } // RefID returns reference ID. diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go index fcb0c6fd671b..ff8dc579f6d5 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/submodule_test.go @@ -15,27 +15,29 @@ func TestGetRefURL(t *testing.T) { refURL string prefixURL string parentPath string + SSHDomain string expect string }{ - {"git://github.com/user1/repo1", "/", "user1/repo2", "http://github.com/user1/repo1"}, - {"https://localhost/user1/repo1.git", "/", "user1/repo2", "https://localhost/user1/repo1"}, - {"http://localhost/user1/repo1.git", "/", "owner/reponame", "http://localhost/user1/repo1"}, - {"git@github.com:user1/repo1.git", "/", "owner/reponame", "http://github.com/user1/repo1"}, - {"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"}, - {"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"}, - {"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "https://try.gitea.io/go-gitea/gitea"}, - {"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "https://try.gitea.io/go-gitea/gitea"}, - {"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "https://try.gitea.io/go-gitea/gitea"}, - {"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "https://127.0.0.1:3000/go-gitea/gitea"}, - {"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "https://gitea.com:3000/user1/repo1"}, - {"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"}, - {"https://username:password@github.com/username/repository.git", "/", "username/repository2", "https://username:password@github.com/username/repository"}, - {"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", ""}, - {"git@localhost:user/repo", "https://localhost/", "user2/repo1", "https://localhost/user/repo"}, - {"../path/to/repo.git/", "https://localhost/", "user/repo2", "https://localhost/user/path/to/repo.git"}, + {"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"}, + {"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"}, + {"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"}, + {"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"}, + {"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"}, + {"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"}, + {"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, + {"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, + {"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"}, + {"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"}, + {"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"}, + {"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"}, + {"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"}, + {"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""}, + {"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"}, + {"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"}, + {"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"}, } for _, kase := range kases { - assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath)) + assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain)) } } diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index 4c5a80cb3635..f9fc6db497cf 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -9,6 +9,7 @@ import ( "path" "strings" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -35,6 +36,11 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { if i == len(parts)-1 { entries, err := tree.ListEntries() if err != nil { + if err == plumbing.ErrObjectNotFound { + return nil, ErrNotExist{ + RelPath: relpath, + } + } return nil, err } for _, v := range entries { @@ -45,6 +51,11 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { } else { tree, err = tree.SubTree(name) if err != nil { + if err == plumbing.ErrObjectNotFound { + return nil, ErrNotExist{ + RelPath: relpath, + } + } return nil, err } } diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 6b134e7d0cce..903d05ed21f4 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -31,7 +31,7 @@ const ( // // If you add an additional place you must increment this number // and add a function to call manager.InformCleanup if it's not going to be used -const numberOfServersToCreate = 3 +const numberOfServersToCreate = 4 // Manager represents the graceful server manager interface var manager *Manager diff --git a/modules/graceful/server.go b/modules/graceful/server.go index 4d0d8677f0e6..e7394f349e79 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -128,6 +128,8 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error { go srv.awaitShutdown() + tlsConfig.MinVersion = tls.VersionTLS12 + l, err := GetListener(srv.network, srv.address) if err != nil { log.Error("Unable to get Listener: %v", err) @@ -160,7 +162,7 @@ func (srv *Server) Serve(serve ServeFunction) error { srv.setState(stateTerminate) GetManager().ServerDone() // use of closed means that the listeners are closed - i.e. we should be shutting down - return nil - if err != nil && strings.Contains(err.Error(), "use of closed") { + if err == nil || strings.Contains(err.Error(), "use of closed") || strings.Contains(err.Error(), "http: Server closed") { return nil } return err diff --git a/modules/hcaptcha/hcaptcha.go b/modules/hcaptcha/hcaptcha.go new file mode 100644 index 000000000000..95fe2dd1c3b1 --- /dev/null +++ b/modules/hcaptcha/hcaptcha.go @@ -0,0 +1,34 @@ +// 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 hcaptcha + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" + + "go.jolheiser.com/hcaptcha" +) + +// Verify calls hCaptcha API to verify token +func Verify(ctx context.Context, response string) (bool, error) { + client, err := hcaptcha.New(setting.Service.HcaptchaSecret, hcaptcha.WithContext(ctx)) + if err != nil { + return false, err + } + + resp, err := client.Verify(response, hcaptcha.PostOptions{ + Sitekey: setting.Service.HcaptchaSitekey, + }) + if err != nil { + return false, err + } + + var respErr error + if len(resp.ErrorCodes) > 0 { + respErr = resp.ErrorCodes[0] + } + return resp.Success, respErr +} diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index a2bf93ee92c4..1a641081396a 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -8,6 +8,7 @@ package highlight import ( "bufio" "bytes" + gohtml "html" "path/filepath" "strings" "sync" @@ -160,7 +161,7 @@ func plainText(code string, numLines int) map[int]string { if content == "" { content = "\n" } - m[line] = content + m[line] = gohtml.EscapeString(content) } return m } diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go index 6502259ba410..9caa6528f7e7 100644 --- a/modules/indexer/code/bleve.go +++ b/modules/indexer/code/bleve.go @@ -37,11 +37,6 @@ import ( const unicodeNormalizeName = "unicodeNormalize" const maxBatchSize = 16 -// indexerID a bleve-compatible unique identifier for an integer id -func indexerID(id int64) string { - return strconv.FormatInt(id, 36) -} - // numericEqualityQuery a numeric equality query for the given value and field func numericEqualityQuery(value int64, field string) *query.NumericRangeQuery { f := float64(value) @@ -58,10 +53,10 @@ func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error { }) } -// openIndexer open the index at the specified path, checking for metadata +// openBleveIndexer open the index at the specified path, checking for metadata // updates and bleve version updates. If index needs to be created (or // re-created), returns (nil, nil) -func openIndexer(path string, latestVersion int) (bleve.Index, error) { +func openBleveIndexer(path string, latestVersion int) (bleve.Index, error) { _, err := os.Stat(path) if err != nil && os.IsNotExist(err) { return nil, nil @@ -104,54 +99,14 @@ func (d *RepoIndexerData) Type() string { return repoIndexerDocType } -func addUpdate(commitSha string, update fileUpdate, repo *models.Repository, batch rupture.FlushingBatch) error { - // Ignore vendored files in code search - if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) { - return nil - } - stdout, err := git.NewCommand("cat-file", "-s", update.BlobSha). - RunInDir(repo.RepoPath()) - if err != nil { - return err - } - if size, err := strconv.Atoi(strings.TrimSpace(stdout)); err != nil { - return fmt.Errorf("Misformatted git cat-file output: %v", err) - } else if int64(size) > setting.Indexer.MaxIndexerFileSize { - return addDelete(update.Filename, repo, batch) - } - - fileContents, err := git.NewCommand("cat-file", "blob", update.BlobSha). - RunInDirBytes(repo.RepoPath()) - if err != nil { - return err - } else if !base.IsTextFile(fileContents) { - // FIXME: UTF-16 files will probably fail here - return nil - } - - id := filenameIndexerID(repo.ID, update.Filename) - return batch.Index(id, &RepoIndexerData{ - RepoID: repo.ID, - CommitID: commitSha, - Content: string(charset.ToUTF8DropErrors(fileContents)), - Language: analyze.GetCodeLanguage(update.Filename, fileContents), - UpdatedAt: time.Now().UTC(), - }) -} - -func addDelete(filename string, repo *models.Repository, batch rupture.FlushingBatch) error { - id := filenameIndexerID(repo.ID, filename) - return batch.Delete(id) -} - const ( repoIndexerAnalyzer = "repoIndexerAnalyzer" repoIndexerDocType = "repoIndexerDocType" repoIndexerLatestVersion = 5 ) -// createRepoIndexer create a repo indexer if one does not already exist -func createRepoIndexer(path string, latestVersion int) (bleve.Index, error) { +// createBleveIndexer create a bleve repo indexer if one does not already exist +func createBleveIndexer(path string, latestVersion int) (bleve.Index, error) { docMapping := bleve.NewDocumentMapping() numericFieldMapping := bleve.NewNumericFieldMapping() numericFieldMapping.IncludeInAll = false @@ -199,18 +154,6 @@ func createRepoIndexer(path string, latestVersion int) (bleve.Index, error) { return indexer, nil } -func filenameIndexerID(repoID int64, filename string) string { - return indexerID(repoID) + "_" + filename -} - -func filenameOfIndexerID(indexerID string) string { - index := strings.IndexByte(indexerID, '_') - if index == -1 { - log.Error("Unexpected ID in repo indexer: %s", indexerID) - } - return indexerID[index+1:] -} - var ( _ Indexer = &BleveIndexer{} ) @@ -230,10 +173,51 @@ func NewBleveIndexer(indexDir string) (*BleveIndexer, bool, error) { return indexer, created, err } +func (b *BleveIndexer) addUpdate(commitSha string, update fileUpdate, repo *models.Repository, batch rupture.FlushingBatch) error { + // Ignore vendored files in code search + if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) { + return nil + } + + stdout, err := git.NewCommand("cat-file", "-s", update.BlobSha). + RunInDir(repo.RepoPath()) + if err != nil { + return err + } + if size, err := strconv.Atoi(strings.TrimSpace(stdout)); err != nil { + return fmt.Errorf("Misformatted git cat-file output: %v", err) + } else if int64(size) > setting.Indexer.MaxIndexerFileSize { + return b.addDelete(update.Filename, repo, batch) + } + + fileContents, err := git.NewCommand("cat-file", "blob", update.BlobSha). + RunInDirBytes(repo.RepoPath()) + if err != nil { + return err + } else if !base.IsTextFile(fileContents) { + // FIXME: UTF-16 files will probably fail here + return nil + } + + id := filenameIndexerID(repo.ID, update.Filename) + return batch.Index(id, &RepoIndexerData{ + RepoID: repo.ID, + CommitID: commitSha, + Content: string(charset.ToUTF8DropErrors(fileContents)), + Language: analyze.GetCodeLanguage(update.Filename, fileContents), + UpdatedAt: time.Now().UTC(), + }) +} + +func (b *BleveIndexer) addDelete(filename string, repo *models.Repository, batch rupture.FlushingBatch) error { + id := filenameIndexerID(repo.ID, filename) + return batch.Delete(id) +} + // init init the indexer func (b *BleveIndexer) init() (bool, error) { var err error - b.indexer, err = openIndexer(b.indexDir, repoIndexerLatestVersion) + b.indexer, err = openBleveIndexer(b.indexDir, repoIndexerLatestVersion) if err != nil { return false, err } @@ -241,7 +225,7 @@ func (b *BleveIndexer) init() (bool, error) { return false, nil } - b.indexer, err = createRepoIndexer(b.indexDir, repoIndexerLatestVersion) + b.indexer, err = createBleveIndexer(b.indexDir, repoIndexerLatestVersion) if err != nil { return false, err } @@ -262,38 +246,19 @@ func (b *BleveIndexer) Close() { } // Index indexes the data -func (b *BleveIndexer) Index(repoID int64) error { - repo, err := models.GetRepositoryByID(repoID) - if err != nil { - return err - } - - sha, err := getDefaultBranchSha(repo) - if err != nil { - return err - } - changes, err := getRepoChanges(repo, sha) - if err != nil { - return err - } else if changes == nil { - return nil - } - +func (b *BleveIndexer) Index(repo *models.Repository, sha string, changes *repoChanges) error { batch := rupture.NewFlushingBatch(b.indexer, maxBatchSize) for _, update := range changes.Updates { - if err := addUpdate(sha, update, repo, batch); err != nil { + if err := b.addUpdate(sha, update, repo, batch); err != nil { return err } } for _, filename := range changes.RemovedFilenames { - if err := addDelete(filename, repo, batch); err != nil { + if err := b.addDelete(filename, repo, batch); err != nil { return err } } - if err = batch.Flush(); err != nil { - return err - } - return repo.UpdateIndexerStatus(models.RepoIndexerTypeCode, sha) + return batch.Flush() } // Delete deletes indexes by ids diff --git a/modules/indexer/code/bleve_test.go b/modules/indexer/code/bleve_test.go index 2b3128ac883b..f79957220ff1 100644 --- a/modules/indexer/code/bleve_test.go +++ b/modules/indexer/code/bleve_test.go @@ -6,21 +6,15 @@ package code import ( "io/ioutil" - "path/filepath" "testing" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) -func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..", "..")) -} - -func TestIndexAndSearch(t *testing.T) { +func TestBleveIndexAndSearch(t *testing.T) { models.PrepareTestEnv(t) dir, err := ioutil.TempDir("", "bleve.index") @@ -31,10 +25,9 @@ func TestIndexAndSearch(t *testing.T) { } defer util.RemoveAll(dir) - setting.Indexer.RepoIndexerEnabled = true idx, _, err := NewBleveIndexer(dir) if err != nil { - assert.Fail(t, "Unable to create indexer Error: %v", err) + assert.Fail(t, "Unable to create bleve indexer Error: %v", err) if idx != nil { idx.Close() } @@ -42,45 +35,5 @@ func TestIndexAndSearch(t *testing.T) { } defer idx.Close() - err = idx.Index(1) - assert.NoError(t, err) - - var ( - keywords = []struct { - Keyword string - IDs []int64 - Langs int - }{ - { - Keyword: "Description", - IDs: []int64{1}, - Langs: 1, - }, - { - Keyword: "repo1", - IDs: []int64{1}, - Langs: 1, - }, - { - Keyword: "non-exist", - IDs: []int64{}, - Langs: 0, - }, - } - ) - - for _, kw := range keywords { - total, res, langs, err := idx.Search(nil, "", kw.Keyword, 1, 10) - assert.NoError(t, err) - assert.EqualValues(t, len(kw.IDs), total) - - assert.NotNil(t, langs) - assert.Len(t, langs, kw.Langs) - - var ids = make([]int64, 0, len(res)) - for _, hit := range res { - ids = append(ids, hit.RepoID) - } - assert.EqualValues(t, kw.IDs, ids) - } + testIndexer("beleve", t, idx) } diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go new file mode 100644 index 000000000000..08b20b80a08f --- /dev/null +++ b/modules/indexer/code/elastic_search.go @@ -0,0 +1,411 @@ +// 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 code + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/analyze" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-enry/go-enry/v2" + "github.com/olivere/elastic/v7" +) + +const ( + esRepoIndexerLatestVersion = 1 +) + +var ( + _ Indexer = &ElasticSearchIndexer{} +) + +// ElasticSearchIndexer implements Indexer interface +type ElasticSearchIndexer struct { + client *elastic.Client + indexerAliasName string +} + +type elasticLogger struct { + *log.Logger +} + +func (l elasticLogger) Printf(format string, args ...interface{}) { + _ = l.Logger.Log(2, l.Logger.GetLevel(), format, args...) +} + +// NewElasticSearchIndexer creates a new elasticsearch indexer +func NewElasticSearchIndexer(url, indexerName string) (*ElasticSearchIndexer, bool, error) { + opts := []elastic.ClientOptionFunc{ + elastic.SetURL(url), + elastic.SetSniff(false), + elastic.SetHealthcheckInterval(10 * time.Second), + elastic.SetGzip(false), + } + + logger := elasticLogger{log.GetLogger(log.DEFAULT)} + + if logger.GetLevel() == log.TRACE || logger.GetLevel() == log.DEBUG { + opts = append(opts, elastic.SetTraceLog(logger)) + } else if logger.GetLevel() == log.ERROR || logger.GetLevel() == log.CRITICAL || logger.GetLevel() == log.FATAL { + opts = append(opts, elastic.SetErrorLog(logger)) + } else if logger.GetLevel() == log.INFO || logger.GetLevel() == log.WARN { + opts = append(opts, elastic.SetInfoLog(logger)) + } + + client, err := elastic.NewClient(opts...) + if err != nil { + return nil, false, err + } + + indexer := &ElasticSearchIndexer{ + client: client, + indexerAliasName: indexerName, + } + exists, err := indexer.init() + + return indexer, !exists, err +} + +const ( + defaultMapping = `{ + "mappings": { + "properties": { + "repo_id": { + "type": "long", + "index": true + }, + "content": { + "type": "text", + "term_vector": "with_positions_offsets", + "index": true + }, + "commit_id": { + "type": "keyword", + "index": true + }, + "language": { + "type": "keyword", + "index": true + }, + "updated_at": { + "type": "long", + "index": true + } + } + } + }` +) + +func (b *ElasticSearchIndexer) realIndexerName() string { + return fmt.Sprintf("%s.v%d", b.indexerAliasName, esRepoIndexerLatestVersion) +} + +// Init will initialize the indexer +func (b *ElasticSearchIndexer) init() (bool, error) { + ctx := context.Background() + exists, err := b.client.IndexExists(b.realIndexerName()).Do(ctx) + if err != nil { + return false, err + } + if !exists { + var mapping = defaultMapping + + createIndex, err := b.client.CreateIndex(b.realIndexerName()).BodyString(mapping).Do(ctx) + if err != nil { + return false, err + } + if !createIndex.Acknowledged { + return false, fmt.Errorf("create index %s with %s failed", b.realIndexerName(), mapping) + } + } + + // check version + r, err := b.client.Aliases().Do(ctx) + if err != nil { + return false, err + } + + realIndexerNames := r.IndicesByAlias(b.indexerAliasName) + if len(realIndexerNames) < 1 { + res, err := b.client.Alias(). + Add(b.realIndexerName(), b.indexerAliasName). + Do(ctx) + if err != nil { + return false, err + } + if !res.Acknowledged { + return false, fmt.Errorf("") + } + } else if len(realIndexerNames) >= 1 && realIndexerNames[0] < b.realIndexerName() { + log.Warn("Found older gitea indexer named %s, but we will create a new one %s and keep the old NOT DELETED. You can delete the old version after the upgrade succeed.", + realIndexerNames[0], b.realIndexerName()) + res, err := b.client.Alias(). + Remove(realIndexerNames[0], b.indexerAliasName). + Add(b.realIndexerName(), b.indexerAliasName). + Do(ctx) + if err != nil { + return false, err + } + if !res.Acknowledged { + return false, fmt.Errorf("") + } + } + + return exists, nil +} + +func (b *ElasticSearchIndexer) addUpdate(sha string, update fileUpdate, repo *models.Repository) ([]elastic.BulkableRequest, error) { + // Ignore vendored files in code search + if setting.Indexer.ExcludeVendored && enry.IsVendor(update.Filename) { + return nil, nil + } + + stdout, err := git.NewCommand("cat-file", "-s", update.BlobSha). + RunInDir(repo.RepoPath()) + if err != nil { + return nil, err + } + if size, err := strconv.Atoi(strings.TrimSpace(stdout)); err != nil { + return nil, fmt.Errorf("Misformatted git cat-file output: %v", err) + } else if int64(size) > setting.Indexer.MaxIndexerFileSize { + return []elastic.BulkableRequest{b.addDelete(update.Filename, repo)}, nil + } + + fileContents, err := git.NewCommand("cat-file", "blob", update.BlobSha). + RunInDirBytes(repo.RepoPath()) + if err != nil { + return nil, err + } else if !base.IsTextFile(fileContents) { + // FIXME: UTF-16 files will probably fail here + return nil, nil + } + + id := filenameIndexerID(repo.ID, update.Filename) + + return []elastic.BulkableRequest{ + elastic.NewBulkIndexRequest(). + Index(b.indexerAliasName). + Id(id). + Doc(map[string]interface{}{ + "repo_id": repo.ID, + "content": string(charset.ToUTF8DropErrors(fileContents)), + "commit_id": sha, + "language": analyze.GetCodeLanguage(update.Filename, fileContents), + "updated_at": timeutil.TimeStampNow(), + }), + }, nil +} + +func (b *ElasticSearchIndexer) addDelete(filename string, repo *models.Repository) elastic.BulkableRequest { + id := filenameIndexerID(repo.ID, filename) + return elastic.NewBulkDeleteRequest(). + Index(b.indexerAliasName). + Id(id) +} + +// Index will save the index data +func (b *ElasticSearchIndexer) Index(repo *models.Repository, sha string, changes *repoChanges) error { + reqs := make([]elastic.BulkableRequest, 0) + for _, update := range changes.Updates { + updateReqs, err := b.addUpdate(sha, update, repo) + if err != nil { + return err + } + if len(updateReqs) > 0 { + reqs = append(reqs, updateReqs...) + } + } + + for _, filename := range changes.RemovedFilenames { + reqs = append(reqs, b.addDelete(filename, repo)) + } + + if len(reqs) > 0 { + _, err := b.client.Bulk(). + Index(b.indexerAliasName). + Add(reqs...). + Do(context.Background()) + return err + } + return nil +} + +// Delete deletes indexes by ids +func (b *ElasticSearchIndexer) Delete(repoID int64) error { + _, err := b.client.DeleteByQuery(b.indexerAliasName). + Query(elastic.NewTermsQuery("repo_id", repoID)). + Do(context.Background()) + return err +} + +// indexPos find words positions for start and the following end on content. It will +// return the beginning position of the frist start and the ending position of the +// first end following the start string. +// If not found any of the positions, it will return -1, -1. +func indexPos(content, start, end string) (int, int) { + startIdx := strings.Index(content, start) + if startIdx < 0 { + return -1, -1 + } + endIdx := strings.Index(content[startIdx+len(start):], end) + if endIdx < 0 { + return -1, -1 + } + return startIdx, startIdx + len(start) + endIdx + len(end) +} + +func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int) (int64, []*SearchResult, []*SearchResultLanguages, error) { + hits := make([]*SearchResult, 0, pageSize) + for _, hit := range searchResult.Hits.Hits { + // FIXME: There is no way to get the position the keyword on the content currently on the same request. + // So we get it from content, this may made the query slower. See + // https://discuss.elastic.co/t/fetching-position-of-keyword-in-matched-document/94291 + var startIndex, endIndex int = -1, -1 + c, ok := hit.Highlight["content"] + if ok && len(c) > 0 { + // FIXME: Since the high lighting content will include and for the keywords, + // now we should find the poisitions. But how to avoid html content which contains the + // and tags? If elastic search has handled that? + startIndex, endIndex = indexPos(c[0], "", "") + if startIndex == -1 { + panic(fmt.Sprintf("1===%s,,,%#v,,,%s", kw, hit.Highlight, c[0])) + } + } else { + panic(fmt.Sprintf("2===%#v", hit.Highlight)) + } + + repoID, fileName := parseIndexerID(hit.Id) + var res = make(map[string]interface{}) + if err := json.Unmarshal(hit.Source, &res); err != nil { + return 0, nil, nil, err + } + + language := res["language"].(string) + + hits = append(hits, &SearchResult{ + RepoID: repoID, + Filename: fileName, + CommitID: res["commit_id"].(string), + Content: res["content"].(string), + UpdatedUnix: timeutil.TimeStamp(res["updated_at"].(float64)), + Language: language, + StartIndex: startIndex, + EndIndex: endIndex - 9, // remove the length since we give Content the original data + Color: enry.GetColor(language), + }) + } + + return searchResult.TotalHits(), hits, extractAggs(searchResult), nil +} + +func extractAggs(searchResult *elastic.SearchResult) []*SearchResultLanguages { + var searchResultLanguages []*SearchResultLanguages + agg, found := searchResult.Aggregations.Terms("language") + if found { + searchResultLanguages = make([]*SearchResultLanguages, 0, 10) + + for _, bucket := range agg.Buckets { + searchResultLanguages = append(searchResultLanguages, &SearchResultLanguages{ + Language: bucket.Key.(string), + Color: enry.GetColor(bucket.Key.(string)), + Count: int(bucket.DocCount), + }) + } + } + return searchResultLanguages +} + +// Search searches for codes and language stats by given conditions. +func (b *ElasticSearchIndexer) Search(repoIDs []int64, language, keyword string, page, pageSize int) (int64, []*SearchResult, []*SearchResultLanguages, error) { + kwQuery := elastic.NewMultiMatchQuery(keyword, "content") + query := elastic.NewBoolQuery() + query = query.Must(kwQuery) + if len(repoIDs) > 0 { + var repoStrs = make([]interface{}, 0, len(repoIDs)) + for _, repoID := range repoIDs { + repoStrs = append(repoStrs, repoID) + } + repoQuery := elastic.NewTermsQuery("repo_id", repoStrs...) + query = query.Must(repoQuery) + } + + var ( + start int + kw = "" + keyword + "" + aggregation = elastic.NewTermsAggregation().Field("language").Size(10).OrderByCountDesc() + ) + + if page > 0 { + start = (page - 1) * pageSize + } + + if len(language) == 0 { + searchResult, err := b.client.Search(). + Index(b.indexerAliasName). + Aggregation("language", aggregation). + Query(query). + Highlight( + elastic.NewHighlight(). + Field("content"). + NumOfFragments(0). // return all highting content on fragments + HighlighterType("fvh"), + ). + Sort("repo_id", true). + From(start).Size(pageSize). + Do(context.Background()) + if err != nil { + return 0, nil, nil, err + } + + return convertResult(searchResult, kw, pageSize) + } + + langQuery := elastic.NewMatchQuery("language", language) + countResult, err := b.client.Search(). + Index(b.indexerAliasName). + Aggregation("language", aggregation). + Query(query). + Size(0). // We only needs stats information + Do(context.Background()) + if err != nil { + return 0, nil, nil, err + } + + query = query.Must(langQuery) + searchResult, err := b.client.Search(). + Index(b.indexerAliasName). + Query(query). + Highlight( + elastic.NewHighlight(). + Field("content"). + NumOfFragments(0). // return all highting content on fragments + HighlighterType("fvh"), + ). + Sort("repo_id", true). + From(start).Size(pageSize). + Do(context.Background()) + if err != nil { + return 0, nil, nil, err + } + + total, hits, _, err := convertResult(searchResult, kw, pageSize) + + return total, hits, extractAggs(countResult), err +} + +// Close implements indexer +func (b *ElasticSearchIndexer) Close() {} diff --git a/modules/indexer/code/elastic_search_test.go b/modules/indexer/code/elastic_search_test.go new file mode 100644 index 000000000000..7cf62e0c5f4e --- /dev/null +++ b/modules/indexer/code/elastic_search_test.go @@ -0,0 +1,42 @@ +// 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 code + +import ( + "os" + "testing" + + "code.gitea.io/gitea/models" + + "github.com/stretchr/testify/assert" +) + +func TestESIndexAndSearch(t *testing.T) { + models.PrepareTestEnv(t) + + u := os.Getenv("TEST_INDEXER_CODE_ES_URL") + if u == "" { + t.SkipNow() + return + } + + indexer, _, err := NewElasticSearchIndexer(u, "gitea_codes") + if err != nil { + assert.Fail(t, "Unable to create ES indexer Error: %v", err) + if indexer != nil { + indexer.Close() + } + return + } + defer indexer.Close() + + testIndexer("elastic_search", t, indexer) +} + +func TestIndexPos(t *testing.T) { + startIdx, endIdx := indexPos("test index start and end", "start", "end") + assert.EqualValues(t, 11, startIdx) + assert.EqualValues(t, 24, endIdx) +} diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index a0f91ce4b597..35c298a5486b 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -7,10 +7,14 @@ package code import ( "context" "os" + "strconv" + "strings" "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/timeutil" ) @@ -35,14 +39,73 @@ type SearchResultLanguages struct { Count int } -// Indexer defines an interface to indexer issues contents +// Indexer defines an interface to index and search code contents type Indexer interface { - Index(repoID int64) error + Index(repo *models.Repository, sha string, changes *repoChanges) error Delete(repoID int64) error Search(repoIDs []int64, language, keyword string, page, pageSize int) (int64, []*SearchResult, []*SearchResultLanguages, error) Close() } +func filenameIndexerID(repoID int64, filename string) string { + return indexerID(repoID) + "_" + filename +} + +func indexerID(id int64) string { + return strconv.FormatInt(id, 36) +} + +func parseIndexerID(indexerID string) (int64, string) { + index := strings.IndexByte(indexerID, '_') + if index == -1 { + log.Error("Unexpected ID in repo indexer: %s", indexerID) + } + repoID, _ := strconv.ParseInt(indexerID[:index], 36, 64) + return repoID, indexerID[index+1:] +} + +func filenameOfIndexerID(indexerID string) string { + index := strings.IndexByte(indexerID, '_') + if index == -1 { + log.Error("Unexpected ID in repo indexer: %s", indexerID) + } + return indexerID[index+1:] +} + +// IndexerData represents data stored in the code indexer +type IndexerData struct { + RepoID int64 + IsDelete bool +} + +var ( + indexerQueue queue.Queue +) + +func index(indexer Indexer, repoID int64) error { + repo, err := models.GetRepositoryByID(repoID) + if err != nil { + return err + } + + sha, err := getDefaultBranchSha(repo) + if err != nil { + return err + } + changes, err := getRepoChanges(repo, sha) + if err != nil { + return err + } else if changes == nil { + return nil + } + + if err := indexer.Index(repo, sha, changes); err != nil { + return err + } + + return repo.UpdateIndexerStatus(models.RepoIndexerTypeCode, sha) +} + // Init initialize the repo indexer func Init() { if !setting.Indexer.RepoIndexerEnabled { @@ -50,8 +113,6 @@ func Init() { return } - initQueue(setting.Indexer.UpdateQueueLength) - ctx, cancel := context.WithCancel(context.Background()) graceful.GetManager().RunAtTerminate(ctx, func() { @@ -61,36 +122,105 @@ func Init() { }) waitChannel := make(chan time.Duration) + + // Create the Queue + switch setting.Indexer.RepoType { + case "bleve", "elasticsearch": + handler := func(data ...queue.Data) { + idx, err := indexer.get() + if idx == nil || err != nil { + log.Error("Codes indexer handler: unable to get indexer!") + return + } + + 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: %v %t", indexerData.RepoID, indexerData.IsDelete) + + if indexerData.IsDelete { + if err := indexer.Delete(indexerData.RepoID); err != nil { + log.Error("indexer.Delete: %v", err) + } + } else { + if err := index(indexer, indexerData.RepoID); err != nil { + log.Error("index: %v", err) + continue + } + } + } + } + + indexerQueue = queue.CreateQueue("code_indexer", handler, &IndexerData{}) + if indexerQueue == nil { + log.Fatal("Unable to create codes indexer queue") + } + default: + log.Fatal("Unknown codes indexer type; %s", setting.Indexer.RepoType) + } + go func() { start := time.Now() - log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoPath) - defer func() { - if err := recover(); err != nil { - log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2)) - log.Error("The indexer files are likely corrupted and may need to be deleted") - log.Error("You can completely remove the %q directory to make Gitea recreate the indexes", setting.Indexer.RepoPath) + var ( + rIndexer Indexer + populate bool + err error + ) + switch setting.Indexer.RepoType { + case "bleve": + log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoPath) + defer func() { + if err := recover(); err != nil { + log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2)) + log.Error("The indexer files are likely corrupted and may need to be deleted") + log.Error("You can completely remove the \"%s\" directory to make Gitea recreate the indexes", setting.Indexer.RepoPath) + } + }() + + rIndexer, populate, err = NewBleveIndexer(setting.Indexer.RepoPath) + if err != nil { + if rIndexer != nil { + rIndexer.Close() + } cancel() indexer.Close() close(waitChannel) - log.Fatal("PID: %d Unable to initialize the Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err) + log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err) } - }() - bleveIndexer, created, err := NewBleveIndexer(setting.Indexer.RepoPath) - if err != nil { - if bleveIndexer != nil { - bleveIndexer.Close() + case "elasticsearch": + log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoConnStr) + defer func() { + if err := recover(); err != nil { + log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2)) + log.Error("The indexer files are likely corrupted and may need to be deleted") + log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", setting.Indexer.RepoConnStr) + } + }() + + rIndexer, populate, err = NewElasticSearchIndexer(setting.Indexer.RepoConnStr, setting.Indexer.RepoIndexerName) + if err != nil { + if rIndexer != nil { + rIndexer.Close() + } + cancel() + indexer.Close() + close(waitChannel) + log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err) } - cancel() - indexer.Close() - close(waitChannel) - log.Fatal("PID: %d Unable to initialize the Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err) + default: + log.Fatal("PID: %d Unknown Indexer type: %s", os.Getpid(), setting.Indexer.RepoType) } - indexer.set(bleveIndexer) - go processRepoIndexerOperationQueue(indexer) + indexer.set(rIndexer) + + // Start processing the queue + go graceful.GetManager().RunWithShutdownFns(indexerQueue.Run) - if created { - go populateRepoIndexer() + if populate { + go graceful.GetManager().RunWithShutdownContext(populateRepoIndexer) } select { case waitChannel <- time.Since(start): @@ -127,3 +257,77 @@ func Init() { }() } } + +// DeleteRepoFromIndexer remove all of a repository's entries from the indexer +func DeleteRepoFromIndexer(repo *models.Repository) { + indexData := &IndexerData{RepoID: repo.ID, IsDelete: true} + if err := indexerQueue.Push(indexData); err != nil { + log.Error("Delete repo index data %v failed: %v", indexData, err) + } +} + +// UpdateRepoIndexer update a repository's entries in the indexer +func UpdateRepoIndexer(repo *models.Repository) { + indexData := &IndexerData{RepoID: repo.ID} + if err := indexerQueue.Push(indexData); err != nil { + log.Error("Update repo index data %v failed: %v", indexData, err) + } +} + +// populateRepoIndexer populate the repo indexer with pre-existing data. This +// should only be run when the indexer is created for the first time. +func populateRepoIndexer(ctx context.Context) { + log.Info("Populating the repo indexer with existing repositories") + + exist, err := models.IsTableNotEmpty("repository") + if err != nil { + log.Fatal("System error: %v", err) + } else if !exist { + return + } + + // if there is any existing repo indexer metadata in the DB, delete it + // since we are starting afresh. Also, xorm requires deletes to have a + // condition, and we want to delete everything, thus 1=1. + if err := models.DeleteAllRecords("repo_indexer_status"); err != nil { + log.Fatal("System error: %v", err) + } + + var maxRepoID int64 + if maxRepoID, err = models.GetMaxID("repository"); err != nil { + log.Fatal("System error: %v", err) + } + + // start with the maximum existing repo ID and work backwards, so that we + // don't include repos that are created after gitea starts; such repos will + // already be added to the indexer, and we don't need to add them again. + for maxRepoID > 0 { + select { + case <-ctx.Done(): + log.Info("Repository Indexer population shutdown before completion") + return + default: + } + ids, err := models.GetUnindexedRepos(models.RepoIndexerTypeCode, maxRepoID, 0, 50) + if err != nil { + log.Error("populateRepoIndexer: %v", err) + return + } else if len(ids) == 0 { + break + } + for _, id := range ids { + select { + case <-ctx.Done(): + log.Info("Repository Indexer population shutdown before completion") + return + default: + } + if err := indexerQueue.Push(&IndexerData{RepoID: id}); err != nil { + log.Error("indexerQueue.Push: %v", err) + return + } + maxRepoID = id - 1 + } + } + log.Info("Done (re)populating the repo indexer with existing repositories") +} diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go new file mode 100644 index 000000000000..0b4851a48a6e --- /dev/null +++ b/modules/indexer/code/indexer_test.go @@ -0,0 +1,83 @@ +// 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 code + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + models.MainTest(m, filepath.Join("..", "..", "..")) +} + +func testIndexer(name string, t *testing.T, indexer Indexer) { + t.Run(name, func(t *testing.T) { + var repoID int64 = 1 + err := index(indexer, repoID) + assert.NoError(t, err) + var ( + keywords = []struct { + RepoIDs []int64 + Keyword string + IDs []int64 + Langs int + }{ + { + RepoIDs: nil, + Keyword: "Description", + IDs: []int64{repoID}, + Langs: 1, + }, + { + RepoIDs: []int64{2}, + Keyword: "Description", + IDs: []int64{}, + Langs: 0, + }, + { + RepoIDs: nil, + Keyword: "repo1", + IDs: []int64{repoID}, + Langs: 1, + }, + { + RepoIDs: []int64{2}, + Keyword: "repo1", + IDs: []int64{}, + Langs: 0, + }, + { + RepoIDs: nil, + Keyword: "non-exist", + IDs: []int64{}, + Langs: 0, + }, + } + ) + + for _, kw := range keywords { + t.Run(kw.Keyword, func(t *testing.T) { + total, res, langs, err := indexer.Search(kw.RepoIDs, "", kw.Keyword, 1, 10) + assert.NoError(t, err) + assert.EqualValues(t, len(kw.IDs), total) + assert.EqualValues(t, kw.Langs, len(langs)) + + var ids = make([]int64, 0, len(res)) + for _, hit := range res { + ids = append(ids, hit.RepoID) + assert.EqualValues(t, "# repo1\n\nDescription for repo1", hit.Content) + } + assert.EqualValues(t, kw.IDs, ids) + }) + } + + assert.NoError(t, indexer.Delete(repoID)) + }) +} diff --git a/modules/indexer/code/queue.go b/modules/indexer/code/queue.go deleted file mode 100644 index 94675559eaf0..000000000000 --- a/modules/indexer/code/queue.go +++ /dev/null @@ -1,134 +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 code - -import ( - "os" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" -) - -type repoIndexerOperation struct { - repoID int64 - deleted bool - watchers []chan<- error -} - -var repoIndexerOperationQueue chan repoIndexerOperation - -func initQueue(queueLength int) { - repoIndexerOperationQueue = make(chan repoIndexerOperation, queueLength) -} - -func processRepoIndexerOperationQueue(indexer Indexer) { - for { - select { - case op := <-repoIndexerOperationQueue: - var err error - if op.deleted { - if err = indexer.Delete(op.repoID); err != nil { - log.Error("indexer.Delete: %v", err) - } - } else { - if err = indexer.Index(op.repoID); err != nil { - log.Error("indexer.Index: %v", err) - } - } - for _, watcher := range op.watchers { - watcher <- err - } - case <-graceful.GetManager().IsShutdown(): - log.Info("PID: %d Repository indexer queue processing stopped", os.Getpid()) - return - } - } -} - -// DeleteRepoFromIndexer remove all of a repository's entries from the indexer -func DeleteRepoFromIndexer(repo *models.Repository, watchers ...chan<- error) { - addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: true, watchers: watchers}) -} - -// UpdateRepoIndexer update a repository's entries in the indexer -func UpdateRepoIndexer(repo *models.Repository, watchers ...chan<- error) { - addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: false, watchers: watchers}) -} - -func addOperationToQueue(op repoIndexerOperation) { - if !setting.Indexer.RepoIndexerEnabled { - return - } - select { - case repoIndexerOperationQueue <- op: - break - default: - go func() { - repoIndexerOperationQueue <- op - }() - } -} - -// populateRepoIndexer populate the repo indexer with pre-existing data. This -// should only be run when the indexer is created for the first time. -func populateRepoIndexer() { - log.Info("Populating the repo indexer with existing repositories") - - isShutdown := graceful.GetManager().IsShutdown() - - exist, err := models.IsTableNotEmpty("repository") - if err != nil { - log.Fatal("System error: %v", err) - } else if !exist { - return - } - - // if there is any existing repo indexer metadata in the DB, delete it - // since we are starting afresh. Also, xorm requires deletes to have a - // condition, and we want to delete everything, thus 1=1. - if err := models.DeleteAllRecords("repo_indexer_status"); err != nil { - log.Fatal("System error: %v", err) - } - - var maxRepoID int64 - if maxRepoID, err = models.GetMaxID("repository"); err != nil { - log.Fatal("System error: %v", err) - } - - // start with the maximum existing repo ID and work backwards, so that we - // don't include repos that are created after gitea starts; such repos will - // already be added to the indexer, and we don't need to add them again. - for maxRepoID > 0 { - select { - case <-isShutdown: - log.Info("Repository Indexer population shutdown before completion") - return - default: - } - ids, err := models.GetUnindexedRepos(models.RepoIndexerTypeCode, maxRepoID, 0, 50) - if err != nil { - log.Error("populateRepoIndexer: %v", err) - return - } else if len(ids) == 0 { - break - } - for _, id := range ids { - select { - case <-isShutdown: - log.Info("Repository Indexer population shutdown before completion") - return - default: - } - repoIndexerOperationQueue <- repoIndexerOperation{ - repoID: id, - deleted: false, - } - maxRepoID = id - 1 - } - } - log.Info("Done (re)populating the repo indexer with existing repositories") -} diff --git a/modules/indexer/code/wrapped.go b/modules/indexer/code/wrapped.go index 926597a382ba..d83954487498 100644 --- a/modules/indexer/code/wrapped.go +++ b/modules/indexer/code/wrapped.go @@ -7,6 +7,8 @@ package code import ( "fmt" "sync" + + "code.gitea.io/gitea/models" ) var ( @@ -55,12 +57,12 @@ func (w *wrappedIndexer) get() (Indexer, error) { return w.internal, nil } -func (w *wrappedIndexer) Index(repoID int64) error { +func (w *wrappedIndexer) Index(repo *models.Repository, sha string, changes *repoChanges) error { indexer, err := w.get() if err != nil { return err } - return indexer.Index(repoID) + return indexer.Index(repo, sha, changes) } func (w *wrappedIndexer) Delete(repoID int64) error { diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go index b0fa77e2550f..cf0a05d644ce 100644 --- a/modules/lfs/content_store.go +++ b/modules/lfs/content_store.go @@ -10,11 +10,10 @@ import ( "errors" "io" "os" - "path/filepath" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/storage" ) var ( @@ -24,17 +23,15 @@ var ( // ContentStore provides a simple file system based storage. type ContentStore struct { - BasePath string + storage.ObjectStorage } // Get takes a Meta object and retrieves the content from the store, returning // it as an io.Reader. If fromByte > 0, the reader starts from that byte func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) { - path := filepath.Join(s.BasePath, transformKey(meta.Oid)) - - f, err := os.Open(path) + f, err := s.Open(meta.RelativePath()) if err != nil { - log.Error("Whilst trying to read LFS OID[%s]: Unable to open %s Error: %v", meta.Oid, path, err) + log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", meta.Oid, err) return nil, err } if fromByte > 0 { @@ -48,82 +45,55 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC // Put takes a Meta object and an io.Reader and writes the content to the store. func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error { - path := filepath.Join(s.BasePath, transformKey(meta.Oid)) - tmpPath := path + ".tmp" - - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0750); err != nil { - log.Error("Whilst putting LFS OID[%s]: Unable to create the LFS directory: %s Error: %v", meta.Oid, dir, err) - return err - } - - file, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0640) - if err != nil { - log.Error("Whilst putting LFS OID[%s]: Unable to open temporary file for writing: %s Error: %v", tmpPath, err) - return err - } - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary path: %s: Error: %v", tmpPath, err) - } - }() - hash := sha256.New() - hw := io.MultiWriter(hash, file) - - written, err := io.Copy(hw, r) + rd := io.TeeReader(r, hash) + p := meta.RelativePath() + written, err := s.Save(p, rd) if err != nil { - log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, tmpPath, err) - file.Close() + log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, p, err) return err } - file.Close() if written != meta.Size { + if err := s.Delete(p); err != nil { + log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err) + } return errSizeMismatch } shaStr := hex.EncodeToString(hash.Sum(nil)) if shaStr != meta.Oid { + if err := s.Delete(p); err != nil { + log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err) + } return errHashMismatch } - if err := os.Rename(tmpPath, path); err != nil { - log.Error("Whilst putting LFS OID[%s]: Unable to move tmp file to final destination: %s Error: %v", meta.Oid, path, err) - return err - } - return nil } // Exists returns true if the object exists in the content store. -func (s *ContentStore) Exists(meta *models.LFSMetaObject) bool { - path := filepath.Join(s.BasePath, transformKey(meta.Oid)) - if _, err := os.Stat(path); os.IsNotExist(err) { - return false +func (s *ContentStore) Exists(meta *models.LFSMetaObject) (bool, error) { + _, err := s.ObjectStorage.Stat(meta.RelativePath()) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err } - return true + return true, nil } // Verify returns true if the object exists in the content store and size is correct. func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) { - path := filepath.Join(s.BasePath, transformKey(meta.Oid)) - - fi, err := os.Stat(path) - if os.IsNotExist(err) || err == nil && fi.Size() != meta.Size { + p := meta.RelativePath() + fi, err := s.ObjectStorage.Stat(p) + if os.IsNotExist(err) || (err == nil && fi.Size() != meta.Size) { return false, nil } else if err != nil { - log.Error("Unable stat file: %s for LFS OID[%s] Error: %v", path, meta.Oid, err) + log.Error("Unable stat file: %s for LFS OID[%s] Error: %v", p, meta.Oid, err) return false, err } return true, nil } - -func transformKey(key string) string { - if len(key) < 5 { - return key - } - - return filepath.Join(key[0:2], key[2:4], key[4:]) -} diff --git a/modules/lfs/pointers.go b/modules/lfs/pointers.go index bc27ee37a7fb..c6fbf090e516 100644 --- a/modules/lfs/pointers.go +++ b/modules/lfs/pointers.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" ) // ReadPointerFile will return a partially filled LFSMetaObject if the provided reader is a pointer file @@ -53,9 +54,10 @@ func IsPointerFile(buf *[]byte) *models.LFSMetaObject { return nil } - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} meta := &models.LFSMetaObject{Oid: oid, Size: size} - if !contentStore.Exists(meta) { + exist, err := contentStore.Exists(meta) + if err != nil || !exist { return nil } @@ -64,6 +66,6 @@ func IsPointerFile(buf *[]byte) *models.LFSMetaObject { // ReadMetaObject will read a models.LFSMetaObject and return a reader func ReadMetaObject(meta *models.LFSMetaObject) (io.ReadCloser, error) { - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} return contentStore.Get(meta, 0) } diff --git a/modules/lfs/server.go b/modules/lfs/server.go index f227ebe2eb15..2801f8410cc0 100644 --- a/modules/lfs/server.go +++ b/modules/lfs/server.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "gitea.com/macaron/macaron" "github.com/dgrijalva/jwt-go" @@ -187,7 +188,7 @@ func getContentHandler(ctx *context.Context) { } } - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} content, err := contentStore.Get(meta, fromByte) if err != nil { // Errors are logged in contentStore.Get @@ -288,8 +289,14 @@ func PostHandler(ctx *context.Context) { ctx.Resp.Header().Set("Content-Type", metaMediaType) sentStatus := 202 - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} - if meta.Existing && contentStore.Exists(meta) { + contentStore := &ContentStore{ObjectStorage: storage.LFS} + exist, err := contentStore.Exists(meta) + if err != nil { + log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", rv.Oid, rv.User, rv.Repo, err) + writeStatus(ctx, 500) + return + } + if meta.Existing && exist { sentStatus = 200 } ctx.Resp.WriteHeader(sentStatus) @@ -343,12 +350,20 @@ func BatchHandler(ctx *context.Context) { return } - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} meta, err := repository.GetLFSMetaObjectByOid(object.Oid) - if err == nil && contentStore.Exists(meta) { // Object is found and exists - responseObjects = append(responseObjects, Represent(object, meta, true, false)) - continue + if err == nil { // Object is found and exists + exist, err := contentStore.Exists(meta) + if err != nil { + log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", object.Oid, object.User, object.Repo, err) + writeStatus(ctx, 500) + return + } + if exist { + responseObjects = append(responseObjects, Represent(object, meta, true, false)) + continue + } } if requireWrite && setting.LFS.MaxFileSize > 0 && object.Size > setting.LFS.MaxFileSize { @@ -360,7 +375,13 @@ func BatchHandler(ctx *context.Context) { // Object is not found meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID}) if err == nil { - responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta))) + exist, err := contentStore.Exists(meta) + if err != nil { + log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", object.Oid, object.User, object.Repo, err) + writeStatus(ctx, 500) + return + } + responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !exist)) } else { log.Error("Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v", object.Oid, object.Size, object.User, object.Repo, err) } @@ -387,7 +408,7 @@ func PutHandler(ctx *context.Context) { return } - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} bodyReader := ctx.Req.Body().ReadCloser() defer bodyReader.Close() if err := contentStore.Put(meta, bodyReader); err != nil { @@ -429,7 +450,7 @@ func VerifyHandler(ctx *context.Context) { return } - contentStore := &ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &ContentStore{ObjectStorage: storage.LFS} ok, err := contentStore.Verify(meta) if err != nil { // Error will be logged in Verify diff --git a/modules/log/stack.go b/modules/log/stack.go index ffe9aa67cc58..568c10cd00d0 100644 --- a/modules/log/stack.go +++ b/modules/log/stack.go @@ -69,7 +69,7 @@ func functionName(programCounter uintptr) []byte { name = name[period+1:] } // And we should just replace the interpunct with a dot - name = bytes.Replace(name, []byte("·"), []byte("."), -1) + name = bytes.ReplaceAll(name, []byte("·"), []byte(".")) return name } diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go index ad4cd7f2e16b..9baf8a4998b7 100644 --- a/modules/markup/common/footnote.go +++ b/modules/markup/common/footnote.go @@ -34,7 +34,7 @@ func CleanValue(value []byte) []byte { needsDash := false for _, r := range rs { switch { - case unicode.IsLetter(r) || unicode.IsNumber(r): + case unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_': if needsDash && len(result) > 0 { result = append(result, '-') } diff --git a/modules/markup/html.go b/modules/markup/html.go index bef6269a6915..f5f811b59b6f 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -683,9 +683,9 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) { absoluteLink := isLinkStr(link) if !absoluteLink { if image { - link = strings.Replace(link, " ", "+", -1) + link = strings.ReplaceAll(link, " ", "+") } else { - link = strings.Replace(link, " ", "-", -1) + link = strings.ReplaceAll(link, " ", "-") } if !strings.Contains(link, "/") { link = url.PathEscape(link) @@ -902,7 +902,7 @@ func emojiShortCodeProcessor(ctx *postProcessCtx, node *html.Node) { } alias := node.Data[m[0]:m[1]] - alias = strings.Replace(alias, ":", "", -1) + alias = strings.ReplaceAll(alias, ":", "") converted := emoji.FromAlias(alias) if converted == nil { // check if this is a custom reaction diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 7f820d399055..a018d74840e3 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -35,7 +35,7 @@ func TestRender_Commits(t *testing.T) { var sha = "65f1bf27bc3bf70f64657658635e66094edbcb4d" var commit = util.URLJoin(AppSubURL, "commit", sha) var subtree = util.URLJoin(commit, "src") - var tree = strings.Replace(subtree, "/commit/", "/tree/", -1) + var tree = strings.ReplaceAll(subtree, "/commit/", "/tree/") test(sha, `

65f1bf27bc

`) test(sha[:7], `

65f1bf2

`) @@ -235,7 +235,7 @@ func TestRender_emoji(t *testing.T) { setting.StaticURLPrefix = AppURL test := func(input, expected string) { - expected = strings.Replace(expected, "&", "&", -1) + expected = strings.ReplaceAll(expected, "&", "&") buffer := RenderString("a.md", input, setting.AppSubURL, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } diff --git a/modules/markup/markdown/meta.go b/modules/markup/markdown/meta.go new file mode 100644 index 000000000000..faf92ae2c6b7 --- /dev/null +++ b/modules/markup/markdown/meta.go @@ -0,0 +1,51 @@ +// 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 markdown + +import ( + "errors" + "strings" + + "gopkg.in/yaml.v2" +) + +func isYAMLSeparator(line string) bool { + line = strings.TrimSpace(line) + for i := 0; i < len(line); i++ { + if line[i] != '-' { + return false + } + } + return len(line) > 2 +} + +// ExtractMetadata consumes a markdown file, parses YAML frontmatter, +// and returns the frontmatter metadata separated from the markdown content +func ExtractMetadata(contents string, out interface{}) (string, error) { + var front, body []string + lines := strings.Split(contents, "\n") + for idx, line := range lines { + if idx == 0 { + // First line has to be a separator + if !isYAMLSeparator(line) { + return "", errors.New("frontmatter must start with a separator line") + } + continue + } + if isYAMLSeparator(line) { + front, body = lines[1:idx], lines[idx+1:] + break + } + } + + if len(front) == 0 { + return "", errors.New("could not determine metadata") + } + + if err := yaml.Unmarshal([]byte(strings.Join(front, "\n")), out); err != nil { + return "", err + } + return strings.Join(body, "\n"), nil +} diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go new file mode 100644 index 000000000000..a585f0382f81 --- /dev/null +++ b/modules/markup/markdown/meta_test.go @@ -0,0 +1,63 @@ +// 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 markdown + +import ( + "fmt" + "testing" + + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestExtractMetadata(t *testing.T) { + t.Run("ValidFrontAndBody", func(t *testing.T) { + var meta structs.IssueTemplate + body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta) + assert.NoError(t, err) + assert.Equal(t, body, bodyTest) + assert.Equal(t, metaTest, meta) + assert.True(t, meta.Valid()) + }) + + t.Run("NoFirstSeparator", func(t *testing.T) { + var meta structs.IssueTemplate + _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta) + assert.Error(t, err) + }) + + t.Run("NoLastSeparator", func(t *testing.T) { + var meta structs.IssueTemplate + _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta) + assert.Error(t, err) + }) + + t.Run("NoBody", func(t *testing.T) { + var meta structs.IssueTemplate + body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) + assert.NoError(t, err) + assert.Equal(t, body, "") + assert.Equal(t, metaTest, meta) + assert.True(t, meta.Valid()) + }) +} + +var ( + sepTest = "-----" + frontTest = `name: Test +about: "A Test" +title: "Test Title" +labels: + - bug + - "test label"` + bodyTest = "This is the body" + metaTest = structs.IssueTemplate{ + Name: "Test", + About: "A Test", + Title: "Test Title", + Labels: []string{"bug", "test label"}, + } +) diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 86222cc88e76..ddd445aba2ad 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "html" + "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -94,10 +95,23 @@ func (r *Renderer) WriteRegularLink(l org.RegularLink) { } switch l.Kind() { case "image": - r.WriteString(fmt.Sprintf(`%s`, link, description, description)) + imageSrc := getMediaURL(link) + fmt.Fprintf(r, `%s`, imageSrc, description, description) case "video": - r.WriteString(fmt.Sprintf(``, link, description, description)) + videoSrc := getMediaURL(link) + fmt.Fprintf(r, ``, videoSrc, description, description) default: - r.WriteString(fmt.Sprintf(`%s`, link, description, description)) + fmt.Fprintf(r, `%s`, link, description, description) } } + +func getMediaURL(l []byte) string { + srcURL := string(l) + + // Check if link is valid + if len(srcURL) > 0 && !markup.IsLink(l) { + srcURL = strings.Replace(srcURL, "/src/", "/media/", 1) + } + + return srcURL +} diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index 40323912b427..020a3f592ad8 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -27,12 +27,12 @@ func TestRender_StandardLinks(t *testing.T) { assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } - googleRendered := "

\nhttps://google.com/\n

" + googleRendered := "

https://google.com/

" test("[[https://google.com/]]", googleRendered) lnk := util.URLJoin(AppSubURL, "WikiPage") test("[[WikiPage][WikiPage]]", - "

\nWikiPage\n

") + "

WikiPage

") } func TestRender_Images(t *testing.T) { @@ -48,5 +48,5 @@ func TestRender_Images(t *testing.T) { result := util.URLJoin(AppSubURL, url) test("[[file:"+url+"]]", - "

\n\""+result+"\"\n

") + "

\""+result+"\"

") } diff --git a/modules/migrations/base/downloader.go b/modules/migrations/base/downloader.go index c31f3df1d19e..5c47ed53052c 100644 --- a/modules/migrations/base/downloader.go +++ b/modules/migrations/base/downloader.go @@ -7,13 +7,20 @@ package base import ( "context" + "io" "time" "code.gitea.io/gitea/modules/structs" ) +// AssetDownloader downloads an asset (attachment) for a release +type AssetDownloader interface { + GetAsset(relTag string, relID, id int64) (io.ReadCloser, error) +} + // Downloader downloads the site repo informations type Downloader interface { + AssetDownloader SetContext(context.Context) GetRepoInfo() (*Repository, error) GetTopics() ([]string, error) @@ -22,14 +29,13 @@ type Downloader interface { GetLabels() ([]*Label, error) GetIssues(page, perPage int) ([]*Issue, bool, error) GetComments(issueNumber int64) ([]*Comment, error) - GetPullRequests(page, perPage int) ([]*PullRequest, error) + GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) GetReviews(pullRequestNumber int64) ([]*Review, error) } // DownloaderFactory defines an interface to match a downloader implementation and create a downloader type DownloaderFactory interface { - Match(opts MigrateOptions) (bool, error) - New(opts MigrateOptions) (Downloader, error) + New(ctx context.Context, opts MigrateOptions) (Downloader, error) GitServiceType() structs.GitServiceType } @@ -40,14 +46,16 @@ var ( // RetryDownloader retry the downloads type RetryDownloader struct { Downloader + ctx context.Context RetryTimes int // the total execute times RetryDelay int // time to delay seconds } // NewRetryDownloader creates a retry downloader -func NewRetryDownloader(downloader Downloader, retryTimes, retryDelay int) *RetryDownloader { +func NewRetryDownloader(ctx context.Context, downloader Downloader, retryTimes, retryDelay int) *RetryDownloader { return &RetryDownloader{ Downloader: downloader, + ctx: ctx, RetryTimes: retryTimes, RetryDelay: retryDelay, } @@ -55,6 +63,7 @@ func NewRetryDownloader(downloader Downloader, retryTimes, retryDelay int) *Retr // SetContext set context func (d *RetryDownloader) SetContext(ctx context.Context) { + d.ctx = ctx d.Downloader.SetContext(ctx) } @@ -69,7 +78,11 @@ func (d *RetryDownloader) GetRepoInfo() (*Repository, error) { if repo, err = d.Downloader.GetRepoInfo(); err == nil { return repo, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } @@ -85,7 +98,11 @@ func (d *RetryDownloader) GetTopics() ([]string, error) { if topics, err = d.Downloader.GetTopics(); err == nil { return topics, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } @@ -101,7 +118,11 @@ func (d *RetryDownloader) GetMilestones() ([]*Milestone, error) { if milestones, err = d.Downloader.GetMilestones(); err == nil { return milestones, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } @@ -117,7 +138,11 @@ func (d *RetryDownloader) GetReleases() ([]*Release, error) { if releases, err = d.Downloader.GetReleases(); err == nil { return releases, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } @@ -133,7 +158,11 @@ func (d *RetryDownloader) GetLabels() ([]*Label, error) { if labels, err = d.Downloader.GetLabels(); err == nil { return labels, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } @@ -150,7 +179,11 @@ func (d *RetryDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { if issues, isEnd, err = d.Downloader.GetIssues(page, perPage); err == nil { return issues, isEnd, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, false, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, false, err } @@ -166,25 +199,34 @@ func (d *RetryDownloader) GetComments(issueNumber int64) ([]*Comment, error) { if comments, err = d.Downloader.GetComments(issueNumber); err == nil { return comments, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } // GetPullRequests returns a repository's pull requests with retry -func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, error) { +func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { var ( times = d.RetryTimes prs []*PullRequest err error + isEnd bool ) for ; times > 0; times-- { - if prs, err = d.Downloader.GetPullRequests(page, perPage); err == nil { - return prs, nil + if prs, isEnd, err = d.Downloader.GetPullRequests(page, perPage); err == nil { + return prs, isEnd, nil + } + select { + case <-d.ctx.Done(): + return nil, false, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) } - return nil, err + return nil, false, err } // GetReviews returns pull requests reviews @@ -198,7 +240,11 @@ func (d *RetryDownloader) GetReviews(pullRequestNumber int64) ([]*Review, error) if reviews, err = d.Downloader.GetReviews(pullRequestNumber); err == nil { return reviews, nil } - time.Sleep(time.Second * time.Duration(d.RetryDelay)) + select { + case <-d.ctx.Done(): + return nil, d.ctx.Err() + case <-time.After(time.Second * time.Duration(d.RetryDelay)): + } } return nil, err } diff --git a/modules/migrations/base/issue.go b/modules/migrations/base/issue.go index 4e2bf25f1772..b9625a23f6cd 100644 --- a/modules/migrations/base/issue.go +++ b/modules/migrations/base/issue.go @@ -23,4 +23,5 @@ type Issue struct { Closed *time.Time Labels []*Label Reactions []*Reaction + Assignees []string } diff --git a/modules/migrations/base/options.go b/modules/migrations/base/options.go index 2d180b61d955..dbc40b138aad 100644 --- a/modules/migrations/base/options.go +++ b/modules/migrations/base/options.go @@ -8,4 +8,28 @@ package base import "code.gitea.io/gitea/modules/structs" // MigrateOptions defines the way a repository gets migrated -type MigrateOptions = structs.MigrateRepoOption +// this is for internal usage by migrations module and func who interact with it +type MigrateOptions struct { + // required: true + CloneAddr string `json:"clone_addr" binding:"Required"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` + // required: true + UID int `json:"uid" binding:"Required"` + // required: true + RepoName string `json:"repo_name" binding:"Required"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description"` + OriginalURL string + GitServiceType structs.GitServiceType + Wiki bool + Issues bool + Milestones bool + Labels bool + Releases bool + Comments bool + PullRequests bool + MigrateToRepoID int64 +} diff --git a/modules/migrations/base/pullrequest.go b/modules/migrations/base/pullrequest.go index 964512e1377e..ee612fbb8e83 100644 --- a/modules/migrations/base/pullrequest.go +++ b/modules/migrations/base/pullrequest.go @@ -31,7 +31,6 @@ type PullRequest struct { MergeCommitSHA string Head PullRequestBranch Base PullRequestBranch - Assignee string Assignees []string IsLocked bool Reactions []*Reaction diff --git a/modules/migrations/base/release.go b/modules/migrations/base/release.go index b2541f1bf501..c9b26ab1dae0 100644 --- a/modules/migrations/base/release.go +++ b/modules/migrations/base/release.go @@ -8,13 +8,14 @@ import "time" // ReleaseAsset represents a release asset type ReleaseAsset struct { - URL string + ID int64 Name string ContentType *string Size *int DownloadCount *int Created time.Time Updated time.Time + DownloadURL *string } // Release represents a release diff --git a/modules/migrations/base/repo.go b/modules/migrations/base/repo.go index 5cfb0de920db..d26a9118545d 100644 --- a/modules/migrations/base/repo.go +++ b/modules/migrations/base/repo.go @@ -7,13 +7,12 @@ package base // Repository defines a standard repository information type Repository struct { - Name string - Owner string - IsPrivate bool - IsMirror bool - Description string - AuthUsername string - AuthPassword string - CloneURL string - OriginalURL string + Name string + Owner string + IsPrivate bool + IsMirror bool + Description string + CloneURL string + OriginalURL string + DefaultBranch string } diff --git a/modules/migrations/base/review.go b/modules/migrations/base/review.go index 8051fed653b3..0a9d03dae902 100644 --- a/modules/migrations/base/review.go +++ b/modules/migrations/base/review.go @@ -36,6 +36,7 @@ type ReviewComment struct { TreePath string DiffHunk string Position int + Line int CommitID string PosterID int64 Reactions []*Reaction diff --git a/modules/migrations/base/uploader.go b/modules/migrations/base/uploader.go index 85ad60fe0e5a..07c2bb0d4239 100644 --- a/modules/migrations/base/uploader.go +++ b/modules/migrations/base/uploader.go @@ -11,7 +11,7 @@ type Uploader interface { CreateRepo(repo *Repository, opts MigrateOptions) error CreateTopics(topic ...string) error CreateMilestones(milestones ...*Milestone) error - CreateReleases(releases ...*Release) error + CreateReleases(downloader Downloader, releases ...*Release) error SyncTags() error CreateLabels(labels ...*Label) error CreateIssues(issues ...*Issue) error diff --git a/modules/migrations/git.go b/modules/migrations/git.go index af345808b56b..0aad8dbef5bb 100644 --- a/modules/migrations/git.go +++ b/modules/migrations/git.go @@ -6,6 +6,7 @@ package migrations import ( "context" + "io" "code.gitea.io/gitea/modules/migrations/base" ) @@ -64,6 +65,11 @@ func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) { return nil, ErrNotSupported } +// GetAsset returns an asset +func (g *PlainGitDownloader) GetAsset(_ string, _, _ int64) (io.ReadCloser, error) { + return nil, ErrNotSupported +} + // GetIssues returns issues according page and perPage func (g *PlainGitDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { return nil, false, ErrNotSupported @@ -75,8 +81,8 @@ func (g *PlainGitDownloader) GetComments(issueNumber int64) ([]*base.Comment, er } // GetPullRequests returns pull requests according page and perPage -func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, error) { - return nil, ErrNotSupported +func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, bool, error) { + return nil, false, ErrNotSupported } // GetReviews returns reviews according issue number diff --git a/modules/migrations/gitea_downloader.go b/modules/migrations/gitea_downloader.go new file mode 100644 index 000000000000..8299c040b013 --- /dev/null +++ b/modules/migrations/gitea_downloader.go @@ -0,0 +1,672 @@ +// 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 ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations/base" + "code.gitea.io/gitea/modules/structs" + + gitea_sdk "code.gitea.io/sdk/gitea" +) + +var ( + _ base.Downloader = &GiteaDownloader{} + _ base.DownloaderFactory = &GiteaDownloaderFactory{} +) + +func init() { + RegisterDownloaderFactory(&GiteaDownloaderFactory{}) +} + +// GiteaDownloaderFactory defines a gitea downloader factory +type GiteaDownloaderFactory struct { +} + +// New returns a Downloader related to this factory according MigrateOptions +func (f *GiteaDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { + u, err := url.Parse(opts.CloneAddr) + if err != nil { + return nil, err + } + + baseURL := u.Scheme + "://" + u.Host + repoNameSpace := strings.TrimPrefix(u.Path, "/") + repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git") + + path := strings.Split(repoNameSpace, "/") + if len(path) < 2 { + return nil, fmt.Errorf("invalid path") + } + + repoPath := strings.Join(path[len(path)-2:], "/") + if len(path) > 2 { + subPath := strings.Join(path[:len(path)-2], "/") + baseURL += "/" + subPath + } + + log.Trace("Create gitea downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace) + + return NewGiteaDownloader(ctx, baseURL, repoPath, opts.AuthUsername, opts.AuthPassword, opts.AuthToken) +} + +// GitServiceType returns the type of git service +func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType { + return structs.GiteaService +} + +// GiteaDownloader implements a Downloader interface to get repository information's +type GiteaDownloader struct { + ctx context.Context + client *gitea_sdk.Client + repoOwner string + repoName string + pagination bool + maxPerPage int +} + +// NewGiteaDownloader creates a gitea Downloader via gitea API +// Use either a username/password or personal token. token is preferred +// Note: Public access only allows very basic access +func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GiteaDownloader, error) { + giteaClient, err := gitea_sdk.NewClient( + baseURL, + gitea_sdk.SetToken(token), + gitea_sdk.SetBasicAuth(username, password), + gitea_sdk.SetContext(ctx), + ) + if err != nil { + log.Error(fmt.Sprintf("NewGiteaDownloader: %s", err.Error())) + return nil, err + } + + path := strings.Split(repoPath, "/") + + paginationSupport := true + if err := giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil { + paginationSupport = false + } + + // set small maxPerPage since we can only guess + // (default would be 50 but this can differ) + maxPerPage := 10 + // gitea instances >=1.13 can tell us what maximum they have + apiConf, _, err := giteaClient.GetGlobalAPISettings() + if err != nil { + log.Info("Unable to get global API settings. Ignoring these.") + log.Debug("giteaClient.GetGlobalAPISettings. Error: %v", err) + } + if apiConf != nil { + maxPerPage = apiConf.MaxResponseItems + } + + return &GiteaDownloader{ + ctx: ctx, + client: giteaClient, + repoOwner: path[0], + repoName: path[1], + pagination: paginationSupport, + maxPerPage: maxPerPage, + }, nil +} + +// SetContext set context +func (g *GiteaDownloader) SetContext(ctx context.Context) { + g.ctx = ctx +} + +// GetRepoInfo returns a repository information +func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { + if g == nil { + return nil, errors.New("error: GiteaDownloader is nil") + } + + repo, _, err := g.client.GetRepo(g.repoOwner, g.repoName) + if err != nil { + return nil, err + } + + return &base.Repository{ + Name: repo.Name, + Owner: repo.Owner.UserName, + IsPrivate: repo.Private, + Description: repo.Description, + CloneURL: repo.CloneURL, + OriginalURL: repo.HTMLURL, + DefaultBranch: repo.DefaultBranch, + }, nil +} + +// GetTopics return gitea topics +func (g *GiteaDownloader) GetTopics() ([]string, error) { + topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{}) + return topics, err +} + +// GetMilestones returns milestones +func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) { + var milestones = make([]*base.Milestone, 0, g.maxPerPage) + + for i := 1; ; i++ { + // make sure gitea can shutdown gracefully + select { + case <-g.ctx.Done(): + return nil, nil + default: + } + + ms, _, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName, gitea_sdk.ListMilestoneOption{ + ListOptions: gitea_sdk.ListOptions{ + PageSize: g.maxPerPage, + Page: i, + }, + State: gitea_sdk.StateAll, + }) + if err != nil { + return nil, err + } + + for i := range ms { + // old gitea instances dont have this information + createdAT := time.Now() + var updatedAT *time.Time + if ms[i].Closed != nil { + createdAT = *ms[i].Closed + updatedAT = ms[i].Closed + } + + // new gitea instances (>=1.13) do + if !ms[i].Created.IsZero() { + createdAT = ms[i].Created + } + if ms[i].Updated != nil && !ms[i].Updated.IsZero() { + updatedAT = ms[i].Updated + } + + milestones = append(milestones, &base.Milestone{ + Title: ms[i].Title, + Description: ms[i].Description, + Deadline: ms[i].Deadline, + Created: createdAT, + Updated: updatedAT, + Closed: ms[i].Closed, + State: string(ms[i].State), + }) + } + if !g.pagination || len(ms) < g.maxPerPage { + break + } + } + return milestones, nil +} + +func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label { + return &base.Label{ + Name: label.Name, + Color: label.Color, + Description: label.Description, + } +} + +// GetLabels returns labels +func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) { + var labels = make([]*base.Label, 0, g.maxPerPage) + + for i := 1; ; i++ { + // make sure gitea can shutdown gracefully + select { + case <-g.ctx.Done(): + return nil, nil + default: + } + + ls, _, err := g.client.ListRepoLabels(g.repoOwner, g.repoName, gitea_sdk.ListLabelsOptions{ListOptions: gitea_sdk.ListOptions{ + PageSize: g.maxPerPage, + Page: i, + }}) + if err != nil { + return nil, err + } + + for i := range ls { + labels = append(labels, g.convertGiteaLabel(ls[i])) + } + if !g.pagination || len(ls) < g.maxPerPage { + break + } + } + return labels, nil +} + +func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Release { + r := &base.Release{ + TagName: rel.TagName, + TargetCommitish: rel.Target, + Name: rel.Title, + Body: rel.Note, + Draft: rel.IsDraft, + Prerelease: rel.IsPrerelease, + PublisherID: rel.Publisher.ID, + PublisherName: rel.Publisher.UserName, + PublisherEmail: rel.Publisher.Email, + Published: rel.PublishedAt, + Created: rel.CreatedAt, + } + + for _, asset := range rel.Attachments { + size := int(asset.Size) + dlCount := int(asset.DownloadCount) + r.Assets = append(r.Assets, base.ReleaseAsset{ + ID: asset.ID, + Name: asset.Name, + Size: &size, + DownloadCount: &dlCount, + Created: asset.Created, + DownloadURL: &asset.DownloadURL, + }) + } + return r +} + +// GetReleases returns releases +func (g *GiteaDownloader) GetReleases() ([]*base.Release, error) { + var releases = make([]*base.Release, 0, g.maxPerPage) + + for i := 1; ; i++ { + // make sure gitea can shutdown gracefully + select { + case <-g.ctx.Done(): + return nil, nil + default: + } + + rl, _, err := g.client.ListReleases(g.repoOwner, g.repoName, gitea_sdk.ListReleasesOptions{ListOptions: gitea_sdk.ListOptions{ + PageSize: g.maxPerPage, + Page: i, + }}) + if err != nil { + return nil, err + } + + for i := range rl { + releases = append(releases, g.convertGiteaRelease(rl[i])) + } + if !g.pagination || len(rl) < g.maxPerPage { + break + } + } + return releases, nil +} + +// GetAsset returns an asset +func (g *GiteaDownloader) GetAsset(_ string, relID, id int64) (io.ReadCloser, error) { + asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, relID, id) + if err != nil { + return nil, err + } + resp, err := http.Get(asset.DownloadURL) + if err != nil { + return nil, err + } + + // resp.Body is closed by the uploader + return resp.Body, nil +} + +func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) { + var reactions []*base.Reaction + if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { + log.Info("GiteaDownloader: instance to old, skip getIssueReactions") + return reactions, nil + } + rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index) + if err != nil { + return nil, err + } + + for _, reaction := range rl { + reactions = append(reactions, &base.Reaction{ + UserID: reaction.User.ID, + UserName: reaction.User.UserName, + Content: reaction.Reaction, + }) + } + return reactions, nil +} + +func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) { + var reactions []*base.Reaction + if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { + log.Info("GiteaDownloader: instance to old, skip getCommentReactions") + return reactions, nil + } + rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID) + if err != nil { + return nil, err + } + + for i := range rl { + reactions = append(reactions, &base.Reaction{ + UserID: rl[i].User.ID, + UserName: rl[i].User.UserName, + Content: rl[i].Reaction, + }) + } + return reactions, nil +} + +// GetIssues returns issues according start and limit +func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { + if perPage > g.maxPerPage { + perPage = g.maxPerPage + } + var allIssues = make([]*base.Issue, 0, perPage) + + issues, _, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gitea_sdk.ListIssueOption{ + ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: perPage}, + State: gitea_sdk.StateAll, + Type: gitea_sdk.IssueTypeIssue, + }) + if err != nil { + return nil, false, fmt.Errorf("error while listing issues: %v", err) + } + for _, issue := range issues { + + var labels = make([]*base.Label, 0, len(issue.Labels)) + for i := range issue.Labels { + labels = append(labels, g.convertGiteaLabel(issue.Labels[i])) + } + + var milestone string + if issue.Milestone != nil { + milestone = issue.Milestone.Title + } + + reactions, err := g.getIssueReactions(issue.Index) + if err != nil { + return nil, false, fmt.Errorf("error while loading reactions: %v", err) + } + + var assignees []string + for i := range issue.Assignees { + assignees = append(assignees, issue.Assignees[i].UserName) + } + + allIssues = append(allIssues, &base.Issue{ + Title: issue.Title, + Number: issue.Index, + PosterID: issue.Poster.ID, + PosterName: issue.Poster.UserName, + PosterEmail: issue.Poster.Email, + Content: issue.Body, + Milestone: milestone, + State: string(issue.State), + Created: issue.Created, + Updated: issue.Updated, + Closed: issue.Closed, + Reactions: reactions, + Labels: labels, + Assignees: assignees, + IsLocked: issue.IsLocked, + }) + } + + isEnd := len(issues) < perPage + if !g.pagination { + isEnd = len(issues) == 0 + } + return allIssues, isEnd, nil +} + +// GetComments returns comments according issueNumber +func (g *GiteaDownloader) GetComments(index int64) ([]*base.Comment, error) { + var allComments = make([]*base.Comment, 0, g.maxPerPage) + + // for i := 1; ; i++ { + // make sure gitea can shutdown gracefully + select { + case <-g.ctx.Done(): + return nil, nil + default: + } + + comments, _, err := g.client.ListIssueComments(g.repoOwner, g.repoName, index, gitea_sdk.ListIssueCommentOptions{ListOptions: gitea_sdk.ListOptions{ + // PageSize: g.maxPerPage, + // Page: i, + }}) + if err != nil { + return nil, fmt.Errorf("error while listing comments: %v", err) + } + + for _, comment := range comments { + reactions, err := g.getCommentReactions(comment.ID) + if err != nil { + return nil, fmt.Errorf("error while listing comment creactions: %v", err) + } + + allComments = append(allComments, &base.Comment{ + IssueIndex: index, + PosterID: comment.Poster.ID, + PosterName: comment.Poster.UserName, + PosterEmail: comment.Poster.Email, + Content: comment.Body, + Created: comment.Created, + Updated: comment.Updated, + Reactions: reactions, + }) + } + + // TODO enable pagination vor (gitea >= 1.14) when it got implemented + // if !g.pagination || len(comments) < g.maxPerPage { + // break + // } + //} + return allComments, nil +} + +// GetPullRequests returns pull requests according page and perPage +func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { + if perPage > g.maxPerPage { + perPage = g.maxPerPage + } + var allPRs = make([]*base.PullRequest, 0, perPage) + + prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{ + ListOptions: gitea_sdk.ListOptions{ + Page: page, + PageSize: perPage, + }, + State: gitea_sdk.StateAll, + }) + if err != nil { + return nil, false, fmt.Errorf("error while listing repos: %v", err) + } + for _, pr := range prs { + var milestone string + if pr.Milestone != nil { + milestone = pr.Milestone.Title + } + + var labels = make([]*base.Label, 0, len(pr.Labels)) + for i := range pr.Labels { + labels = append(labels, g.convertGiteaLabel(pr.Labels[i])) + } + + var ( + headUserName string + headRepoName string + headCloneURL string + headRef string + headSHA string + ) + if pr.Head != nil { + if pr.Head.Repository != nil { + headUserName = pr.Head.Repository.Owner.UserName + headRepoName = pr.Head.Repository.Name + headCloneURL = pr.Head.Repository.CloneURL + } + headSHA = pr.Head.Sha + headRef = pr.Head.Ref + if headSHA == "" { + headCommit, _, err := g.client.GetSingleCommit(g.repoOwner, g.repoName, url.PathEscape(pr.Head.Ref)) + if err != nil { + return nil, false, fmt.Errorf("error while resolving git ref: %v", err) + } + headSHA = headCommit.SHA + } + } + + var mergeCommitSHA string + if pr.MergedCommitID != nil { + mergeCommitSHA = *pr.MergedCommitID + } + + reactions, err := g.getIssueReactions(pr.Index) + if err != nil { + return nil, false, fmt.Errorf("error while loading reactions: %v", err) + } + + var assignees []string + for i := range pr.Assignees { + assignees = append(assignees, pr.Assignees[i].UserName) + } + + createdAt := time.Now() + if pr.Created != nil { + createdAt = *pr.Created + } + updatedAt := time.Now() + if pr.Created != nil { + updatedAt = *pr.Updated + } + + closedAt := pr.Closed + if pr.Merged != nil && closedAt == nil { + closedAt = pr.Merged + } + + allPRs = append(allPRs, &base.PullRequest{ + Title: pr.Title, + Number: pr.Index, + PosterID: pr.Poster.ID, + PosterName: pr.Poster.UserName, + PosterEmail: pr.Poster.Email, + Content: pr.Body, + State: string(pr.State), + Created: createdAt, + Updated: updatedAt, + Closed: closedAt, + Labels: labels, + Milestone: milestone, + Reactions: reactions, + Assignees: assignees, + Merged: pr.HasMerged, + MergedTime: pr.Merged, + MergeCommitSHA: mergeCommitSHA, + IsLocked: pr.IsLocked, + PatchURL: pr.PatchURL, + Head: base.PullRequestBranch{ + Ref: headRef, + SHA: headSHA, + RepoName: headRepoName, + OwnerName: headUserName, + CloneURL: headCloneURL, + }, + Base: base.PullRequestBranch{ + Ref: pr.Base.Ref, + SHA: pr.Base.Sha, + RepoName: g.repoName, + OwnerName: g.repoOwner, + }, + }) + } + + isEnd := len(prs) < perPage + if !g.pagination { + isEnd = len(prs) == 0 + } + return allPRs, isEnd, nil +} + +// GetReviews returns pull requests review +func (g *GiteaDownloader) GetReviews(index int64) ([]*base.Review, error) { + if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil { + log.Info("GiteaDownloader: instance to old, skip GetReviews") + return nil, nil + } + + var allReviews = make([]*base.Review, 0, g.maxPerPage) + + for i := 1; ; i++ { + // make sure gitea can shutdown gracefully + select { + case <-g.ctx.Done(): + return nil, nil + default: + } + + prl, _, err := g.client.ListPullReviews(g.repoOwner, g.repoName, index, gitea_sdk.ListPullReviewsOptions{ListOptions: gitea_sdk.ListOptions{ + Page: i, + PageSize: g.maxPerPage, + }}) + if err != nil { + return nil, err + } + + for _, pr := range prl { + + rcl, _, err := g.client.ListPullReviewComments(g.repoOwner, g.repoName, index, pr.ID) + if err != nil { + return nil, err + } + var reviewComments []*base.ReviewComment + for i := range rcl { + line := int(rcl[i].LineNum) + if rcl[i].OldLineNum > 0 { + line = int(rcl[i].OldLineNum) * -1 + } + + reviewComments = append(reviewComments, &base.ReviewComment{ + ID: rcl[i].ID, + Content: rcl[i].Body, + TreePath: rcl[i].Path, + DiffHunk: rcl[i].DiffHunk, + Line: line, + CommitID: rcl[i].CommitID, + PosterID: rcl[i].Reviewer.ID, + CreatedAt: rcl[i].Created, + UpdatedAt: rcl[i].Updated, + }) + } + + allReviews = append(allReviews, &base.Review{ + ID: pr.ID, + IssueIndex: index, + ReviewerID: pr.Reviewer.ID, + ReviewerName: pr.Reviewer.UserName, + Official: pr.Official, + CommitID: pr.CommitID, + Content: pr.Body, + CreatedAt: pr.Submitted, + State: string(pr.State), + Comments: reviewComments, + }) + } + + if len(prl) < g.maxPerPage { + break + } + } + return allReviews, nil +} diff --git a/modules/migrations/gitea_downloader_test.go b/modules/migrations/gitea_downloader_test.go new file mode 100644 index 000000000000..c52c1225f401 --- /dev/null +++ b/modules/migrations/gitea_downloader_test.go @@ -0,0 +1,365 @@ +// 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 ( + "context" + "fmt" + "net/http" + "os" + "sort" + "testing" + "time" + + "code.gitea.io/gitea/modules/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func assertEqualIssue(t *testing.T, issueExp, issueGet *base.Issue) { + assert.EqualValues(t, issueExp.Number, issueGet.Number) + assert.EqualValues(t, issueExp.Title, issueGet.Title) + assert.EqualValues(t, issueExp.Content, issueGet.Content) + assert.EqualValues(t, issueExp.Milestone, issueGet.Milestone) + assert.EqualValues(t, issueExp.PosterID, issueGet.PosterID) + assert.EqualValues(t, issueExp.PosterName, issueGet.PosterName) + assert.EqualValues(t, issueExp.PosterEmail, issueGet.PosterEmail) + assert.EqualValues(t, issueExp.IsLocked, issueGet.IsLocked) + assert.EqualValues(t, issueExp.Created.Unix(), issueGet.Created.Unix()) + assert.EqualValues(t, issueExp.Updated.Unix(), issueGet.Updated.Unix()) + if issueExp.Closed != nil { + assert.EqualValues(t, issueExp.Closed.Unix(), issueGet.Closed.Unix()) + } else { + assert.True(t, issueGet.Closed == nil) + } + sort.Strings(issueExp.Assignees) + sort.Strings(issueGet.Assignees) + assert.EqualValues(t, issueExp.Assignees, issueGet.Assignees) + assert.EqualValues(t, issueExp.Labels, issueGet.Labels) + assert.EqualValues(t, issueExp.Reactions, issueGet.Reactions) +} + +func TestGiteaDownloadRepo(t *testing.T) { + // Skip tests if Gitea token is not found + giteaToken := os.Getenv("GITEA_TOKEN") + if giteaToken == "" { + t.Skip("skipped test because GITEA_TOKEN was not in the environment") + } + + resp, err := http.Get("https://gitea.com/gitea") + if err != nil || resp.StatusCode != 200 { + t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name()) + } + + downloader, err := NewGiteaDownloader(context.Background(), "https://gitea.com", "gitea/test_repo", "", "", giteaToken) + if downloader == nil { + t.Fatal("NewGitlabDownloader is nil") + } + if !assert.NoError(t, err) { + t.Fatal("NewGitlabDownloader error occur") + } + + repo, err := downloader.GetRepoInfo() + assert.NoError(t, err) + assert.EqualValues(t, &base.Repository{ + Name: "test_repo", + Owner: "gitea", + IsPrivate: false, + Description: "Test repository for testing migration from gitea to gitea", + CloneURL: "https://gitea.com/gitea/test_repo.git", + OriginalURL: "https://gitea.com/gitea/test_repo", + DefaultBranch: "master", + }, repo) + + topics, err := downloader.GetTopics() + assert.NoError(t, err) + sort.Strings(topics) + assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) + + labels, err := downloader.GetLabels() + assert.NoError(t, err) + assert.Len(t, labels, 6) + for _, l := range labels { + switch l.Name { + case "Bug": + assertLabelEqual(t, "Bug", "e11d21", "", l) + case "documentation": + assertLabelEqual(t, "Enhancement", "207de5", "", l) + case "confirmed": + assertLabelEqual(t, "Feature", "0052cc", "a feature request", l) + case "enhancement": + assertLabelEqual(t, "Invalid", "d4c5f9", "", l) + case "critical": + assertLabelEqual(t, "Question", "fbca04", "", l) + case "discussion": + assertLabelEqual(t, "Valid", "53e917", "", l) + default: + assert.Error(t, fmt.Errorf("unexpected label: %s", l.Name)) + } + } + + milestones, err := downloader.GetMilestones() + assert.NoError(t, err) + assert.Len(t, milestones, 2) + + for _, milestone := range milestones { + switch milestone.Title { + case "V1": + assert.EqualValues(t, "Generate Content", milestone.Description) + // assert.EqualValues(t, "ToDo", milestone.Created) + // assert.EqualValues(t, "ToDo", milestone.Updated) + assert.EqualValues(t, 1598985406, milestone.Closed.Unix()) + assert.True(t, milestone.Deadline == nil) + assert.EqualValues(t, "closed", milestone.State) + case "V2 Finalize": + assert.EqualValues(t, "", milestone.Description) + // assert.EqualValues(t, "ToDo", milestone.Created) + // assert.EqualValues(t, "ToDo", milestone.Updated) + assert.True(t, milestone.Closed == nil) + assert.EqualValues(t, 1599263999, milestone.Deadline.Unix()) + assert.EqualValues(t, "open", milestone.State) + default: + assert.Error(t, fmt.Errorf("unexpected milestone: %s", milestone.Title)) + } + } + + releases, err := downloader.GetReleases() + assert.NoError(t, err) + assert.EqualValues(t, []*base.Release{ + { + Name: "Second Release", + TagName: "v2-rc1", + TargetCommitish: "master", + Body: "this repo has:\r\n* reactions\r\n* wiki\r\n* issues (open/closed)\r\n* pulls (open/closed/merged) (external/internal)\r\n* pull reviews\r\n* projects\r\n* milestones\r\n* labels\r\n* releases\r\n\r\nto test migration against", + Draft: false, + Prerelease: true, + Created: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), + Published: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), + PublisherID: 689, + PublisherName: "6543", + PublisherEmail: "6543@noreply.gitea.io", + }, + { + Name: "First Release", + TagName: "V1", + TargetCommitish: "master", + Body: "as title", + Draft: false, + Prerelease: false, + Created: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), + Published: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), + PublisherID: 689, + PublisherName: "6543", + PublisherEmail: "6543@noreply.gitea.io", + }, + }, releases) + + issues, isEnd, err := downloader.GetIssues(1, 50) + assert.NoError(t, err) + assert.EqualValues(t, 7, len(issues)) + assert.True(t, isEnd) + assert.EqualValues(t, "open", issues[0].State) + + issues, isEnd, err = downloader.GetIssues(3, 2) + assert.NoError(t, err) + assert.EqualValues(t, 2, len(issues)) + assert.False(t, isEnd) + + var ( + closed4 = time.Date(2020, 9, 1, 15, 49, 34, 0, time.UTC) + closed2 = time.Unix(1598969497, 0) + ) + + assertEqualIssue(t, &base.Issue{ + Number: 4, + Title: "what is this repo about?", + Content: "", + Milestone: "V1", + PosterID: -1, + PosterName: "Ghost", + PosterEmail: "", + State: "closed", + IsLocked: true, + Created: time.Unix(1598975321, 0), + Updated: time.Unix(1598975400, 0), + Labels: []*base.Label{{ + Name: "Question", + Color: "fbca04", + Description: "", + }}, + Reactions: []*base.Reaction{ + { + UserID: 689, + UserName: "6543", + Content: "gitea", + }, + { + UserID: 689, + UserName: "6543", + Content: "laugh", + }, + }, + Closed: &closed4, + }, issues[0]) + assertEqualIssue(t, &base.Issue{ + Number: 2, + Title: "Spam", + Content: ":(", + Milestone: "", + PosterID: 689, + PosterName: "6543", + PosterEmail: "6543@noreply.gitea.io", + State: "closed", + IsLocked: false, + Created: time.Unix(1598919780, 0), + Updated: closed2, + Labels: []*base.Label{{ + Name: "Invalid", + Color: "d4c5f9", + Description: "", + }}, + Reactions: nil, + Closed: &closed2, + }, issues[1]) + + comments, err := downloader.GetComments(4) + assert.NoError(t, err) + assert.Len(t, comments, 2) + assert.EqualValues(t, 1598975370, comments[0].Created.Unix()) + assert.EqualValues(t, 1599070865, comments[0].Updated.Unix()) + assert.EqualValues(t, 1598975393, comments[1].Created.Unix()) + assert.EqualValues(t, 1598975393, comments[1].Updated.Unix()) + assert.EqualValues(t, []*base.Comment{ + { + IssueIndex: 4, + PosterID: 689, + PosterName: "6543", + PosterEmail: "6543@noreply.gitea.io", + Created: comments[0].Created, + Updated: comments[0].Updated, + Content: "a really good question!\n\nIt is the used as TESTSET for gitea2gitea repo migration function", + }, + { + IssueIndex: 4, + PosterID: -1, + PosterName: "Ghost", + PosterEmail: "", + Created: comments[1].Created, + Updated: comments[1].Updated, + Content: "Oh!", + }, + }, comments) + + prs, isEnd, err := downloader.GetPullRequests(1, 50) + assert.NoError(t, err) + assert.True(t, isEnd) + assert.Len(t, prs, 6) + prs, isEnd, err = downloader.GetPullRequests(1, 3) + assert.NoError(t, err) + assert.False(t, isEnd) + assert.Len(t, prs, 3) + merged12 := time.Unix(1598982934, 0) + assertEqualPulls(t, &base.PullRequest{ + Number: 12, + PosterID: 689, + PosterName: "6543", + PosterEmail: "6543@noreply.gitea.io", + Title: "Dont Touch", + Content: "\r\nadd dont touch note", + Milestone: "V2 Finalize", + State: "closed", + IsLocked: false, + Created: time.Unix(1598982759, 0), + Updated: time.Unix(1599023425, 0), + Closed: &merged12, + Assignees: []string{"techknowlogick"}, + Labels: []*base.Label{}, + + Base: base.PullRequestBranch{ + CloneURL: "", + Ref: "master", + SHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", + RepoName: "test_repo", + OwnerName: "gitea", + }, + Head: base.PullRequestBranch{ + CloneURL: "https://gitea.com/6543-forks/test_repo.git", + Ref: "refs/pull/12/head", + SHA: "b6ab5d9ae000b579a5fff03f92c486da4ddf48b6", + RepoName: "test_repo", + OwnerName: "6543-forks", + }, + Merged: true, + MergedTime: &merged12, + MergeCommitSHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", + PatchURL: "https://gitea.com/gitea/test_repo/pulls/12.patch", + }, prs[1]) + + reviews, err := downloader.GetReviews(7) + assert.NoError(t, err) + if assert.Len(t, reviews, 3) { + assert.EqualValues(t, 689, reviews[0].ReviewerID) + assert.EqualValues(t, "6543", reviews[0].ReviewerName) + assert.EqualValues(t, "techknowlogick", reviews[1].ReviewerName) + assert.EqualValues(t, "techknowlogick", reviews[2].ReviewerName) + assert.False(t, reviews[1].Official) + assert.EqualValues(t, "I think this needs some changes", reviews[1].Content) + assert.EqualValues(t, "REQUEST_CHANGES", reviews[1].State) + assert.True(t, reviews[2].Official) + assert.EqualValues(t, "looks good", reviews[2].Content) + assert.EqualValues(t, "APPROVED", reviews[2].State) + + // TODO: https://github.com/go-gitea/gitea/issues/12846 + // assert.EqualValues(t, 9, reviews[1].ReviewerID) + // assert.EqualValues(t, 9, reviews[2].ReviewerID) + + assert.Len(t, reviews[0].Comments, 1) + assert.EqualValues(t, &base.ReviewComment{ + ID: 116561, + InReplyTo: 0, + Content: "is one `\\newline` to less?", + TreePath: "README.md", + DiffHunk: "@@ -2,3 +2,3 @@\n \n-Test repository for testing migration from gitea 2 gitea\n\\ No newline at end of file\n+Test repository for testing migration from gitea 2 gitea", + Position: 0, + Line: 4, + CommitID: "187ece0cb6631e2858a6872e5733433bb3ca3b03", + PosterID: 689, + Reactions: nil, + CreatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), + UpdatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), + }, reviews[0].Comments[0]) + } +} + +func assertEqualPulls(t *testing.T, pullExp, pullGet *base.PullRequest) { + assertEqualIssue(t, pull2issue(pullExp), pull2issue(pullGet)) + assert.EqualValues(t, 0, pullGet.OriginalNumber) + assert.EqualValues(t, pullExp.PatchURL, pullGet.PatchURL) + assert.EqualValues(t, pullExp.Merged, pullGet.Merged) + assert.EqualValues(t, pullExp.MergedTime.Unix(), pullGet.MergedTime.Unix()) + assert.EqualValues(t, pullExp.MergeCommitSHA, pullGet.MergeCommitSHA) + assert.EqualValues(t, pullExp.Base, pullGet.Base) + assert.EqualValues(t, pullExp.Head, pullGet.Head) +} + +func pull2issue(pull *base.PullRequest) *base.Issue { + return &base.Issue{ + Number: pull.Number, + PosterID: pull.PosterID, + PosterName: pull.PosterName, + PosterEmail: pull.PosterEmail, + Title: pull.Title, + Content: pull.Content, + Milestone: pull.Milestone, + State: pull.State, + IsLocked: pull.IsLocked, + Created: pull.Created, + Updated: pull.Updated, + Closed: pull.Closed, + Labels: pull.Labels, + Reactions: pull.Reactions, + Assignees: pull.Assignees, + } +} diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea_uploader.go similarity index 96% rename from modules/migrations/gitea.go rename to modules/migrations/gitea_uploader.go index 8c097e143cb0..cd1fd5cb8d8e 100644 --- a/modules/migrations/gitea.go +++ b/modules/migrations/gitea_uploader.go @@ -93,12 +93,15 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate } var remoteAddr = repo.CloneURL - if len(opts.AuthUsername) > 0 { + if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 { u, err := url.Parse(repo.CloneURL) if err != nil { return err } u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword) + if len(opts.AuthToken) > 0 { + u.User = url.UserPassword("oauth2", opts.AuthToken) + } remoteAddr = u.String() } @@ -119,8 +122,9 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate if err != nil { return err } + r.DefaultBranch = repo.DefaultBranch - r, err = repository.MigrateRepositoryGitData(g.doer, owner, r, structs.MigrateRepoOption{ + r, err = repository.MigrateRepositoryGitData(g.doer, owner, r, base.MigrateOptions{ RepoName: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, @@ -210,7 +214,7 @@ func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { } // CreateReleases creates releases -func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { +func (g *GiteaLocalUploader) CreateReleases(downloader base.Downloader, releases ...*base.Release) error { var rels = make([]*models.Release, 0, len(releases)) for _, release := range releases { var rel = models.Release{ @@ -269,13 +273,20 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { // download attachment err = func() error { - resp, err := http.Get(asset.URL) - if err != nil { - return err + var rc io.ReadCloser + if asset.DownloadURL == nil { + rc, err = downloader.GetAsset(rel.TagName, rel.ID, asset.ID) + if err != nil { + return err + } + } else { + resp, err := http.Get(*asset.DownloadURL) + if err != nil { + return err + } + rc = resp.Body } - defer resp.Body.Close() - - _, err = storage.Attachments.Save(attach.RelativePath(), resp.Body) + _, err = storage.Attachments.Save(attach.RelativePath(), rc) return err }() if err != nil { @@ -777,8 +788,12 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { } for _, comment := range review.Comments { - _, _, line, _ := git.ParseDiffHunkString(comment.DiffHunk) - + line := comment.Line + if line != 0 { + comment.Position = 1 + } else { + _, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk) + } headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) diff --git a/modules/migrations/gitea_test.go b/modules/migrations/gitea_uploader_test.go similarity index 92% rename from modules/migrations/gitea_test.go rename to modules/migrations/gitea_uploader_test.go index c0d2dcd18026..8432a1eecdad 100644 --- a/modules/migrations/gitea_test.go +++ b/modules/migrations/gitea_uploader_test.go @@ -6,11 +6,13 @@ package migrations import ( + "context" "testing" "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -26,12 +28,12 @@ func TestGiteaUploadRepo(t *testing.T) { user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) var ( - downloader = NewGithubDownloaderV3("", "", "go-xorm", "builder") + downloader = NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", "", "go-xorm", "builder") repoName = "builder-" + time.Now().Format("2006-01-02-15-04-05") uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName) ) - err := migrateRepository(downloader, uploader, structs.MigrateRepoOption{ + err := migrateRepository(downloader, uploader, base.MigrateOptions{ CloneAddr: "https://github.com/go-xorm/builder", RepoName: repoName, AuthUsername: "", diff --git a/modules/migrations/github.go b/modules/migrations/github.go index 97d62b994f21..088e54744d98 100644 --- a/modules/migrations/github.go +++ b/modules/migrations/github.go @@ -6,8 +6,11 @@ package migrations import ( + "bytes" "context" "fmt" + "io" + "io/ioutil" "net/http" "net/url" "strings" @@ -37,30 +40,21 @@ func init() { type GithubDownloaderV3Factory struct { } -// Match returns ture if the migration remote URL matched this downloader factory -func (f *GithubDownloaderV3Factory) Match(opts base.MigrateOptions) (bool, error) { - u, err := url.Parse(opts.CloneAddr) - if err != nil { - return false, err - } - - return strings.EqualFold(u.Host, "github.com") && opts.AuthUsername != "", nil -} - // New returns a Downloader related to this factory according MigrateOptions -func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) { +func (f *GithubDownloaderV3Factory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { u, err := url.Parse(opts.CloneAddr) if err != nil { return nil, err } + baseURL := u.Scheme + "://" + u.Host fields := strings.Split(u.Path, "/") oldOwner := fields[1] oldName := strings.TrimSuffix(fields[2], ".git") log.Trace("Create github downloader: %s/%s", oldOwner, oldName) - return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, oldOwner, oldName), nil + return NewGithubDownloaderV3(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil } // GitServiceType returns the type of git service @@ -81,34 +75,33 @@ type GithubDownloaderV3 struct { } // NewGithubDownloaderV3 creates a github Downloader via github v3 API -func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *GithubDownloaderV3 { +func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 { var downloader = GithubDownloaderV3{ userName: userName, password: password, - ctx: context.Background(), + ctx: ctx, repoOwner: repoOwner, repoName: repoName, } - var client *http.Client - if userName != "" { - if password == "" { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: userName}, - ) - client = oauth2.NewClient(downloader.ctx, ts) - } else { - client = &http.Client{ - Transport: &http.Transport{ - Proxy: func(req *http.Request) (*url.URL, error) { - req.SetBasicAuth(userName, password) - return nil, nil - }, - }, - } - } + client := &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + req.SetBasicAuth(userName, password) + return nil, nil + }, + }, + } + if token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + client = oauth2.NewClient(downloader.ctx, ts) } downloader.client = github.NewClient(client) + if baseURL != "https://github.com" { + downloader.client, _ = github.NewEnterpriseClient(baseURL, baseURL, client) + } return &downloader } @@ -154,14 +147,20 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) { } g.rate = &resp.Rate + defaultBranch := "" + if gr.DefaultBranch != nil { + defaultBranch = *gr.DefaultBranch + } + // convert github repo to stand Repo return &base.Repository{ - Owner: g.repoOwner, - Name: gr.GetName(), - IsPrivate: *gr.Private, - Description: gr.GetDescription(), - OriginalURL: gr.GetHTMLURL(), - CloneURL: gr.GetCloneURL(), + Owner: g.repoOwner, + Name: gr.GetName(), + IsPrivate: *gr.Private, + Description: gr.GetDescription(), + OriginalURL: gr.GetHTMLURL(), + CloneURL: gr.GetCloneURL(), + DefaultBranch: defaultBranch, }, nil } @@ -290,10 +289,8 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) } for _, asset := range rel.Assets { - u, _ := url.Parse(*asset.BrowserDownloadURL) - u.User = url.UserPassword(g.userName, g.password) r.Assets = append(r.Assets, base.ReleaseAsset{ - URL: u.String(), + ID: *asset.ID, Name: *asset.Name, ContentType: asset.ContentType, Size: asset.Size, @@ -331,6 +328,18 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { return releases, nil } +// GetAsset returns an asset +func (g *GithubDownloaderV3) GetAsset(_ string, _, id int64) (io.ReadCloser, error) { + asset, redir, err := g.client.Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, id, http.DefaultClient) + if err != nil { + return nil, err + } + if asset == nil { + return ioutil.NopCloser(bytes.NewBufferString(redir)), nil + } + return asset, nil +} + // GetIssues returns issues according start and limit func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { opt := &github.IssueListByRepoOptions{ @@ -487,7 +496,7 @@ func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, er } // GetPullRequests returns pull requests according page and perPage -func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) { +func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { opt := &github.PullRequestListOptions{ Sort: "created", Direction: "asc", @@ -501,7 +510,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq g.sleep() prs, resp, err := g.client.PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt) if err != nil { - return nil, fmt.Errorf("error while listing repos: %v", err) + return nil, false, fmt.Errorf("error while listing repos: %v", err) } g.rate = &resp.Rate for _, pr := range prs { @@ -567,7 +576,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq PerPage: perPage, }) if err != nil { - return nil, err + return nil, false, err } g.rate = &resp.Rate if len(res) == 0 { @@ -617,7 +626,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq }) } - return allPRs, nil + return allPRs, len(prs) < perPage, nil } func convertGithubReview(r *github.PullRequestReview) *base.Review { diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go index 814c771e8c2b..efa8b6ba9bbb 100644 --- a/modules/migrations/github_test.go +++ b/modules/migrations/github_test.go @@ -6,6 +6,7 @@ package migrations import ( + "context" "os" "testing" "time" @@ -64,18 +65,19 @@ func assertLabelEqual(t *testing.T, name, color, description string, label *base func TestGitHubDownloadRepo(t *testing.T) { GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in // - downloader := NewGithubDownloaderV3(os.Getenv("GITHUB_READ_TOKEN"), "", "go-gitea", "test_repo") + downloader := NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", os.Getenv("GITHUB_READ_TOKEN"), "go-gitea", "test_repo") err := downloader.RefreshRate() assert.NoError(t, err) repo, err := downloader.GetRepoInfo() assert.NoError(t, err) assert.EqualValues(t, &base.Repository{ - Name: "test_repo", - Owner: "go-gitea", - Description: "Test repository for testing migration from github to gitea", - CloneURL: "https://github.com/go-gitea/test_repo.git", - OriginalURL: "https://github.com/go-gitea/test_repo", + Name: "test_repo", + Owner: "go-gitea", + Description: "Test repository for testing migration from github to gitea", + CloneURL: "https://github.com/go-gitea/test_repo.git", + OriginalURL: "https://github.com/go-gitea/test_repo", + DefaultBranch: "master", }, repo) topics, err := downloader.GetTopics() @@ -269,7 +271,7 @@ func TestGitHubDownloadRepo(t *testing.T) { }, comments[:2]) // downloader.GetPullRequests() - prs, err := downloader.GetPullRequests(1, 2) + prs, _, err := downloader.GetPullRequests(1, 2) assert.NoError(t, err) assert.EqualValues(t, 2, len(prs)) diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go index 4f218c95f1f9..23cd90c7478b 100644 --- a/modules/migrations/gitlab.go +++ b/modules/migrations/gitlab.go @@ -8,6 +8,8 @@ import ( "context" "errors" "fmt" + "io" + "net/http" "net/url" "strings" "time" @@ -32,23 +34,8 @@ func init() { type GitlabDownloaderFactory struct { } -// Match returns true if the migration remote URL matched this downloader factory -func (f *GitlabDownloaderFactory) Match(opts base.MigrateOptions) (bool, error) { - var matched bool - - u, err := url.Parse(opts.CloneAddr) - if err != nil { - return false, err - } - if strings.EqualFold(u.Host, "gitlab.com") && opts.AuthUsername != "" { - matched = true - } - - return matched, nil -} - // New returns a Downloader related to this factory according MigrateOptions -func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader, error) { +func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { u, err := url.Parse(opts.CloneAddr) if err != nil { return nil, err @@ -56,10 +43,11 @@ func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader baseURL := u.Scheme + "://" + u.Host repoNameSpace := strings.TrimPrefix(u.Path, "/") + repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git") log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace) - return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword), nil + return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword, opts.AuthToken) } // GitServiceType returns the type of git service @@ -85,40 +73,37 @@ type GitlabDownloader struct { // NewGitlabDownloader creates a gitlab Downloader via gitlab API // Use either a username/password, personal token entered into the username field, or anonymous/public access // Note: Public access only allows very basic access -func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDownloader { - var gitlabClient *gitlab.Client - var err error - if username != "" { - if password == "" { - gitlabClient, err = gitlab.NewClient(username, gitlab.WithBaseURL(baseURL)) - } else { - gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL)) - } +func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GitlabDownloader, error) { + gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL)) + // Only use basic auth if token is blank and password is NOT + // Basic auth will fail with empty strings, but empty token will allow anonymous public API usage + if token == "" && password != "" { + gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL)) } if err != nil { log.Trace("Error logging into gitlab: %v", err) - return nil + return nil, err } // Grab and store project/repo ID here, due to issues using the URL escaped path - gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil) + gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil, gitlab.WithContext(ctx)) if err != nil { log.Trace("Error retrieving project: %v", err) - return nil + return nil, err } if gr == nil { log.Trace("Error getting project, project is nil") - return nil + return nil, errors.New("Error getting project, project is nil") } return &GitlabDownloader{ - ctx: context.Background(), + ctx: ctx, client: gitlabClient, repoID: gr.ID, repoName: gr.Name, - } + }, nil } // SetContext set context @@ -128,11 +113,7 @@ func (g *GitlabDownloader) SetContext(ctx context.Context) { // GetRepoInfo returns a repository information func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) { - if g == nil { - return nil, errors.New("error: GitlabDownloader is nil") - } - - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil) + gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, err } @@ -157,22 +138,19 @@ func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) { // convert gitlab repo to stand Repo return &base.Repository{ - Owner: owner, - Name: gr.Name, - IsPrivate: private, - Description: gr.Description, - OriginalURL: gr.WebURL, - CloneURL: gr.HTTPURLToRepo, + Owner: owner, + Name: gr.Name, + IsPrivate: private, + Description: gr.Description, + OriginalURL: gr.WebURL, + CloneURL: gr.HTTPURLToRepo, + DefaultBranch: gr.DefaultBranch, }, nil } // GetTopics return gitlab topics func (g *GitlabDownloader) GetTopics() ([]string, error) { - if g == nil { - return nil, errors.New("error: GitlabDownloader is nil") - } - - gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil) + gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, err } @@ -181,9 +159,6 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) { // GetMilestones returns milestones func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { - if g == nil { - return nil, errors.New("error: GitlabDownloader is nil") - } var perPage = 100 var state = "all" var milestones = make([]*base.Milestone, 0, perPage) @@ -193,7 +168,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { ListOptions: gitlab.ListOptions{ Page: i, PerPage: perPage, - }}, nil) + }}, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, err } @@ -240,25 +215,35 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { return milestones, nil } +func (g *GitlabDownloader) normalizeColor(val string) string { + val = strings.TrimLeft(val, "#") + val = strings.ToLower(val) + if len(val) == 3 { + c := []rune(val) + val = fmt.Sprintf("%c%c%c%c%c%c", c[0], c[0], c[1], c[1], c[2], c[2]) + } + if len(val) != 6 { + return "" + } + return val +} + // GetLabels returns labels func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { - if g == nil { - return nil, errors.New("error: GitlabDownloader is nil") - } var perPage = 100 var labels = make([]*base.Label, 0, perPage) for i := 1; ; i++ { - ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ + ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{ Page: i, PerPage: perPage, - }, nil) + }}, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, err } for _, label := range ls { baseLabel := &base.Label{ Name: label.Name, - Color: strings.TrimLeft(label.Color, "#)"), + Color: g.normalizeColor(label.Color), Description: label.Description, } labels = append(labels, baseLabel) @@ -271,7 +256,7 @@ func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { } func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release { - + var zero int r := &base.Release{ TagName: rel.TagName, TargetCommitish: rel.Commit.ID, @@ -284,9 +269,11 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea for k, asset := range rel.Assets.Links { r.Assets = append(r.Assets, base.ReleaseAsset{ - URL: asset.URL, - Name: asset.Name, - ContentType: &rel.Assets.Sources[k].Format, + ID: int64(asset.ID), + Name: asset.Name, + ContentType: &rel.Assets.Sources[k].Format, + Size: &zero, + DownloadCount: &zero, }) } return r @@ -300,7 +287,7 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{ Page: i, PerPage: perPage, - }, nil) + }, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, err } @@ -315,9 +302,30 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { return releases, nil } +// GetAsset returns an asset +func (g *GitlabDownloader) GetAsset(tag string, _, id int64) (io.ReadCloser, error) { + link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, tag, int(id), gitlab.WithContext(g.ctx)) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", link.URL, nil) + if err != nil { + return nil, err + } + req = req.WithContext(g.ctx) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + // resp.Body is closed by the uploader + return resp.Body, nil +} + // GetIssues returns issues according start and limit // Note: issue label description and colors are not supported by the go-gitlab library at this time -// TODO: figure out how to transfer issue reactions func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { state := "all" sort := "asc" @@ -333,7 +341,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er var allIssues = make([]*base.Issue, 0, perPage) - issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil) + issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) if err != nil { return nil, false, fmt.Errorf("error while listing issues: %v", err) } @@ -351,6 +359,22 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er milestone = issue.Milestone.Title } + var reactions []*base.Reaction + var awardPage = 1 + for { + awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) + if err != nil { + return nil, false, fmt.Errorf("error while listing issue awards: %v", err) + } + if len(awards) < perPage { + break + } + for i := range awards { + reactions = append(reactions, g.awardToReaction(awards[i])) + } + awardPage++ + } + allIssues = append(allIssues, &base.Issue{ Title: issue.Title, Number: int64(issue.IID), @@ -361,6 +385,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er State: issue.State, Created: *issue.CreatedAt, Labels: labels, + Reactions: reactions, Closed: issue.ClosedAt, IsLocked: issue.DiscussionLocked, Updated: *issue.UpdatedAt, @@ -374,6 +399,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er } // GetComments returns comments according issueNumber +// TODO: figure out how to transfer comment reactions func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) { var allComments = make([]*base.Comment, 0, 100) @@ -390,14 +416,14 @@ func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, erro comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListIssueDiscussionsOptions{ Page: page, PerPage: 100, - }, nil) + }, nil, gitlab.WithContext(g.ctx)) } else { // If this is a PR, we need to figure out the Gitlab/original PR ID to be passed below realIssueNumber = issueNumber - g.issueCount comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListMergeRequestDiscussionsOptions{ Page: page, PerPage: 100, - }, nil) + }, nil, gitlab.WithContext(g.ctx)) } if err != nil { @@ -438,8 +464,7 @@ func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, erro } // GetPullRequests returns pull requests according page and perPage -func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) { - +func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { opt := &gitlab.ListProjectMergeRequestsOptions{ ListOptions: gitlab.ListOptions{ PerPage: perPage, @@ -452,9 +477,9 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque var allPRs = make([]*base.PullRequest, 0, perPage) - prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil) + prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) if err != nil { - return nil, fmt.Errorf("error while listing merge requests: %v", err) + return nil, false, fmt.Errorf("error while listing merge requests: %v", err) } for _, pr := range prs { @@ -491,6 +516,22 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque milestone = pr.Milestone.Title } + var reactions []*base.Reaction + var awardPage = 1 + for { + awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) + if err != nil { + return nil, false, fmt.Errorf("error while listing merge requests awards: %v", err) + } + if len(awards) < perPage { + break + } + for i := range awards { + reactions = append(reactions, g.awardToReaction(awards[i])) + } + awardPage++ + } + // Add the PR ID to the Issue Count because PR and Issues share ID space in Gitea newPRNumber := g.issueCount + int64(pr.IID) @@ -510,6 +551,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque MergeCommitSHA: pr.MergeCommitSHA, MergedTime: mergeTime, IsLocked: locked, + Reactions: reactions, Head: base.PullRequestBranch{ Ref: pr.SourceBranch, SHA: pr.SHA, @@ -527,13 +569,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque }) } - return allPRs, nil + return allPRs, len(prs) < perPage, nil } // GetReviews returns pull requests review func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, error) { - - state, _, err := g.client.MergeRequestApprovals.GetApprovalState(g.repoID, int(pullRequestNumber)) + state, _, err := g.client.MergeRequestApprovals.GetApprovalState(g.repoID, int(pullRequestNumber), gitlab.WithContext(g.ctx)) if err != nil { return nil, err } @@ -560,3 +601,11 @@ func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, return reviews, nil } + +func (g *GitlabDownloader) awardToReaction(award *gitlab.AwardEmoji) *base.Reaction { + return &base.Reaction{ + UserID: int64(award.User.ID), + UserName: award.User.Username, + Content: award.Name, + } +} diff --git a/modules/migrations/gitlab_test.go b/modules/migrations/gitlab_test.go index 003da5bbdfd1..f64d72147cf5 100644 --- a/modules/migrations/gitlab_test.go +++ b/modules/migrations/gitlab_test.go @@ -5,6 +5,8 @@ package migrations import ( + "context" + "fmt" "net/http" "os" "testing" @@ -27,19 +29,20 @@ func TestGitlabDownloadRepo(t *testing.T) { t.Skipf("Can't access test repo, skipping %s", t.Name()) } - downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken, "") - if downloader == nil { - t.Fatal("NewGitlabDownloader is nil") + downloader, err := NewGitlabDownloader(context.Background(), "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken) + if err != nil { + t.Fatal(fmt.Sprintf("NewGitlabDownloader is nil: %v", err)) } repo, err := downloader.GetRepoInfo() assert.NoError(t, err) // Repo Owner is blank in Gitlab Group repos assert.EqualValues(t, &base.Repository{ - Name: "test_repo", - Owner: "", - Description: "Test repository for testing migration from gitlab to gitea", - CloneURL: "https://gitlab.com/gitea/test_repo.git", - OriginalURL: "https://gitlab.com/gitea/test_repo", + Name: "test_repo", + Owner: "", + Description: "Test repository for testing migration from gitlab to gitea", + CloneURL: "https://gitlab.com/gitea/test_repo.git", + OriginalURL: "https://gitlab.com/gitea/test_repo", + DefaultBranch: "master", }, repo) topics, err := downloader.GetTopics() @@ -129,7 +132,7 @@ func TestGitlabDownloadRepo(t *testing.T) { PosterName: "lafriks", State: "closed", Created: time.Date(2019, 11, 28, 8, 43, 35, 459000000, time.UTC), - Updated: time.Date(2019, 11, 28, 8, 46, 23, 275000000, time.UTC), + Updated: time.Date(2019, 11, 28, 8, 46, 23, 304000000, time.UTC), Labels: []*base.Label{ { Name: "bug", @@ -138,8 +141,18 @@ func TestGitlabDownloadRepo(t *testing.T) { Name: "discussion", }, }, - Reactions: nil, - Closed: &closed1, + Reactions: []*base.Reaction{ + { + UserID: 1241334, + UserName: "lafriks", + Content: "thumbsup", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "open_mouth", + }}, + Closed: &closed1, }, { Number: 2, @@ -156,8 +169,38 @@ func TestGitlabDownloadRepo(t *testing.T) { Name: "duplicate", }, }, - Reactions: nil, - Closed: &closed2, + Reactions: []*base.Reaction{ + { + UserID: 1241334, + UserName: "lafriks", + Content: "thumbsup", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "thumbsdown", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "laughing", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "tada", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "confused", + }, + { + UserID: 1241334, + UserName: "lafriks", + Content: "hearts", + }}, + Closed: &closed2, }, }, issues) @@ -170,7 +213,6 @@ func TestGitlabDownloadRepo(t *testing.T) { PosterID: 1241334, PosterName: "lafriks", Created: time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC), - Updated: time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC), Content: "This is a comment", Reactions: nil, }, @@ -200,26 +242,35 @@ func TestGitlabDownloadRepo(t *testing.T) { }, }, comments[:4]) - prs, err := downloader.GetPullRequests(1, 1) + prs, _, err := downloader.GetPullRequests(1, 1) assert.NoError(t, err) assert.Len(t, prs, 1) assert.EqualValues(t, []*base.PullRequest{ { - Number: 4, - Title: "Test branch", - Content: "do not merge this PR", - Milestone: "1.0.0", - PosterID: 1241334, - PosterName: "lafriks", - State: "opened", - Created: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC), - Updated: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC), + Number: 4, + OriginalNumber: 2, + Title: "Test branch", + Content: "do not merge this PR", + Milestone: "1.0.0", + PosterID: 1241334, + PosterName: "lafriks", + State: "opened", + Created: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC), Labels: []*base.Label{ { Name: "bug", }, }, + Reactions: []*base.Reaction{{ + UserID: 4575606, + UserName: "real6543", + Content: "thumbsup", + }, { + UserID: 4575606, + UserName: "real6543", + Content: "tada", + }}, PatchURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2.patch", Head: base.PullRequestBranch{ Ref: "feat/test", @@ -243,13 +294,20 @@ func TestGitlabDownloadRepo(t *testing.T) { rvs, err := downloader.GetReviews(1) assert.NoError(t, err) - if assert.Len(t, prs, 2) { - assert.EqualValues(t, 527793, rvs[0].ReviewerID) - assert.EqualValues(t, "axifive", rvs[0].ReviewerName) - assert.EqualValues(t, "APPROVED", rvs[0].State) - assert.EqualValues(t, 4102996, rvs[1].ReviewerID) - assert.EqualValues(t, "zeripath", rvs[1].ReviewerName) - assert.EqualValues(t, "APPROVED", rvs[1].State) + if assert.Len(t, rvs, 2) { + for i := range rvs { + switch rvs[i].ReviewerID { + case 4102996: + assert.EqualValues(t, "zeripath", rvs[i].ReviewerName) + assert.EqualValues(t, "APPROVED", rvs[i].State) + case 527793: + assert.EqualValues(t, "axifive", rvs[i].ReviewerName) + assert.EqualValues(t, "APPROVED", rvs[i].State) + default: + t.Errorf("Unexpected Reviewer ID: %d", rvs[i].ReviewerID) + + } + } } rvs, err = downloader.GetReviews(2) assert.NoError(t, err) diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go index c970ba692088..191f2a555034 100644 --- a/modules/migrations/migrate.go +++ b/modules/migrations/migrate.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" ) // MigrateOptions is equal to base.MigrateOptions @@ -33,18 +32,15 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, var ( downloader base.Downloader uploader = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName) - theFactory base.DownloaderFactory + err error ) for _, factory := range factories { - if match, err := factory.Match(opts); err != nil { - return nil, err - } else if match { - downloader, err = factory.New(opts) + if factory.GitServiceType() == opts.GitServiceType { + downloader, err = factory.New(ctx, opts) if err != nil { return nil, err } - theFactory = factory break } } @@ -57,21 +53,16 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, opts.Comments = false opts.Issues = false opts.PullRequests = false - opts.GitServiceType = structs.PlainGitService downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr) log.Trace("Will migrate from git: %s", opts.OriginalURL) - } else if opts.GitServiceType == structs.NotMigrated { - opts.GitServiceType = theFactory.GitServiceType() } uploader.gitServiceType = opts.GitServiceType if setting.Migrations.MaxAttempts > 1 { - downloader = base.NewRetryDownloader(downloader, setting.Migrations.MaxAttempts, setting.Migrations.RetryBackoff) + downloader = base.NewRetryDownloader(ctx, downloader, setting.Migrations.MaxAttempts, setting.Migrations.RetryBackoff) } - downloader.SetContext(ctx) - if err := migrateRepository(downloader, uploader, opts); err != nil { if err1 := uploader.Rollback(); err1 != nil { log.Error("rollback failed: %v", err1) @@ -169,7 +160,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts relBatchSize = len(releases) } - if err := uploader.CreateReleases(releases[:relBatchSize]...); err != nil { + if err := uploader.CreateReleases(downloader, releases[:relBatchSize]...); err != nil { return err } releases = releases[relBatchSize:] @@ -238,7 +229,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts log.Trace("migrating pull requests and comments") var prBatchSize = uploader.MaxBatchInsertSize("pullrequest") for i := 1; ; i++ { - prs, err := downloader.GetPullRequests(i, prBatchSize) + prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize) if err != nil { return err } @@ -309,7 +300,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts } } - if len(prs) < prBatchSize { + if isEnd { break } } diff --git a/modules/nosql/leveldb.go b/modules/nosql/leveldb.go new file mode 100644 index 000000000000..5da2291e036e --- /dev/null +++ b/modules/nosql/leveldb.go @@ -0,0 +1,25 @@ +// 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 nosql + +import "net/url" + +// ToLevelDBURI converts old style connections to a LevelDBURI +// +// A LevelDBURI matches the pattern: +// +// leveldb://path[?[option=value]*] +// +// We have previously just provided the path but this prevent other options +func ToLevelDBURI(connection string) *url.URL { + uri, err := url.Parse(connection) + if err == nil && uri.Scheme == "leveldb" { + return uri + } + uri, _ = url.Parse("leveldb://common") + uri.Host = "" + uri.Path = connection + return uri +} diff --git a/modules/nosql/manager.go b/modules/nosql/manager.go new file mode 100644 index 000000000000..ad61d6d18c3f --- /dev/null +++ b/modules/nosql/manager.go @@ -0,0 +1,71 @@ +// 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 nosql + +import ( + "strconv" + "sync" + "time" + + "github.com/go-redis/redis/v7" + "github.com/syndtr/goleveldb/leveldb" +) + +var manager *Manager + +// Manager is the nosql connection manager +type Manager struct { + mutex sync.Mutex + + RedisConnections map[string]*redisClientHolder + LevelDBConnections map[string]*levelDBHolder +} + +type redisClientHolder struct { + redis.UniversalClient + name []string + count int64 +} + +func (r *redisClientHolder) Close() error { + return manager.CloseRedisClient(r.name[0]) +} + +type levelDBHolder struct { + name []string + count int64 + db *leveldb.DB +} + +func init() { + _ = GetManager() +} + +// GetManager returns a Manager and initializes one as singleton is there's none yet +func GetManager() *Manager { + if manager == nil { + manager = &Manager{ + RedisConnections: make(map[string]*redisClientHolder), + LevelDBConnections: make(map[string]*levelDBHolder), + } + } + return manager +} + +func valToTimeDuration(vs []string) (result time.Duration) { + var err error + for _, v := range vs { + result, err = time.ParseDuration(v) + if err != nil { + var val int + val, err = strconv.Atoi(v) + result = time.Duration(val) + } + if err == nil { + return + } + } + return +} diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go new file mode 100644 index 000000000000..769d5002d0e7 --- /dev/null +++ b/modules/nosql/manager_leveldb.go @@ -0,0 +1,151 @@ +// 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 nosql + +import ( + "path" + "strconv" + "strings" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +// CloseLevelDB closes a levelDB +func (m *Manager) CloseLevelDB(connection string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + db, ok := m.LevelDBConnections[connection] + if !ok { + connection = ToLevelDBURI(connection).String() + db, ok = m.LevelDBConnections[connection] + } + if !ok { + return nil + } + + db.count-- + if db.count > 0 { + return nil + } + + for _, name := range db.name { + delete(m.LevelDBConnections, name) + } + return db.db.Close() +} + +// GetLevelDB gets a levelDB for a particular connection +func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + db, ok := m.LevelDBConnections[connection] + if ok { + db.count++ + + return db.db, nil + } + dataDir := connection + uri := ToLevelDBURI(connection) + db = &levelDBHolder{ + name: []string{connection, uri.String()}, + } + + dataDir = path.Join(uri.Host, uri.Path) + opts := &opt.Options{} + for k, v := range uri.Query() { + switch replacer.Replace(strings.ToLower(k)) { + case "blockcachecapacity": + opts.BlockCacheCapacity, _ = strconv.Atoi(v[0]) + case "blockcacheevictremoved": + opts.BlockCacheEvictRemoved, _ = strconv.ParseBool(v[0]) + case "blockrestartinterval": + opts.BlockRestartInterval, _ = strconv.Atoi(v[0]) + case "blocksize": + opts.BlockSize, _ = strconv.Atoi(v[0]) + case "compactionexpandlimitfactor": + opts.CompactionExpandLimitFactor, _ = strconv.Atoi(v[0]) + case "compactiongpoverlapsfactor": + opts.CompactionGPOverlapsFactor, _ = strconv.Atoi(v[0]) + case "compactionl0trigger": + opts.CompactionL0Trigger, _ = strconv.Atoi(v[0]) + case "compactionsourcelimitfactor": + opts.CompactionSourceLimitFactor, _ = strconv.Atoi(v[0]) + case "compactiontablesize": + opts.CompactionTableSize, _ = strconv.Atoi(v[0]) + case "compactiontablesizemultiplier": + opts.CompactionTableSizeMultiplier, _ = strconv.ParseFloat(v[0], 64) + case "compactiontablesizemultiplierperlevel": + for _, val := range v { + f, _ := strconv.ParseFloat(val, 64) + opts.CompactionTableSizeMultiplierPerLevel = append(opts.CompactionTableSizeMultiplierPerLevel, f) + } + case "compactiontotalsize": + opts.CompactionTotalSize, _ = strconv.Atoi(v[0]) + case "compactiontotalsizemultiplier": + opts.CompactionTotalSizeMultiplier, _ = strconv.ParseFloat(v[0], 64) + case "compactiontotalsizemultiplierperlevel": + for _, val := range v { + f, _ := strconv.ParseFloat(val, 64) + opts.CompactionTotalSizeMultiplierPerLevel = append(opts.CompactionTotalSizeMultiplierPerLevel, f) + } + case "compression": + val, _ := strconv.Atoi(v[0]) + opts.Compression = opt.Compression(val) + case "disablebufferpool": + opts.DisableBufferPool, _ = strconv.ParseBool(v[0]) + case "disableblockcache": + opts.DisableBlockCache, _ = strconv.ParseBool(v[0]) + case "disablecompactionbackoff": + opts.DisableCompactionBackoff, _ = strconv.ParseBool(v[0]) + case "disablelargebatchtransaction": + opts.DisableLargeBatchTransaction, _ = strconv.ParseBool(v[0]) + case "errorifexist": + opts.ErrorIfExist, _ = strconv.ParseBool(v[0]) + case "errorifmissing": + opts.ErrorIfMissing, _ = strconv.ParseBool(v[0]) + case "iteratorsamplingrate": + opts.IteratorSamplingRate, _ = strconv.Atoi(v[0]) + case "nosync": + opts.NoSync, _ = strconv.ParseBool(v[0]) + case "nowritemerge": + opts.NoWriteMerge, _ = strconv.ParseBool(v[0]) + case "openfilescachecapacity": + opts.OpenFilesCacheCapacity, _ = strconv.Atoi(v[0]) + case "readonly": + opts.ReadOnly, _ = strconv.ParseBool(v[0]) + case "strict": + val, _ := strconv.Atoi(v[0]) + opts.Strict = opt.Strict(val) + case "writebuffer": + opts.WriteBuffer, _ = strconv.Atoi(v[0]) + case "writel0pausetrigger": + opts.WriteL0PauseTrigger, _ = strconv.Atoi(v[0]) + case "writel0slowdowntrigger": + opts.WriteL0SlowdownTrigger, _ = strconv.Atoi(v[0]) + case "clientname": + db.name = append(db.name, v[0]) + } + } + + var err error + db.db, err = leveldb.OpenFile(dataDir, opts) + if err != nil { + if !errors.IsCorrupted(err) { + return nil, err + } + db.db, err = leveldb.RecoverFile(dataDir, opts) + if err != nil { + return nil, err + } + } + + for _, name := range db.name { + m.LevelDBConnections[name] = db + } + db.count++ + return db.db, nil +} diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go new file mode 100644 index 000000000000..7792a90112a6 --- /dev/null +++ b/modules/nosql/manager_redis.go @@ -0,0 +1,205 @@ +// 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 nosql + +import ( + "crypto/tls" + "path" + "strconv" + "strings" + + "github.com/go-redis/redis/v7" +) + +var replacer = strings.NewReplacer("_", "", "-", "") + +// CloseRedisClient closes a redis client +func (m *Manager) CloseRedisClient(connection string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + client, ok := m.RedisConnections[connection] + if !ok { + connection = ToRedisURI(connection).String() + client, ok = m.RedisConnections[connection] + } + if !ok { + return nil + } + + client.count-- + if client.count > 0 { + return nil + } + + for _, name := range client.name { + delete(m.RedisConnections, name) + } + return client.UniversalClient.Close() +} + +// GetRedisClient gets a redis client for a particular connection +func (m *Manager) GetRedisClient(connection string) redis.UniversalClient { + m.mutex.Lock() + defer m.mutex.Unlock() + client, ok := m.RedisConnections[connection] + if ok { + client.count++ + return client + } + + uri := ToRedisURI(connection) + client, ok = m.RedisConnections[uri.String()] + if ok { + client.count++ + return client + } + client = &redisClientHolder{ + name: []string{connection, uri.String()}, + } + + opts := &redis.UniversalOptions{} + tlsConfig := &tls.Config{} + + // Handle username/password + if password, ok := uri.User.Password(); ok { + opts.Password = password + // Username does not appear to be handled by redis.Options + opts.Username = uri.User.Username() + } else if uri.User.Username() != "" { + // assume this is the password + opts.Password = uri.User.Username() + } + + // Now handle the uri query sets + for k, v := range uri.Query() { + switch replacer.Replace(strings.ToLower(k)) { + case "addr": + opts.Addrs = append(opts.Addrs, v...) + case "addrs": + opts.Addrs = append(opts.Addrs, strings.Split(v[0], ",")...) + case "username": + opts.Username = v[0] + case "password": + opts.Password = v[0] + case "database": + fallthrough + case "db": + opts.DB, _ = strconv.Atoi(v[0]) + case "maxretries": + opts.MaxRetries, _ = strconv.Atoi(v[0]) + case "minretrybackoff": + opts.MinRetryBackoff = valToTimeDuration(v) + case "maxretrybackoff": + opts.MaxRetryBackoff = valToTimeDuration(v) + case "timeout": + timeout := valToTimeDuration(v) + if timeout != 0 { + if opts.DialTimeout == 0 { + opts.DialTimeout = timeout + } + if opts.ReadTimeout == 0 { + opts.ReadTimeout = timeout + } + } + case "dialtimeout": + opts.DialTimeout = valToTimeDuration(v) + case "readtimeout": + opts.ReadTimeout = valToTimeDuration(v) + case "writetimeout": + opts.WriteTimeout = valToTimeDuration(v) + case "poolsize": + opts.PoolSize, _ = strconv.Atoi(v[0]) + case "minidleconns": + opts.MinIdleConns, _ = strconv.Atoi(v[0]) + case "pooltimeout": + opts.PoolTimeout = valToTimeDuration(v) + case "idletimeout": + opts.IdleTimeout = valToTimeDuration(v) + case "idlecheckfrequency": + opts.IdleCheckFrequency = valToTimeDuration(v) + case "maxredirects": + opts.MaxRedirects, _ = strconv.Atoi(v[0]) + case "readonly": + opts.ReadOnly, _ = strconv.ParseBool(v[0]) + case "routebylatency": + opts.RouteByLatency, _ = strconv.ParseBool(v[0]) + case "routerandomly": + opts.RouteRandomly, _ = strconv.ParseBool(v[0]) + case "sentinelmasterid": + fallthrough + case "mastername": + opts.MasterName = v[0] + case "skipverify": + fallthrough + case "insecureskipverify": + insecureSkipVerify, _ := strconv.ParseBool(v[0]) + tlsConfig.InsecureSkipVerify = insecureSkipVerify + case "clientname": + client.name = append(client.name, v[0]) + } + } + + switch uri.Scheme { + case "redis+sentinels": + fallthrough + case "rediss+sentinel": + opts.TLSConfig = tlsConfig + fallthrough + case "redis+sentinel": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + + client.UniversalClient = redis.NewFailoverClient(opts.Failover()) + case "redis+clusters": + fallthrough + case "rediss+cluster": + opts.TLSConfig = tlsConfig + fallthrough + case "redis+cluster": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + client.UniversalClient = redis.NewClusterClient(opts.Cluster()) + case "redis+socket": + simpleOpts := opts.Simple() + simpleOpts.Network = "unix" + simpleOpts.Addr = path.Join(uri.Host, uri.Path) + client.UniversalClient = redis.NewClient(simpleOpts) + case "rediss": + opts.TLSConfig = tlsConfig + fallthrough + case "redis": + if uri.Host != "" { + opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) + } + if uri.Path != "" { + if db, err := strconv.Atoi(uri.Path); err == nil { + opts.DB = db + } + } + client.UniversalClient = redis.NewClient(opts.Simple()) + default: + return nil + } + + for _, name := range client.name { + m.RedisConnections[name] = client + } + + client.count++ + + return client +} diff --git a/modules/nosql/redis.go b/modules/nosql/redis.go new file mode 100644 index 000000000000..528f5fc802c8 --- /dev/null +++ b/modules/nosql/redis.go @@ -0,0 +1,102 @@ +// 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 nosql + +import ( + "net/url" + "strconv" + "strings" +) + +// The file contains common redis connection functions + +// ToRedisURI converts old style connections to a RedisURI +// +// A RedisURI matches the pattern: +// +// redis://[username:password@]host[:port][/database][?[option=value]*] +// rediss://[username:password@]host[:port][/database][?[option=value]*] +// redis+socket://[username:password@]path[/database][?[option=value]*] +// redis+sentinel://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*] +// redis+cluster://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*] +// +// We have previously used a URI like: +// addrs=127.0.0.1:6379 db=0 +// network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 +// +// We need to convert this old style to the new style +func ToRedisURI(connection string) *url.URL { + uri, err := url.Parse(connection) + if err == nil && strings.HasPrefix(uri.Scheme, "redis") { + // OK we're going to assume that this is a reasonable redis URI + return uri + } + + // Let's set a nice default + uri, _ = url.Parse("redis://127.0.0.1:6379/0") + network := "tcp" + query := uri.Query() + + // OK so there are two types: Space delimited and Comma delimited + // Let's assume that we have a space delimited string - as this is the most common + fields := strings.Fields(connection) + if len(fields) == 1 { + // It's a comma delimited string, then... + fields = strings.Split(connection, ",") + + } + for _, f := range fields { + items := strings.SplitN(f, "=", 2) + if len(items) < 2 { + continue + } + switch strings.ToLower(items[0]) { + case "network": + if items[1] == "unix" { + uri.Scheme = "redis+socket" + } + network = items[1] + case "addrs": + uri.Host = items[1] + // now we need to handle the clustering + if strings.Contains(items[1], ",") && network == "tcp" { + uri.Scheme = "redis+cluster" + } + case "addr": + uri.Host = items[1] + case "password": + uri.User = url.UserPassword(uri.User.Username(), items[1]) + case "username": + password, set := uri.User.Password() + if !set { + uri.User = url.User(items[1]) + } else { + uri.User = url.UserPassword(items[1], password) + } + case "db": + uri.Path = "/" + items[1] + case "idle_timeout": + _, err := strconv.Atoi(items[1]) + if err == nil { + query.Add("idle_timeout", items[1]+"s") + } else { + query.Add("idle_timeout", items[1]) + } + default: + // Other options become query params + query.Add(items[0], items[1]) + } + } + + // Finally we need to fix up the Host if we have a unix port + if uri.Scheme == "redis+socket" { + query.Set("db", uri.Path) + uri.Path = uri.Host + uri.Host = "" + } + uri.RawQuery = query.Encode() + + return uri +} diff --git a/modules/nosql/redis_test.go b/modules/nosql/redis_test.go new file mode 100644 index 000000000000..c70d236bdc9c --- /dev/null +++ b/modules/nosql/redis_test.go @@ -0,0 +1,35 @@ +// 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 nosql + +import ( + "testing" +) + +func TestToRedisURI(t *testing.T) { + tests := []struct { + name string + connection string + want string + }{ + { + name: "old_default", + connection: "addrs=127.0.0.1:6379 db=0", + want: "redis://127.0.0.1:6379/0", + }, + { + name: "old_macaron_session_default", + connection: "network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180", + want: "redis://:macaron@127.0.0.1:6379/0?idle_timeout=180s&pool_size=100", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToRedisURI(tt.connection); got == nil || got.String() != tt.want { + t.Errorf(`ToRedisURI(%q) = %s, want %s`, tt.connection, got.String(), tt.want) + } + }) + } +} diff --git a/modules/notification/action/action.go b/modules/notification/action/action.go index 040cf3df1030..f7078d349345 100644 --- a/modules/notification/action/action.go +++ b/modules/notification/action/action.go @@ -92,13 +92,22 @@ func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *model act := &models.Action{ ActUserID: doer.ID, ActUser: doer, - Content: fmt.Sprintf("%d|%s", issue.Index, comment.Content), RepoID: issue.Repo.ID, Repo: issue.Repo, Comment: comment, CommentID: comment.ID, IsPrivate: issue.Repo.IsPrivate, } + + content := "" + + if len(comment.Content) > 200 { + content = comment.Content[:strings.LastIndex(comment.Content[0:200], " ")] + "…" + } else { + content = comment.Content + } + act.Content = fmt.Sprintf("%d|%s", issue.Index, content) + if issue.IsPull { act.OpType = models.ActionCommentPull } else { diff --git a/modules/notification/base/notifier.go b/modules/notification/base/notifier.go index 428f9a9544f3..5cd2b4c06059 100644 --- a/modules/notification/base/notifier.go +++ b/modules/notification/base/notifier.go @@ -28,6 +28,7 @@ type Notifier interface { NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) NotifyIssueClearLabels(doer *models.User, issue *models.Issue) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) + NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue, addedLabels []*models.Label, removedLabels []*models.Label) diff --git a/modules/notification/base/null.go b/modules/notification/base/null.go index b2ce0742b6c1..15d06ec85630 100644 --- a/modules/notification/base/null.go +++ b/modules/notification/base/null.go @@ -102,6 +102,10 @@ func (*NullNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Iss func (*NullNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) { } +// NotifyIssueChangeRef places a place holder function +func (*NullNotifier) NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldTitle string) { +} + // NotifyIssueChangeLabels places a place holder function func (*NullNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue, addedLabels []*models.Label, removedLabels []*models.Label) { diff --git a/modules/notification/indexer/indexer.go b/modules/notification/indexer/indexer.go index f292d7339b20..6e848e6318ad 100644 --- a/modules/notification/indexer/indexer.go +++ b/modules/notification/indexer/indexer.go @@ -148,3 +148,7 @@ func (r *indexerNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) { issue_indexer.UpdateIssueIndexer(issue) } + +func (r *indexerNotifier) NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) { + issue_indexer.UpdateIssueIndexer(issue) +} diff --git a/modules/notification/notification.go b/modules/notification/notification.go index d17b13b9e53f..57f1e7c16df8 100644 --- a/modules/notification/notification.go +++ b/modules/notification/notification.go @@ -178,6 +178,13 @@ func NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle str } } +// NotifyIssueChangeRef notifies change reference to notifiers +func NotifyIssueChangeRef(doer *models.User, issue *models.Issue, oldRef string) { + for _, notifier := range notifiers { + notifier.NotifyIssueChangeRef(doer, issue, oldRef) + } +} + // NotifyIssueChangeLabels notifies change labels to notifiers func NotifyIssueChangeLabels(doer *models.User, issue *models.Issue, addedLabels []*models.Label, removedLabels []*models.Label) { diff --git a/modules/notification/webhook/webhook.go b/modules/notification/webhook/webhook.go index 625cf119a9ac..231112ec4c6c 100644 --- a/modules/notification/webhook/webhook.go +++ b/modules/notification/webhook/webhook.go @@ -53,7 +53,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ @@ -61,7 +61,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } if err != nil { @@ -77,7 +77,7 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo if err := webhook_module.PrepareWebhooks(oldRepo, models.HookEventFork, &api.ForkPayload{ Forkee: oldRepo.APIFormat(oldMode), Repo: repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }); err != nil { log.Error("PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) } @@ -89,8 +89,8 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoCreated, Repository: repo.APIFormat(models.AccessModeOwner), - Organization: u.APIFormat(), - Sender: doer.APIFormat(), + Organization: convert.ToUser(u, false, false), + Sender: convert.ToUser(doer, false, false), }); err != nil { log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err) } @@ -99,30 +99,26 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) { // Add to hook queue for created repo after session commit. - if u.IsOrganization() { - if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoCreated, - Repository: repo.APIFormat(models.AccessModeOwner), - Organization: u.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err) - } + if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoCreated, + Repository: repo.APIFormat(models.AccessModeOwner), + Organization: convert.ToUser(u, false, false), + Sender: convert.ToUser(doer, false, false), + }); err != nil { + log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err) } } func (m *webhookNotifier) NotifyDeleteRepository(doer *models.User, repo *models.Repository) { u := repo.MustOwner() - if u.IsOrganization() { - if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoDeleted, - Repository: repo.APIFormat(models.AccessModeOwner), - Organization: u.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err) - } + if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoDeleted, + Repository: repo.APIFormat(models.AccessModeOwner), + Organization: convert.ToUser(u, false, false), + Sender: convert.ToUser(doer, false, false), + }); err != nil { + log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err) } } @@ -139,7 +135,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), } if removed { apiPullRequest.Action = api.HookIssueUnassigned @@ -157,7 +153,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), } if removed { apiIssue.Action = api.HookIssueUnassigned @@ -191,7 +187,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model }, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ @@ -204,7 +200,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model }, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: issue.Poster.APIFormat(), + Sender: convert.ToUser(issue.Poster, false, false), }) } @@ -226,7 +222,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), } if isClosed { apiPullRequest.Action = api.HookIssueClosed @@ -239,7 +235,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), } if isClosed { apiIssue.Action = api.HookIssueClosed @@ -269,7 +265,7 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) { Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: issue.Poster.APIFormat(), + Sender: convert.ToUser(issue.Poster, false, false), }); err != nil { log.Error("PrepareWebhooks: %v", err) } @@ -295,7 +291,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) { Index: pull.Issue.Index, PullRequest: convert.ToAPIPullRequest(pull), Repository: pull.Issue.Repo.APIFormat(mode), - Sender: pull.Issue.Poster.APIFormat(), + Sender: convert.ToUser(pull.Issue.Poster, false, false), }); err != nil { log.Error("PrepareWebhooks: %v", err) } @@ -316,7 +312,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod }, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ @@ -329,7 +325,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod }, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } if err != nil { @@ -359,28 +355,28 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, Issue: convert.ToAPIIssue(c.Issue), - Comment: c.APIFormat(), + Comment: convert.ToComment(c), Changes: &api.ChangesPayload{ Body: &api.ChangesFromPayload{ From: oldContent, }, }, Repository: c.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: true, }) } else { err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, Issue: convert.ToAPIIssue(c.Issue), - Comment: c.APIFormat(), + Comment: convert.ToComment(c), Changes: &api.ChangesPayload{ Body: &api.ChangesFromPayload{ From: oldContent, }, }, Repository: c.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: false, }) } @@ -399,18 +395,18 @@ func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *mode err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(issue), - Comment: comment.APIFormat(), + Comment: convert.ToComment(comment), Repository: repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: true, }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(issue), - Comment: comment.APIFormat(), + Comment: convert.ToComment(comment), Repository: repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: false, }) } @@ -443,18 +439,18 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentDeleted, Issue: convert.ToAPIIssue(comment.Issue), - Comment: comment.APIFormat(), + Comment: convert.ToComment(comment), Repository: comment.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: true, }) } else { err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentDeleted, Issue: convert.ToAPIIssue(comment.Issue), - Comment: comment.APIFormat(), + Comment: convert.ToComment(comment), Repository: comment.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), IsPull: false, }) } @@ -494,7 +490,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(models.AccessModeNone), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ @@ -502,7 +498,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } if err != nil { @@ -536,7 +532,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } else { err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueMilestone, &api.IssuePayload{ @@ -544,7 +540,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m Index: issue.Index, Issue: convert.ToAPIIssue(issue), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) } if err != nil { @@ -553,7 +549,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m } func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) { - apiPusher := pusher.APIFormat() + apiPusher := convert.ToUser(pusher, false, false) apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL()) if err != nil { log.Error("commits.ToAPIPayloadCommits failed: %v", err) @@ -602,7 +598,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod Index: pr.Issue.Index, PullRequest: convert.ToAPIPullRequest(pr), Repository: pr.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), Action: api.HookIssueClosed, } @@ -635,7 +631,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, }, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }) if err != nil { @@ -674,7 +670,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review Index: review.Issue.Index, PullRequest: convert.ToAPIPullRequest(pr), Repository: review.Issue.Repo.APIFormat(mode), - Sender: review.Reviewer.APIFormat(), + Sender: convert.ToUser(review.Reviewer, false, false), Review: &api.ReviewPayload{ Type: string(reviewHookType), Content: review.Content, @@ -685,7 +681,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review } func (m *webhookNotifier) NotifyCreateRef(pusher *models.User, repo *models.Repository, refType, refFullName string) { - apiPusher := pusher.APIFormat() + apiPusher := convert.ToUser(pusher, false, false) apiRepo := repo.APIFormat(models.AccessModeNone) refName := git.RefEndName(refFullName) @@ -729,14 +725,14 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m Index: pr.Issue.Index, PullRequest: convert.ToAPIPullRequest(pr), Repository: pr.Issue.Repo.APIFormat(models.AccessModeNone), - Sender: doer.APIFormat(), + Sender: convert.ToUser(doer, false, false), }); err != nil { log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err) } } func (m *webhookNotifier) NotifyDeleteRef(pusher *models.User, repo *models.Repository, refType, refFullName string) { - apiPusher := pusher.APIFormat() + apiPusher := convert.ToUser(pusher, false, false) apiRepo := repo.APIFormat(models.AccessModeNone) refName := git.RefEndName(refFullName) @@ -760,9 +756,9 @@ func sendReleaseHook(doer *models.User, rel *models.Release, action api.HookRele mode, _ := models.AccessLevel(rel.Publisher, rel.Repo) if err := webhook_module.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{ Action: action, - Release: rel.APIFormat(), + Release: convert.ToRelease(rel), Repository: rel.Repo.APIFormat(mode), - Sender: rel.Publisher.APIFormat(), + Sender: convert.ToUser(rel.Publisher, false, false), }); err != nil { log.Error("PrepareWebhooks: %v", err) } @@ -781,7 +777,7 @@ func (m *webhookNotifier) NotifyDeleteRelease(doer *models.User, rel *models.Rel } func (m *webhookNotifier) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) { - apiPusher := pusher.APIFormat() + apiPusher := convert.ToUser(pusher, false, false) apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL()) if err != nil { log.Error("commits.ToAPIPayloadCommits failed: %v", err) diff --git a/modules/password/password.go b/modules/password/password.go index 1c4b9c514a45..e1f1f769ec73 100644 --- a/modules/password/password.go +++ b/modules/password/password.go @@ -6,6 +6,7 @@ package password import ( "bytes" + goContext "context" "crypto/rand" "math/big" "strings" @@ -88,7 +89,7 @@ func IsComplexEnough(pwd string) bool { return true } -// Generate a random password +// Generate a random password func Generate(n int) (string, error) { NewComplexity() buffer := make([]byte, n) @@ -101,7 +102,11 @@ func Generate(n int) (string, error) { } buffer[j] = validChars[rnd.Int64()] } - if IsComplexEnough(string(buffer)) && string(buffer[0]) != " " && string(buffer[n-1]) != " " { + pwned, err := IsPwned(goContext.Background(), string(buffer)) + if err != nil { + return "", err + } + if IsComplexEnough(string(buffer)) && !pwned && string(buffer[0]) != " " && string(buffer[n-1]) != " " { return string(buffer), nil } } diff --git a/modules/password/pwn.go b/modules/password/pwn.go new file mode 100644 index 000000000000..938524e6dee2 --- /dev/null +++ b/modules/password/pwn.go @@ -0,0 +1,30 @@ +// 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 password + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" + + "go.jolheiser.com/pwn" +) + +// IsPwned checks whether a password has been pwned +// NOTE: This func returns true if it encounters an error under the assumption that you ALWAYS want to check against +// HIBP, so not getting a response should block a password until it can be verified. +func IsPwned(ctx context.Context, password string) (bool, error) { + if !setting.PasswordCheckPwn { + return false, nil + } + + client := pwn.New(pwn.WithContext(ctx)) + count, err := client.CheckPassword(password, true) + if err != nil { + return true, err + } + + return count > 0, nil +} diff --git a/modules/private/serv.go b/modules/private/serv.go index 5b4a27f11621..235d99a2b9c2 100644 --- a/modules/private/serv.go +++ b/modules/private/serv.go @@ -47,6 +47,7 @@ type ServCommandResults struct { KeyID int64 KeyName string UserName string + UserEmail string UserID int64 OwnerName string RepoName string diff --git a/modules/public/public.go b/modules/public/public.go index 008fba9b0188..2dcc530a739a 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -159,7 +159,7 @@ func (opts *Options) handle(ctx *macaron.Context, log *log.Logger, opt *Options) // Add an Expires header to the static content if opt.ExpiresAfter > 0 { ctx.Resp.Header().Set("Expires", time.Now().Add(opt.ExpiresAfter).UTC().Format(http.TimeFormat)) - tag := GenerateETag(fmt.Sprintf("%d", fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat)) + tag := GenerateETag(fmt.Sprint(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat)) ctx.Resp.Header().Set("ETag", tag) if ctx.Req.Header.Get("If-None-Match") == tag { ctx.Resp.WriteHeader(304) diff --git a/modules/queue/helper.go b/modules/queue/helper.go index e6fb1b94f995..751e0cfadc0f 100644 --- a/modules/queue/helper.go +++ b/modules/queue/helper.go @@ -9,25 +9,56 @@ import ( "reflect" ) +// Mappable represents an interface that can MapTo another interface +type Mappable interface { + MapTo(v interface{}) error +} + // toConfig will attempt to convert a given configuration cfg into the provided exemplar type. // // It will tolerate the cfg being passed as a []byte or string of a json representation of the // exemplar or the correct type of the exemplar itself func toConfig(exemplar, cfg interface{}) (interface{}, error) { + + // First of all check if we've got the same type as the exemplar - if so it's all fine. if reflect.TypeOf(cfg).AssignableTo(reflect.TypeOf(exemplar)) { return cfg, nil } + // Now if not - does it provide a MapTo function we can try? + if mappable, ok := cfg.(Mappable); ok { + newVal := reflect.New(reflect.TypeOf(exemplar)) + if err := mappable.MapTo(newVal.Interface()); err == nil { + return newVal.Elem().Interface(), nil + } + // MapTo has failed us ... let's try the json route ... + } + + // OK we've been passed a byte array right? configBytes, ok := cfg.([]byte) if !ok { - configStr, ok := cfg.(string) - if !ok { - return nil, ErrInvalidConfiguration{cfg: cfg} - } + // oh ... it's a string then? + var configStr string + + configStr, ok = cfg.(string) configBytes = []byte(configStr) } + if !ok { + // hmm ... can we marshal it to json? + var err error + + configBytes, err = json.Marshal(cfg) + ok = (err == nil) + } + if !ok { + // no ... we've tried hard enough at this point - throw an error! + return nil, ErrInvalidConfiguration{cfg: cfg} + } + + // OK unmarshal the byte array into a new copy of the exemplar newVal := reflect.New(reflect.TypeOf(exemplar)) if err := json.Unmarshal(configBytes, newVal.Interface()); err != nil { + // If we can't unmarshal it then return an error! return nil, ErrInvalidConfiguration{cfg: cfg, err: err} } return newVal.Elem().Interface(), nil diff --git a/modules/queue/queue.go b/modules/queue/queue.go index e3c63310bef5..d08cba35a1ea 100644 --- a/modules/queue/queue.go +++ b/modules/queue/queue.go @@ -106,7 +106,64 @@ func (*DummyQueue) IsEmpty() bool { return true } -var queuesMap = map[Type]NewQueueFunc{DummyQueueType: NewDummyQueue} +// ImmediateType is the type to execute the function when push +const ImmediateType Type = "immediate" + +// NewImmediate creates a new false queue to execute the function when push +func NewImmediate(handler HandlerFunc, opts, exemplar interface{}) (Queue, error) { + return &Immediate{ + handler: handler, + }, nil +} + +// Immediate represents an direct execution queue +type Immediate struct { + handler HandlerFunc +} + +// Run does nothing +func (*Immediate) Run(_, _ func(context.Context, func())) {} + +// Push fakes a push of data to the queue +func (q *Immediate) Push(data Data) error { + return q.PushFunc(data, nil) +} + +// PushFunc fakes a push of data to the queue with a function. The function is never run. +func (q *Immediate) PushFunc(data Data, f func() error) error { + if f != nil { + if err := f(); err != nil { + return err + } + } + q.handler(data) + return nil +} + +// Has always returns false as this queue never does anything +func (*Immediate) Has(Data) (bool, error) { + return false, nil +} + +// Flush always returns nil +func (*Immediate) Flush(time.Duration) error { + return nil +} + +// FlushWithContext always returns nil +func (*Immediate) FlushWithContext(context.Context) error { + return nil +} + +// IsEmpty asserts that the queue is empty +func (*Immediate) IsEmpty() bool { + return true +} + +var queuesMap = map[Type]NewQueueFunc{ + DummyQueueType: NewDummyQueue, + ImmediateType: NewImmediate, +} // RegisteredTypes provides the list of requested types of queues func RegisteredTypes() []Type { diff --git a/modules/queue/queue_disk.go b/modules/queue/queue_disk.go index ff0876488b65..88b8c414c0b1 100644 --- a/modules/queue/queue_disk.go +++ b/modules/queue/queue_disk.go @@ -5,6 +5,8 @@ package queue import ( + "code.gitea.io/gitea/modules/nosql" + "gitea.com/lunny/levelqueue" ) @@ -14,7 +16,9 @@ const LevelQueueType Type = "level" // LevelQueueConfiguration is the configuration for a LevelQueue type LevelQueueConfiguration struct { ByteFIFOQueueConfiguration - DataDir string + DataDir string + ConnectionString string + QueueName string } // LevelQueue implements a disk library queue @@ -30,7 +34,11 @@ func NewLevelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) } config := configInterface.(LevelQueueConfiguration) - byteFIFO, err := NewLevelQueueByteFIFO(config.DataDir) + if len(config.ConnectionString) == 0 { + config.ConnectionString = config.DataDir + } + + byteFIFO, err := NewLevelQueueByteFIFO(config.ConnectionString, config.QueueName) if err != nil { return nil, err } @@ -51,18 +59,25 @@ var _ (ByteFIFO) = &LevelQueueByteFIFO{} // LevelQueueByteFIFO represents a ByteFIFO formed from a LevelQueue type LevelQueueByteFIFO struct { - internal *levelqueue.Queue + internal *levelqueue.Queue + connection string } // NewLevelQueueByteFIFO creates a ByteFIFO formed from a LevelQueue -func NewLevelQueueByteFIFO(dataDir string) (*LevelQueueByteFIFO, error) { - internal, err := levelqueue.Open(dataDir) +func NewLevelQueueByteFIFO(connection, prefix string) (*LevelQueueByteFIFO, error) { + db, err := nosql.GetManager().GetLevelDB(connection) + if err != nil { + return nil, err + } + + internal, err := levelqueue.NewQueue(db, []byte(prefix), false) if err != nil { return nil, err } return &LevelQueueByteFIFO{ - internal: internal, + connection: connection, + internal: internal, }, nil } @@ -87,7 +102,9 @@ func (fifo *LevelQueueByteFIFO) Pop() ([]byte, error) { // Close this fifo func (fifo *LevelQueueByteFIFO) Close() error { - return fifo.internal.Close() + err := fifo.internal.Close() + _ = nosql.GetManager().CloseLevelDB(fifo.connection) + return err } // Len returns the length of the fifo diff --git a/modules/queue/queue_disk_channel_test.go b/modules/queue/queue_disk_channel_test.go index 5049fb58d0a8..93061bffc658 100644 --- a/modules/queue/queue_disk_channel_test.go +++ b/modules/queue/queue_disk_channel_test.go @@ -52,7 +52,7 @@ func TestPersistableChannelQueue(t *testing.T) { err = queue.Push(&test1) assert.NoError(t, err) go func() { - err = queue.Push(&test2) + err := queue.Push(&test2) assert.NoError(t, err) }() diff --git a/modules/queue/queue_disk_test.go b/modules/queue/queue_disk_test.go index 789b6c3e6409..edaed49a5239 100644 --- a/modules/queue/queue_disk_test.go +++ b/modules/queue/queue_disk_test.go @@ -65,7 +65,7 @@ func TestLevelQueue(t *testing.T) { err = queue.Push(&test1) assert.NoError(t, err) go func() { - err = queue.Push(&test2) + err := queue.Push(&test2) assert.NoError(t, err) }() diff --git a/modules/queue/queue_redis.go b/modules/queue/queue_redis.go index 4e05ddd17e56..04e7b5d25284 100644 --- a/modules/queue/queue_redis.go +++ b/modules/queue/queue_redis.go @@ -5,12 +5,10 @@ package queue import ( - "errors" - "strings" - "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/nosql" - "github.com/go-redis/redis" + "github.com/go-redis/redis/v7" ) // RedisQueueType is the type for redis queue @@ -75,11 +73,8 @@ type RedisByteFIFO struct { // RedisByteFIFOConfiguration is the configuration for the RedisByteFIFO type RedisByteFIFOConfiguration struct { - Network string - Addresses string - Password string - DBIndex int - QueueName string + ConnectionString string + QueueName string } // NewRedisByteFIFO creates a ByteFIFO formed from a redisClient @@ -87,21 +82,7 @@ func NewRedisByteFIFO(config RedisByteFIFOConfiguration) (*RedisByteFIFO, error) fifo := &RedisByteFIFO{ queueName: config.QueueName, } - dbs := strings.Split(config.Addresses, ",") - if len(dbs) == 0 { - return nil, errors.New("no redis host specified") - } else if len(dbs) == 1 { - fifo.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 { - fifo.client = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: dbs, - }) - } + fifo.client = nosql.GetManager().GetRedisClient(config.ConnectionString) if err := fifo.client.Ping().Err(); err != nil { return nil, err } diff --git a/modules/queue/setting.go b/modules/queue/setting.go index 786a08a3b0f4..9ee1af8c7d8d 100644 --- a/modules/queue/setting.go +++ b/modules/queue/setting.go @@ -27,26 +27,10 @@ func validType(t string) (Type, error) { func getQueueSettings(name string) (setting.QueueSettings, []byte) { 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["SetName"] = q.SetName - opts["Workers"] = q.Workers - opts["MaxWorkers"] = q.MaxWorkers - opts["BlockTimeout"] = q.BlockTimeout - opts["BoostTimeout"] = q.BoostTimeout - opts["BoostWorkers"] = q.BoostWorkers - cfg, err := json.Marshal(opts) + cfg, err := json.Marshal(q) if err != nil { - log.Error("Unable to marshall generic options: %v Error: %v", opts, err) + log.Error("Unable to marshall generic options: %v Error: %v", q, err) log.Error("Unable to create queue for %s", name, err) return q, []byte{} } @@ -74,7 +58,7 @@ func CreateQueue(name string, handle HandlerFunc, exemplar interface{}) Queue { Timeout: q.Timeout, MaxAttempts: q.MaxAttempts, Config: cfg, - QueueLength: q.Length, + QueueLength: q.QueueLength, Name: name, }, exemplar) } @@ -113,7 +97,7 @@ func CreateUniqueQueue(name string, handle HandlerFunc, exemplar interface{}) Un Timeout: q.Timeout, MaxAttempts: q.MaxAttempts, Config: cfg, - QueueLength: q.Length, + QueueLength: q.QueueLength, }, exemplar) } if err != nil { diff --git a/modules/queue/unique_queue_disk.go b/modules/queue/unique_queue_disk.go index bfe7aeed8368..dd6ac1a53850 100644 --- a/modules/queue/unique_queue_disk.go +++ b/modules/queue/unique_queue_disk.go @@ -5,6 +5,8 @@ package queue import ( + "code.gitea.io/gitea/modules/nosql" + "gitea.com/lunny/levelqueue" ) @@ -14,7 +16,9 @@ const LevelUniqueQueueType Type = "unique-level" // LevelUniqueQueueConfiguration is the configuration for a LevelUniqueQueue type LevelUniqueQueueConfiguration struct { ByteFIFOQueueConfiguration - DataDir string + DataDir string + ConnectionString string + QueueName string } // LevelUniqueQueue implements a disk library queue @@ -34,7 +38,11 @@ func NewLevelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, } config := configInterface.(LevelUniqueQueueConfiguration) - byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.DataDir) + if len(config.ConnectionString) == 0 { + config.ConnectionString = config.DataDir + } + + byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.ConnectionString, config.QueueName) if err != nil { return nil, err } @@ -55,18 +63,25 @@ var _ (UniqueByteFIFO) = &LevelUniqueQueueByteFIFO{} // LevelUniqueQueueByteFIFO represents a ByteFIFO formed from a LevelUniqueQueue type LevelUniqueQueueByteFIFO struct { - internal *levelqueue.UniqueQueue + internal *levelqueue.UniqueQueue + connection string } // NewLevelUniqueQueueByteFIFO creates a new ByteFIFO formed from a LevelUniqueQueue -func NewLevelUniqueQueueByteFIFO(dataDir string) (*LevelUniqueQueueByteFIFO, error) { - internal, err := levelqueue.OpenUnique(dataDir) +func NewLevelUniqueQueueByteFIFO(connection, prefix string) (*LevelUniqueQueueByteFIFO, error) { + db, err := nosql.GetManager().GetLevelDB(connection) + if err != nil { + return nil, err + } + + internal, err := levelqueue.NewUniqueQueue(db, []byte(prefix), []byte(prefix+"-unique"), false) if err != nil { return nil, err } return &LevelUniqueQueueByteFIFO{ - internal: internal, + connection: connection, + internal: internal, }, nil } @@ -96,7 +111,9 @@ func (fifo *LevelUniqueQueueByteFIFO) Has(data []byte) (bool, error) { // Close this fifo func (fifo *LevelUniqueQueueByteFIFO) Close() error { - return fifo.internal.Close() + err := fifo.internal.Close() + _ = nosql.GetManager().CloseLevelDB(fifo.connection) + return err } func init() { diff --git a/modules/queue/unique_queue_redis.go b/modules/queue/unique_queue_redis.go index 940436907581..67efc66bc9d4 100644 --- a/modules/queue/unique_queue_redis.go +++ b/modules/queue/unique_queue_redis.go @@ -4,7 +4,7 @@ package queue -import "github.com/go-redis/redis" +import "github.com/go-redis/redis/v7" // RedisUniqueQueueType is the type for redis queue const RedisUniqueQueueType Type = "unique-redis" diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index a9718f2fdd11..54ea1dc0b3a3 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -5,12 +5,13 @@ package recaptcha import ( + "context" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" - "time" + "strings" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -18,18 +19,29 @@ import ( // Response is the structure of JSON returned from API type Response struct { - Success bool `json:"success"` - ChallengeTS time.Time `json:"challenge_ts"` - Hostname string `json:"hostname"` - ErrorCodes []string `json:"error-codes"` + Success bool `json:"success"` + ChallengeTS string `json:"challenge_ts"` + Hostname string `json:"hostname"` + ErrorCodes []ErrorCode `json:"error-codes"` } const apiURL = "api/siteverify" // Verify calls Google Recaptcha API to verify token -func Verify(response string) (bool, error) { - resp, err := http.PostForm(util.URLJoin(setting.Service.RecaptchaURL, apiURL), - url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}}) +func Verify(ctx context.Context, response string) (bool, error) { + post := url.Values{ + "secret": {setting.Service.RecaptchaSecret}, + "response": {response}, + } + // Basically a copy of http.PostForm, but with a context + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + util.URLJoin(setting.Service.RecaptchaURL, apiURL), strings.NewReader(post.Encode())) + if err != nil { + return false, fmt.Errorf("Failed to create CAPTCHA request: %v", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) if err != nil { return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) } @@ -43,6 +55,36 @@ func Verify(response string) (bool, error) { if err != nil { return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) } + var respErr error + if len(jsonResponse.ErrorCodes) > 0 { + respErr = jsonResponse.ErrorCodes[0] + } + return jsonResponse.Success, respErr +} + +// ErrorCode is a reCaptcha error +type ErrorCode string + +// String fulfills the Stringer interface +func (e ErrorCode) String() string { + switch e { + case "missing-input-secret": + return "The secret parameter is missing." + case "invalid-input-secret": + return "The secret parameter is invalid or malformed." + case "missing-input-response": + return "The response parameter is missing." + case "invalid-input-response": + return "The response parameter is invalid or malformed." + case "bad-request": + return "The request is invalid or malformed." + case "timeout-or-duplicate": + return "The response is no longer valid: either is too old or has been used previously." + } + return string(e) +} - return jsonResponse.Success, nil +// Error fulfills the error interface +func (e ErrorCode) Error() string { + return e.String() } diff --git a/modules/references/references.go b/modules/references/references.go index ce08dcc7ab8c..070c6e566a8e 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -37,6 +37,8 @@ var ( crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) // spaceTrimmedPattern let's us find the trailing space spaceTrimmedPattern = regexp.MustCompile(`(?:.*[0-9a-zA-Z-_])\s`) + // timeLogPattern matches string for time tracking + timeLogPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@([0-9]+([\.,][0-9]+)?(w|d|m|h))+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp issueKeywordsOnce sync.Once @@ -62,10 +64,11 @@ const ( // IssueReference contains an unverified cross-reference to a local issue or pull request type IssueReference struct { - Index int64 - Owner string - Name string - Action XRefAction + Index int64 + Owner string + Name string + Action XRefAction + TimeLog string } // RenderizableReference contains an unverified cross-reference to with rendering information @@ -91,16 +94,18 @@ type rawReference struct { issue string refLocation *RefSpan actionLocation *RefSpan + timeLog string } func rawToIssueReferenceList(reflist []*rawReference) []IssueReference { refarr := make([]IssueReference, len(reflist)) for i, r := range reflist { refarr[i] = IssueReference{ - Index: r.index, - Owner: r.owner, - Name: r.name, - Action: r.action, + Index: r.index, + Owner: r.owner, + Name: r.name, + Action: r.action, + TimeLog: r.timeLog, } } return refarr @@ -386,6 +391,38 @@ func findAllIssueReferencesBytes(content []byte, links []string) []*rawReference } } + if len(ret) == 0 { + return ret + } + + pos = 0 + + for { + match := timeLogPattern.FindSubmatchIndex(content[pos:]) + if match == nil { + break + } + + timeLogEntry := string(content[match[2]+pos+1 : match[3]+pos]) + + var f *rawReference + for _, ref := range ret { + if ref.refLocation != nil && ref.refLocation.End < match[2]+pos && (f == nil || f.refLocation.End < ref.refLocation.End) { + f = ref + } + } + + pos = match[1] + pos + + if f == nil { + f = ret[0] + } + + if len(f.timeLog) == 0 { + f.timeLog = timeLogEntry + } + } + return ret } diff --git a/modules/references/references_test.go b/modules/references/references_test.go index 48589c1637be..0c4037f1204a 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -26,6 +26,7 @@ type testResult struct { Action XRefAction RefLocation *RefSpan ActionLocation *RefSpan + TimeLog string } func TestFindAllIssueReferences(t *testing.T) { @@ -34,19 +35,19 @@ func TestFindAllIssueReferences(t *testing.T) { { "Simply closes: #29 yes", []testResult{ - {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}}, + {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}, ""}, }, }, { "Simply closes: !29 yes", []testResult{ - {29, "", "", "29", true, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}}, + {29, "", "", "29", true, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}, ""}, }, }, { " #124 yes, this is a reference.", []testResult{ - {124, "", "", "124", false, XRefActionNone, &RefSpan{Start: 0, End: 4}, nil}, + {124, "", "", "124", false, XRefActionNone, &RefSpan{Start: 0, End: 4}, nil, ""}, }, }, { @@ -60,13 +61,13 @@ func TestFindAllIssueReferences(t *testing.T) { { "This user3/repo4#200 yes.", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil}, + {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, }, }, { "This user3/repo4!200 yes.", []testResult{ - {200, "user3", "repo4", "200", true, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil}, + {200, "user3", "repo4", "200", true, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, }, }, { @@ -76,19 +77,19 @@ func TestFindAllIssueReferences(t *testing.T) { { "This [two](/user2/repo1/issues/921) yes.", []testResult{ - {921, "user2", "repo1", "921", false, XRefActionNone, nil, nil}, + {921, "user2", "repo1", "921", false, XRefActionNone, nil, nil, ""}, }, }, { "This [three](/user2/repo1/pulls/922) yes.", []testResult{ - {922, "user2", "repo1", "922", true, XRefActionNone, nil, nil}, + {922, "user2", "repo1", "922", true, XRefActionNone, nil, nil, ""}, }, }, { "This [four](http://gitea.com:3000/user3/repo4/issues/203) yes.", []testResult{ - {203, "user3", "repo4", "203", false, XRefActionNone, nil, nil}, + {203, "user3", "repo4", "203", false, XRefActionNone, nil, nil, ""}, }, }, { @@ -102,49 +103,49 @@ func TestFindAllIssueReferences(t *testing.T) { { "This http://gitea.com:3000/user4/repo5/pulls/202 yes.", []testResult{ - {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil}, + {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, }, }, { "This http://GiTeA.COM:3000/user4/repo6/pulls/205 yes.", []testResult{ - {205, "user4", "repo6", "205", true, XRefActionNone, nil, nil}, + {205, "user4", "repo6", "205", true, XRefActionNone, nil, nil, ""}, }, }, { "Reopens #15 yes", []testResult{ - {15, "", "", "15", false, XRefActionReopens, &RefSpan{Start: 8, End: 11}, &RefSpan{Start: 0, End: 7}}, + {15, "", "", "15", false, XRefActionReopens, &RefSpan{Start: 8, End: 11}, &RefSpan{Start: 0, End: 7}, ""}, }, }, { "This closes #20 for you yes", []testResult{ - {20, "", "", "20", false, XRefActionCloses, &RefSpan{Start: 12, End: 15}, &RefSpan{Start: 5, End: 11}}, + {20, "", "", "20", false, XRefActionCloses, &RefSpan{Start: 12, End: 15}, &RefSpan{Start: 5, End: 11}, ""}, }, }, { "Do you fix user6/repo6#300 ? yes", []testResult{ - {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 11, End: 26}, &RefSpan{Start: 7, End: 10}}, + {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 11, End: 26}, &RefSpan{Start: 7, End: 10}, ""}, }, }, { "For 999 #1235 no keyword, but yes", []testResult{ - {1235, "", "", "1235", false, XRefActionNone, &RefSpan{Start: 8, End: 13}, nil}, + {1235, "", "", "1235", false, XRefActionNone, &RefSpan{Start: 8, End: 13}, nil, ""}, }, }, { "For [!123] yes", []testResult{ - {123, "", "", "123", true, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil}, + {123, "", "", "123", true, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil, ""}, }, }, { "For (#345) yes", []testResult{ - {345, "", "", "345", false, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil}, + {345, "", "", "345", false, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil, ""}, }, }, { @@ -154,31 +155,39 @@ func TestFindAllIssueReferences(t *testing.T) { { "For #24, and #25. yes; also #26; #27? #28! and #29: should", []testResult{ - {24, "", "", "24", false, XRefActionNone, &RefSpan{Start: 4, End: 7}, nil}, - {25, "", "", "25", false, XRefActionNone, &RefSpan{Start: 13, End: 16}, nil}, - {26, "", "", "26", false, XRefActionNone, &RefSpan{Start: 28, End: 31}, nil}, - {27, "", "", "27", false, XRefActionNone, &RefSpan{Start: 33, End: 36}, nil}, - {28, "", "", "28", false, XRefActionNone, &RefSpan{Start: 38, End: 41}, nil}, - {29, "", "", "29", false, XRefActionNone, &RefSpan{Start: 47, End: 50}, nil}, + {24, "", "", "24", false, XRefActionNone, &RefSpan{Start: 4, End: 7}, nil, ""}, + {25, "", "", "25", false, XRefActionNone, &RefSpan{Start: 13, End: 16}, nil, ""}, + {26, "", "", "26", false, XRefActionNone, &RefSpan{Start: 28, End: 31}, nil, ""}, + {27, "", "", "27", false, XRefActionNone, &RefSpan{Start: 33, End: 36}, nil, ""}, + {28, "", "", "28", false, XRefActionNone, &RefSpan{Start: 38, End: 41}, nil, ""}, + {29, "", "", "29", false, XRefActionNone, &RefSpan{Start: 47, End: 50}, nil, ""}, }, }, { "This user3/repo4#200, yes.", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil}, + {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, }, }, { "Which abc. #9434 same as above", []testResult{ - {9434, "", "", "9434", false, XRefActionNone, &RefSpan{Start: 11, End: 16}, nil}, + {9434, "", "", "9434", false, XRefActionNone, &RefSpan{Start: 11, End: 16}, nil, ""}, }, }, { "This closes #600 and reopens #599", []testResult{ - {600, "", "", "600", false, XRefActionCloses, &RefSpan{Start: 12, End: 16}, &RefSpan{Start: 5, End: 11}}, - {599, "", "", "599", false, XRefActionReopens, &RefSpan{Start: 29, End: 33}, &RefSpan{Start: 21, End: 28}}, + {600, "", "", "600", false, XRefActionCloses, &RefSpan{Start: 12, End: 16}, &RefSpan{Start: 5, End: 11}, ""}, + {599, "", "", "599", false, XRefActionReopens, &RefSpan{Start: 29, End: 33}, &RefSpan{Start: 21, End: 28}, ""}, + }, + }, + { + "This fixes #100 spent @40m and reopens #101, also fixes #102 spent @4h15m", + []testResult{ + {100, "", "", "100", false, XRefActionCloses, &RefSpan{Start: 11, End: 15}, &RefSpan{Start: 5, End: 10}, "40m"}, + {101, "", "", "101", false, XRefActionReopens, &RefSpan{Start: 39, End: 43}, &RefSpan{Start: 31, End: 38}, ""}, + {102, "", "", "102", false, XRefActionCloses, &RefSpan{Start: 56, End: 60}, &RefSpan{Start: 50, End: 55}, "4h15m"}, }, }, } @@ -237,6 +246,7 @@ func testFixtures(t *testing.T, fixtures []testFixture, context string) { issue: e.Issue, refLocation: e.RefLocation, actionLocation: e.ActionLocation, + timeLog: e.TimeLog, } } expref := rawToIssueReferenceList(expraw) @@ -382,25 +392,25 @@ func TestCustomizeCloseKeywords(t *testing.T) { { "Simplemente cierra: #29 yes", []testResult{ - {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 20, End: 23}, &RefSpan{Start: 12, End: 18}}, + {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 20, End: 23}, &RefSpan{Start: 12, End: 18}, ""}, }, }, { "Closes: #123 no, this English.", []testResult{ - {123, "", "", "123", false, XRefActionNone, &RefSpan{Start: 8, End: 12}, nil}, + {123, "", "", "123", false, XRefActionNone, &RefSpan{Start: 8, End: 12}, nil, ""}, }, }, { "Cerró user6/repo6#300 yes", []testResult{ - {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}}, + {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, }, }, { "Reabre user3/repo4#200 yes", []testResult{ - {200, "user3", "repo4", "200", false, XRefActionReopens, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}}, + {200, "user3", "repo4", "200", false, XRefActionReopens, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, }, }, } diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go index 464249d19ba4..52cc89dbae1b 100644 --- a/modules/repofiles/action.go +++ b/modules/repofiles/action.go @@ -5,20 +5,29 @@ package repofiles import ( - "encoding/json" "fmt" "html" + "regexp" + "strconv" "strings" + "time" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/setting" ) +const ( + secondsByMinute = float64(time.Minute / time.Second) // seconds in a minute + secondsByHour = 60 * secondsByMinute // seconds in an hour + secondsByDay = 8 * secondsByHour // seconds in a day + secondsByWeek = 5 * secondsByDay // seconds in a week + secondsByMonth = 4 * secondsByWeek // seconds in a month +) + +var reDuration = regexp.MustCompile(`(?i)^(?:(\d+([\.,]\d+)?)(?:mo))?(?:(\d+([\.,]\d+)?)(?:w))?(?:(\d+([\.,]\d+)?)(?:d))?(?:(\d+([\.,]\d+)?)(?:h))?(?:(\d+([\.,]\d+)?)(?:m))?$`) + // getIssueFromRef returns the issue referenced by a ref. Returns a nil *Issue // if the provided ref references a non-existent issue. func getIssueFromRef(repo *models.Repository, index int64) (*models.Issue, error) { @@ -32,6 +41,60 @@ func getIssueFromRef(repo *models.Repository, index int64) (*models.Issue, error return issue, nil } +// timeLogToAmount parses time log string and returns amount in seconds +func timeLogToAmount(str string) int64 { + matches := reDuration.FindAllStringSubmatch(str, -1) + if len(matches) == 0 { + return 0 + } + + match := matches[0] + + var a int64 + + // months + if len(match[1]) > 0 { + mo, _ := strconv.ParseFloat(strings.Replace(match[1], ",", ".", 1), 64) + a += int64(mo * secondsByMonth) + } + + // weeks + if len(match[3]) > 0 { + w, _ := strconv.ParseFloat(strings.Replace(match[3], ",", ".", 1), 64) + a += int64(w * secondsByWeek) + } + + // days + if len(match[5]) > 0 { + d, _ := strconv.ParseFloat(strings.Replace(match[5], ",", ".", 1), 64) + a += int64(d * secondsByDay) + } + + // hours + if len(match[7]) > 0 { + h, _ := strconv.ParseFloat(strings.Replace(match[7], ",", ".", 1), 64) + a += int64(h * secondsByHour) + } + + // minutes + if len(match[9]) > 0 { + d, _ := strconv.ParseFloat(strings.Replace(match[9], ",", ".", 1), 64) + a += int64(d * secondsByMinute) + } + + return a +} + +func issueAddTime(issue *models.Issue, doer *models.User, time time.Time, timeLog string) error { + amount := timeLogToAmount(timeLog) + if amount == 0 { + return nil + } + + _, err := models.AddTime(doer, issue, amount, time) + return err +} + func changeIssueStatus(repo *models.Repository, issue *models.Issue, doer *models.User, closed bool) error { stopTimerIfAvailable := func(doer *models.User, issue *models.Issue) error { @@ -139,149 +202,17 @@ func UpdateIssuesCommit(doer *models.User, repo *models.Repository, commits []*r } } close := (ref.Action == references.XRefActionCloses) - if close != refIssue.IsClosed { - if err := changeIssueStatus(refRepo, refIssue, doer, close); err != nil { + if close && len(ref.TimeLog) > 0 { + if err := issueAddTime(refIssue, doer, c.Timestamp, ref.TimeLog); err != nil { return err } } - } - } - return nil -} - -// CommitRepoActionOptions represent options of a new commit action. -type CommitRepoActionOptions struct { - PushUpdateOptions - - RepoOwnerID int64 - Commits *repository.PushCommits -} - -// CommitRepoAction adds new commit action to the repository, and prepare -// corresponding webhooks. -func CommitRepoAction(optsList ...*CommitRepoActionOptions) error { - var pusher *models.User - var repo *models.Repository - actions := make([]*models.Action, len(optsList)) - - for i, opts := range optsList { - if pusher == nil || pusher.Name != opts.PusherName { - var err error - pusher, err = models.GetUserByName(opts.PusherName) - if err != nil { - return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) - } - } - - if repo == nil || repo.OwnerID != opts.RepoOwnerID || repo.Name != opts.RepoName { - var err error - if repo != nil { - // Change repository empty status and update last updated time. - if err := models.UpdateRepository(repo, false); err != nil { - return fmt.Errorf("UpdateRepository: %v", err) - } - } - repo, err = models.GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) - } - } - refName := git.RefEndName(opts.RefFullName) - - // Change default branch and empty status only if pushed ref is non-empty branch. - if repo.IsEmpty && opts.IsBranch() && !opts.IsDelRef() { - repo.DefaultBranch = refName - repo.IsEmpty = false - if refName != "master" { - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { + if close != refIssue.IsClosed { + if err := changeIssueStatus(refRepo, refIssue, doer, close); err != nil { return err } - if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { - if !git.IsErrUnsupportedVersion(err) { - gitRepo.Close() - return err - } - } - gitRepo.Close() - } - } - - opType := models.ActionCommitRepo - - // Check it's tag push or branch. - if opts.IsTag() { - opType = models.ActionPushTag - if opts.IsDelRef() { - opType = models.ActionDeleteTag - } - opts.Commits = &repository.PushCommits{} - } else if opts.IsDelRef() { - opType = models.ActionDeleteBranch - opts.Commits = &repository.PushCommits{} - } else { - // if not the first commit, set the compare URL. - if !opts.IsNewRef() { - opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) - } - - if err := UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { - log.Error("updateIssuesCommit: %v", err) } } - - if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { - opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] - } - - data, err := json.Marshal(opts.Commits) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - - actions[i] = &models.Action{ - ActUserID: pusher.ID, - ActUser: pusher, - OpType: opType, - Content: string(data), - RepoID: repo.ID, - Repo: repo, - RefName: refName, - IsPrivate: repo.IsPrivate, - } - - var isHookEventPush = true - switch opType { - case models.ActionCommitRepo: // Push - if opts.IsNewBranch() { - notification.NotifyCreateRef(pusher, repo, "branch", opts.RefFullName) - } - case models.ActionDeleteBranch: // Delete Branch - notification.NotifyDeleteRef(pusher, repo, "branch", opts.RefFullName) - - case models.ActionPushTag: // Create - notification.NotifyCreateRef(pusher, repo, "tag", opts.RefFullName) - - case models.ActionDeleteTag: // Delete Tag - notification.NotifyDeleteRef(pusher, repo, "tag", opts.RefFullName) - default: - isHookEventPush = false - } - - if isHookEventPush { - notification.NotifyPushCommits(pusher, repo, opts.RefFullName, opts.OldCommitID, opts.NewCommitID, opts.Commits) - } - } - - if repo != nil { - // Change repository empty status and update last updated time. - if err := models.UpdateRepository(repo, false); err != nil { - return fmt.Errorf("UpdateRepository: %v", err) - } - } - - if err := models.NotifyWatchers(actions...); err != nil { - return fmt.Errorf("NotifyWatchers: %v", err) } return nil } diff --git a/modules/repofiles/action_test.go b/modules/repofiles/action_test.go index 8ed3ba7b3c7e..290844de0267 100644 --- a/modules/repofiles/action_test.go +++ b/modules/repofiles/action_test.go @@ -8,133 +8,12 @@ import ( "testing" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) -func testCorrectRepoAction(t *testing.T, opts *CommitRepoActionOptions, actionBean *models.Action) { - models.AssertNotExistsBean(t, actionBean) - assert.NoError(t, CommitRepoAction(opts)) - models.AssertExistsAndLoadBean(t, actionBean) - models.CheckConsistencyFor(t, &models.Action{}) -} - -func TestCommitRepoAction(t *testing.T) { - samples := []struct { - userID int64 - repositoryID int64 - commitRepoActionOptions CommitRepoActionOptions - action models.Action - }{ - { - userID: 2, - repositoryID: 16, - commitRepoActionOptions: CommitRepoActionOptions{ - PushUpdateOptions: PushUpdateOptions{ - RefFullName: "refName", - OldCommitID: "oldCommitID", - NewCommitID: "newCommitID", - }, - Commits: &repository.PushCommits{ - Commits: []*repository.PushCommit{ - { - Sha1: "69554a6", - CommitterEmail: "user2@example.com", - CommitterName: "User2", - AuthorEmail: "user2@example.com", - AuthorName: "User2", - Message: "not signed commit", - }, - { - Sha1: "27566bd", - CommitterEmail: "user2@example.com", - CommitterName: "User2", - AuthorEmail: "user2@example.com", - AuthorName: "User2", - Message: "good signed commit (with not yet validated email)", - }, - }, - Len: 2, - }, - }, - action: models.Action{ - OpType: models.ActionCommitRepo, - RefName: "refName", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - PushUpdateOptions: PushUpdateOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: git.EmptySHA, - NewCommitID: "newCommitID", - }, - Commits: &repository.PushCommits{}, - }, - action: models.Action{ - OpType: models.ActionPushTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - PushUpdateOptions: PushUpdateOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - }, - Commits: &repository.PushCommits{}, - }, - action: models.Action{ - OpType: models.ActionDeleteTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - PushUpdateOptions: PushUpdateOptions{ - RefFullName: git.BranchPrefix + "feature/1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - }, - Commits: &repository.PushCommits{}, - }, - action: models.Action{ - OpType: models.ActionDeleteBranch, - RefName: "feature/1", - }, - }, - } - - for _, s := range samples { - models.PrepareTestEnv(t) - - user := models.AssertExistsAndLoadBean(t, &models.User{ID: s.userID}).(*models.User) - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: s.repositoryID, OwnerID: user.ID}).(*models.Repository) - repo.Owner = user - - s.commitRepoActionOptions.PusherName = user.Name - s.commitRepoActionOptions.RepoOwnerID = user.ID - s.commitRepoActionOptions.RepoName = repo.Name - - s.action.ActUserID = user.ID - s.action.RepoID = repo.ID - s.action.Repo = repo - s.action.IsPrivate = repo.IsPrivate - - testCorrectRepoAction(t, &s.commitRepoActionOptions, &s.action) - } -} - func TestUpdateIssuesCommit(t *testing.T) { assert.NoError(t, models.PrepareTestDatabase()) pushCommits := []*repository.PushCommit{ diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go index 2ffc75e7c8e1..8343776c4716 100644 --- a/modules/repofiles/delete.go +++ b/modules/repofiles/delete.go @@ -67,7 +67,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo } } if protectedBranch.RequireSignedCommits { - _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) + _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) if err != nil { if !models.IsErrWontSign(err) { return nil, err diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go index 2b03db8b4a31..0d867736432e 100644 --- a/modules/repofiles/temp_repo.go +++ b/modules/repofiles/temp_repo.go @@ -19,8 +19,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/gitdiff" - - "github.com/mcuadros/go-version" ) // TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone @@ -196,7 +194,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models authorSig := author.NewGitSig() committerSig := committer.NewGitSig() - binVersion, err := git.BinVersion() + err := git.LoadGitVersion() if err != nil { return "", fmt.Errorf("Unable to get git version: %v", err) } @@ -206,8 +204,6 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models "GIT_AUTHOR_NAME="+authorSig.Name, "GIT_AUTHOR_EMAIL="+authorSig.Email, "GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339), - "GIT_COMMITTER_NAME="+committerSig.Name, - "GIT_COMMITTER_EMAIL="+committerSig.Email, "GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339), ) @@ -218,15 +214,33 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models args := []string{"commit-tree", treeHash, "-p", "HEAD"} // Determine if we should sign - if version.Compare(binVersion, "1.7.9", ">=") { - sign, keyID, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD") + if git.CheckGitVersionAtLeast("1.7.9") == nil { + sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD") if sign { args = append(args, "-S"+keyID) - } else if version.Compare(binVersion, "2.0.0", ">=") { + if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel { + if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { + // Add trailers + _, _ = messageBytes.WriteString("\n") + _, _ = messageBytes.WriteString("Co-Authored-By: ") + _, _ = messageBytes.WriteString(committerSig.String()) + _, _ = messageBytes.WriteString("\n") + _, _ = messageBytes.WriteString("Co-Committed-By: ") + _, _ = messageBytes.WriteString(committerSig.String()) + _, _ = messageBytes.WriteString("\n") + } + committerSig = signer + } + } else if git.CheckGitVersionAtLeast("2.0.0") == nil { args = append(args, "--no-gpg-sign") } } + env = append(env, + "GIT_COMMITTER_NAME="+committerSig.Name, + "GIT_COMMITTER_EMAIL="+committerSig.Email, + ) + stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil { @@ -278,7 +292,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) { var diff *gitdiff.Diff var finalErr error - if err := git.NewCommand("diff-index", "--cached", "-p", "HEAD"). + if err := git.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD"). RunInDirTimeoutEnvFullPipelineFunc(nil, 30*time.Second, t.basePath, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() diff, finalErr = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader) @@ -309,7 +323,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) { // CheckAttribute checks the given attribute of the provided files func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...string) (map[string]map[string]string, error) { - binVersion, err := git.BinVersion() + err := git.LoadGitVersion() if err != nil { log.Error("Error retrieving git version: %v", err) return nil, err @@ -321,7 +335,7 @@ func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...str cmdArgs := []string{"check-attr", "-z", attribute} // git check-attr --cached first appears in git 1.7.8 - if version.Compare(binVersion, "1.7.8", ">=") { + if git.CheckGitVersionAtLeast("1.7.8") == nil { cmdArgs = append(cmdArgs, "--cached") } cmdArgs = append(cmdArgs, "--") diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index d65f61c8409e..a1a9c624d7ff 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -6,22 +6,20 @@ package repofiles import ( "bytes" - "container/list" "fmt" "path" "strings" "time" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/structs" - pull_service "code.gitea.io/gitea/services/pull" stdcharset "golang.org/x/net/html/charset" "golang.org/x/text/transform" @@ -125,7 +123,7 @@ func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string // CreateOrUpdateRepoFile adds or updates a file in the given repository func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *UpdateRepoFileOptions) (*structs.FileResponse, error) { - // If no branch name is set, assume master + // If no branch name is set, assume default branch if opts.OldBranch == "" { opts.OldBranch = repo.DefaultBranch } @@ -163,7 +161,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up } } if protectedBranch.RequireSignedCommits { - _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) + _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch) if err != nil { if !models.IsErrWontSign(err) { return nil, err @@ -433,8 +431,12 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up if err != nil { return nil, err } - contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath} - if !contentStore.Exists(lfsMetaObject) { + contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS} + exist, err := contentStore.Exists(lfsMetaObject) + if err != nil { + return nil, err + } + if !exist { if err := contentStore.Put(lfsMetaObject, strings.NewReader(opts.Content)); err != nil { if _, err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil { return nil, fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err) @@ -461,298 +463,3 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up } return file, nil } - -// PushUpdateOptions defines the push update options -type PushUpdateOptions struct { - PusherID int64 - PusherName string - RepoUserName string - RepoName string - RefFullName string - OldCommitID string - NewCommitID string -} - -// IsNewRef return true if it's a first-time push to a branch, tag or etc. -func (opts PushUpdateOptions) IsNewRef() bool { - return opts.OldCommitID == git.EmptySHA -} - -// IsDelRef return true if it's a deletion to a branch or tag -func (opts PushUpdateOptions) IsDelRef() bool { - return opts.NewCommitID == git.EmptySHA -} - -// IsUpdateRef return true if it's an update operation -func (opts PushUpdateOptions) IsUpdateRef() bool { - return !opts.IsNewRef() && !opts.IsDelRef() -} - -// IsTag return true if it's an operation to a tag -func (opts PushUpdateOptions) IsTag() bool { - return strings.HasPrefix(opts.RefFullName, git.TagPrefix) -} - -// IsNewTag return true if it's a creation to a tag -func (opts PushUpdateOptions) IsNewTag() bool { - return opts.IsTag() && opts.IsNewRef() -} - -// IsDelTag return true if it's a deletion to a tag -func (opts PushUpdateOptions) IsDelTag() bool { - return opts.IsTag() && opts.IsDelRef() -} - -// IsBranch return true if it's a push to branch -func (opts PushUpdateOptions) IsBranch() bool { - return strings.HasPrefix(opts.RefFullName, git.BranchPrefix) -} - -// IsNewBranch return true if it's the first-time push to a branch -func (opts PushUpdateOptions) IsNewBranch() bool { - return opts.IsBranch() && opts.IsNewRef() -} - -// IsUpdateBranch return true if it's not the first push to a branch -func (opts PushUpdateOptions) IsUpdateBranch() bool { - return opts.IsBranch() && opts.IsUpdateRef() -} - -// IsDelBranch return true if it's a deletion to a branch -func (opts PushUpdateOptions) IsDelBranch() bool { - return opts.IsBranch() && opts.IsDelRef() -} - -// TagName returns simple tag name if it's an operation to a tag -func (opts PushUpdateOptions) TagName() string { - return opts.RefFullName[len(git.TagPrefix):] -} - -// BranchName returns simple branch name if it's an operation to branch -func (opts PushUpdateOptions) BranchName() string { - return opts.RefFullName[len(git.BranchPrefix):] -} - -// RepoFullName returns repo full name -func (opts PushUpdateOptions) RepoFullName() string { - return opts.RepoUserName + "/" + opts.RepoName -} - -// PushUpdate must be called for any push actions in order to -// generates necessary push action history feeds and other operations -func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions) error { - if opts.IsNewRef() && opts.IsDelRef() { - return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) - } - - repoPath := models.RepoPath(opts.RepoUserName, opts.RepoName) - - _, err := git.NewCommand("update-server-info").RunInDir(repoPath) - if err != nil { - return fmt.Errorf("Failed to call 'git update-server-info': %v", err) - } - - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - defer gitRepo.Close() - - if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - var commits = &repo_module.PushCommits{} - - if opts.IsTag() { // If is tag reference - tagName := opts.TagName() - if opts.IsDelRef() { - if err := models.PushUpdateDeleteTag(repo, tagName); err != nil { - return fmt.Errorf("PushUpdateDeleteTag: %v", err) - } - } else { - // Clear cache for tag commit count - cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) - if err := repo_module.PushUpdateAddTag(repo, gitRepo, tagName); err != nil { - return fmt.Errorf("PushUpdateAddTag: %v", err) - } - } - } else if opts.IsBranch() { // If is branch reference - pusher, err := models.GetUserByID(opts.PusherID) - if err != nil { - return err - } - - if !opts.IsDelRef() { - // Clear cache for branch commit count - cache.Remove(repo.GetCommitsCountCacheKey(opts.BranchName(), true)) - - newCommit, err := gitRepo.GetCommit(opts.NewCommitID) - if err != nil { - return fmt.Errorf("gitRepo.GetCommit: %v", err) - } - - // Push new branch. - var l *list.List - if opts.IsNewRef() { - l, err = newCommit.CommitsBeforeLimit(10) - if err != nil { - return fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) - } - } else { - l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) - if err != nil { - return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) - } - } - - commits = repo_module.ListToPushCommits(l) - - if err = models.RemoveDeletedBranch(repo.ID, opts.BranchName()); err != nil { - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, opts.BranchName(), err) - } - - if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { - log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) - } - - log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) - - go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID) - } else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil { - // close all related pulls - log.Error("close related pull request failed: %v", err) - } - } - - if err := CommitRepoAction(&CommitRepoActionOptions{ - PushUpdateOptions: opts, - RepoOwnerID: repo.OwnerID, - Commits: commits, - }); err != nil { - return fmt.Errorf("CommitRepoAction: %v", err) - } - - return nil -} - -// PushUpdates generates push action history feeds for push updating multiple refs -func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error { - repoPath := repo.RepoPath() - _, err := git.NewCommand("update-server-info").RunInDir(repoPath) - if err != nil { - return fmt.Errorf("Failed to call 'git update-server-info': %v", err) - } - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - defer gitRepo.Close() - - if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - actions, err := createCommitRepoActions(repo, gitRepo, optsList) - if err != nil { - return err - } - if err := CommitRepoAction(actions...); err != nil { - return fmt.Errorf("CommitRepoAction: %v", err) - } - - var pusher *models.User - - for _, opts := range optsList { - if !opts.IsBranch() { - continue - } - - branch := opts.BranchName() - - if pusher == nil || pusher.ID != opts.PusherID { - var err error - pusher, err = models.GetUserByID(opts.PusherID) - if err != nil { - return err - } - } - - if !opts.IsDelRef() { - if err = models.RemoveDeletedBranch(repo.ID, branch); err != nil { - log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) - } - - if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { - log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) - } - - log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) - - go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID) - // close all related pulls - } else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil { - log.Error("close related pull request failed: %v", err) - } - } - - return nil -} - -func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, optsList []*PushUpdateOptions) ([]*CommitRepoActionOptions, error) { - addTags := make([]string, 0, len(optsList)) - delTags := make([]string, 0, len(optsList)) - actions := make([]*CommitRepoActionOptions, 0, len(optsList)) - - for _, opts := range optsList { - if opts.IsNewRef() && opts.IsDelRef() { - return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) - } - var commits = &repo_module.PushCommits{} - if opts.IsTag() { - // If is tag reference - tagName := opts.TagName() - if opts.IsDelRef() { - delTags = append(delTags, tagName) - } else { - cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) - addTags = append(addTags, tagName) - } - } else if !opts.IsDelRef() { - // If is branch reference - - // Clear cache for branch commit count - cache.Remove(repo.GetCommitsCountCacheKey(opts.BranchName(), true)) - - newCommit, err := gitRepo.GetCommit(opts.NewCommitID) - if err != nil { - return nil, fmt.Errorf("gitRepo.GetCommit: %v", err) - } - - // Push new branch. - var l *list.List - if opts.IsNewRef() { - l, err = newCommit.CommitsBeforeLimit(10) - if err != nil { - return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) - } - } else { - l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) - if err != nil { - return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) - } - } - - commits = repo_module.ListToPushCommits(l) - } - actions = append(actions, &CommitRepoActionOptions{ - PushUpdateOptions: *opts, - RepoOwnerID: repo.OwnerID, - Commits: commits, - }) - } - if err := repo_module.PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil { - return nil, fmt.Errorf("PushUpdateAddDeleteTags: %v", err) - } - return actions, nil -} diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go index eb1379560dff..e3ec48ec0f9e 100644 --- a/modules/repofiles/upload.go +++ b/modules/repofiles/upload.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" ) // UploadRepoFileOptions contains the uploaded repository file options @@ -163,12 +164,16 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep // OK now we can insert the data into the store - there's no way to clean up the store // once it's in there, it's in there. - contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS} for _, uploadInfo := range infos { if uploadInfo.lfsMetaObject == nil { continue } - if !contentStore.Exists(uploadInfo.lfsMetaObject) { + exist, err := contentStore.Exists(uploadInfo.lfsMetaObject) + if err != nil { + return cleanUpAfterFailure(&infos, t, err) + } + if !exist { file, err := os.Open(uploadInfo.upload.LocalPath()) if err != nil { return cleanUpAfterFailure(&infos, t, err) diff --git a/modules/repository/adopt.go b/modules/repository/adopt.go new file mode 100644 index 000000000000..22cd6dd91f7f --- /dev/null +++ b/modules/repository/adopt.go @@ -0,0 +1,272 @@ +// 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 repository + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "github.com/gobwas/glob" + "github.com/unknwon/com" +) + +// AdoptRepository adopts a repository for the user/organization. +func AdoptRepository(doer, u *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { + if !doer.IsAdmin && !u.CanCreateRepo() { + return nil, models.ErrReachLimitOfRepo{ + Limit: u.MaxRepoCreation, + } + } + + if len(opts.DefaultBranch) == 0 { + opts.DefaultBranch = setting.Repository.DefaultBranch + } + + repo := &models.Repository{ + OwnerID: u.ID, + Owner: u, + OwnerName: u.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + OriginalURL: opts.OriginalURL, + OriginalServiceType: opts.GitServiceType, + IsPrivate: opts.IsPrivate, + IsFsckEnabled: !opts.IsMirror, + CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, + Status: opts.Status, + IsEmpty: !opts.AutoInit, + } + + if err := models.WithTx(func(ctx models.DBContext) error { + repoPath := models.RepoPath(u.Name, repo.Name) + if !com.IsExist(repoPath) { + return models.ErrRepoNotExist{ + OwnerName: u.Name, + Name: repo.Name, + } + } + + if err := models.CreateRepository(ctx, doer, u, repo, true); err != nil { + return err + } + if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + + // Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { + return fmt.Errorf("InitializeLabels: %v", err) + } + } + + if stdout, err := git.NewCommand("update-server-info"). + SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). + RunInDir(repoPath); err != nil { + log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("CreateRepository(git update-server-info): %v", err) + } + return nil + }); err != nil { + return nil, err + } + + return repo, nil +} + +// DeleteUnadoptedRepository deletes unadopted repository files from the filesystem +func DeleteUnadoptedRepository(doer, u *models.User, repoName string) error { + if err := models.IsUsableRepoName(repoName); err != nil { + return err + } + + repoPath := models.RepoPath(u.Name, repoName) + if !com.IsExist(repoPath) { + return models.ErrRepoNotExist{ + OwnerName: u.Name, + Name: repoName, + } + } + + if exist, err := models.IsRepositoryExist(u, repoName); err != nil { + return err + } else if exist { + return models.ErrRepoAlreadyExist{ + Uname: u.Name, + Name: repoName, + } + } + + return util.RemoveAll(repoPath) +} + +// ListUnadoptedRepositories lists all the unadopted repositories that match the provided query +func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string, int, error) { + globUser, _ := glob.Compile("*") + globRepo, _ := glob.Compile("*") + + qsplit := strings.SplitN(query, "/", 2) + if len(qsplit) > 0 && len(query) > 0 { + var err error + globUser, err = glob.Compile(qsplit[0]) + if err != nil { + log.Info("Invalid glob expresion '%s' (skipped): %v", qsplit[0], err) + } + if len(qsplit) > 1 { + globRepo, err = glob.Compile(qsplit[1]) + if err != nil { + log.Info("Invalid glob expresion '%s' (skipped): %v", qsplit[1], err) + } + } + } + start := (opts.Page - 1) * opts.PageSize + end := start + opts.PageSize + + repoNamesToCheck := make([]string, 0, opts.PageSize) + + repoNames := make([]string, 0, opts.PageSize) + var ctxUser *models.User + + count := 0 + + // We're going to iterate by pagesize. + root := filepath.Join(setting.RepoRootPath) + if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() || path == root { + return nil + } + + if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) { + // Got a new user + + // Clean up old repoNamesToCheck + if len(repoNamesToCheck) > 0 { + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) + if err != nil { + return err + } + for _, name := range repoNamesToCheck { + found := false + repoLoopCatchup: + for i, repo := range repos { + if repo.LowerName == name { + found = true + repos = append(repos[:i], repos[i+1:]...) + break repoLoopCatchup + } + } + if !found { + if count >= start && count < end { + repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name)) + } + count++ + } + } + repoNamesToCheck = repoNamesToCheck[:0] + } + + if !globUser.Match(info.Name()) { + return filepath.SkipDir + } + + ctxUser, err = models.GetUserByName(info.Name()) + if err != nil { + if models.IsErrUserNotExist(err) { + log.Debug("Missing user: %s", info.Name()) + return filepath.SkipDir + } + return err + } + return nil + } + + name := info.Name() + + if !strings.HasSuffix(name, ".git") { + return filepath.SkipDir + } + name = name[:len(name)-4] + if models.IsUsableRepoName(name) != nil || strings.ToLower(name) != name || !globRepo.Match(name) { + return filepath.SkipDir + } + if count < end { + repoNamesToCheck = append(repoNamesToCheck, name) + if len(repoNamesToCheck) >= opts.PageSize { + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) + if err != nil { + return err + } + for _, name := range repoNamesToCheck { + found := false + repoLoop: + for i, repo := range repos { + if repo.Name == name { + found = true + repos = append(repos[:i], repos[i+1:]...) + break repoLoop + } + } + if !found { + if count >= start && count < end { + repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name)) + } + count++ + } + } + repoNamesToCheck = repoNamesToCheck[:0] + } + return filepath.SkipDir + } + count++ + return filepath.SkipDir + }); err != nil { + return nil, 0, err + } + + if len(repoNamesToCheck) > 0 { + repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: models.ListOptions{ + Page: 1, + PageSize: opts.PageSize, + }, LowerNames: repoNamesToCheck}) + if err != nil { + return nil, 0, err + } + for _, name := range repoNamesToCheck { + found := false + repoLoop: + for i, repo := range repos { + if repo.LowerName == name { + found = true + repos = append(repos[:i], repos[i+1:]...) + break repoLoop + } + } + if !found { + if count >= start && count < end { + repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name)) + } + count++ + } + } + } + return repoNames, count, nil +} diff --git a/modules/repository/cache.go b/modules/repository/cache.go new file mode 100644 index 000000000000..508e5bec0b76 --- /dev/null +++ b/modules/repository/cache.go @@ -0,0 +1,97 @@ +// 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 repository + +import ( + "path" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + + cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" +) + +func recusiveCache(gitRepo *git.Repository, c cgobject.CommitNode, tree *git.Tree, treePath string, ca *cache.LastCommitCache, level int) error { + if level == 0 { + return nil + } + + entries, err := tree.ListEntries() + if err != nil { + return err + } + + entryPaths := make([]string, len(entries)) + entryMap := make(map[string]*git.TreeEntry) + for i, entry := range entries { + entryPaths[i] = entry.Name() + entryMap[entry.Name()] = entry + } + + commits, err := git.GetLastCommitForPaths(c, treePath, entryPaths) + if err != nil { + return err + } + + for entry, cm := range commits { + if err := ca.Put(c.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil { + return err + } + if entryMap[entry].IsDir() { + subTree, err := tree.SubTree(entry) + if err != nil { + return err + } + if err := recusiveCache(gitRepo, c, subTree, entry, ca, level-1); err != nil { + return err + } + } + } + + return nil +} + +func getRefName(fullRefName string) string { + if strings.HasPrefix(fullRefName, git.TagPrefix) { + return fullRefName[len(git.TagPrefix):] + } else if strings.HasPrefix(fullRefName, git.BranchPrefix) { + return fullRefName[len(git.BranchPrefix):] + } + return "" +} + +// CacheRef cachhe last commit information of the branch or the tag +func CacheRef(repo *models.Repository, gitRepo *git.Repository, fullRefName string) error { + if !setting.CacheService.LastCommit.Enabled { + return nil + } + + commit, err := gitRepo.GetCommit(fullRefName) + if err != nil { + return err + } + + commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(getRefName(fullRefName), true), commit.CommitsCount) + if err != nil { + return err + } + if commitsCount < setting.CacheService.LastCommit.CommitsCount { + return nil + } + + commitNodeIndex, _ := gitRepo.CommitNodeIndex() + + c, err := commitNodeIndex.Get(commit.ID) + if err != nil { + return err + } + + ca := cache.NewLastCommitCache(repo.FullName(), gitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds())) + + return recusiveCache(gitRepo, c, &commit.Tree, "", ca, 1) +} diff --git a/modules/repository/check.go b/modules/repository/check.go index 90186d6a292b..274576c34887 100644 --- a/modules/repository/check.go +++ b/modules/repository/check.go @@ -120,7 +120,7 @@ func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) return nil }, ); err != nil { - if strings.HasPrefix("Aborted gathering missing repo", err.Error()) { + if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") { return nil, err } if err2 := models.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil { diff --git a/modules/repository/create.go b/modules/repository/create.go index 945f4a8cea6c..1408637815d3 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -13,16 +13,22 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + + "github.com/unknwon/com" ) // CreateRepository creates a repository for the user/organization. -func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *models.Repository, err error) { +func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { if !doer.IsAdmin && !u.CanCreateRepo() { return nil, models.ErrReachLimitOfRepo{ Limit: u.MaxRepoCreation, } } + if len(opts.DefaultBranch) == 0 { + opts.DefaultBranch = setting.Repository.DefaultBranch + } + repo := &models.Repository{ OwnerID: u.ID, Owner: u, @@ -34,44 +40,71 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m OriginalServiceType: opts.GitServiceType, IsPrivate: opts.IsPrivate, IsFsckEnabled: !opts.IsMirror, + IsTemplate: opts.IsTemplate, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, Status: opts.Status, IsEmpty: !opts.AutoInit, + TrustModel: opts.TrustModel, } - err = models.WithTx(func(ctx models.DBContext) error { - if err = models.CreateRepository(ctx, doer, u, repo); err != nil { + if err := models.WithTx(func(ctx models.DBContext) error { + if err := models.CreateRepository(ctx, doer, u, repo, false); err != nil { return err } // No need for init mirror. - if !opts.IsMirror { - repoPath := models.RepoPath(u.Name, repo.Name) - if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil { - if err2 := util.RemoveAll(repoPath); err2 != nil { - log.Error("initRepository: %v", err) - return fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return fmt.Errorf("initRepository: %v", err) + if opts.IsMirror { + return nil + } + + repoPath := models.RepoPath(u.Name, repo.Name) + if com.IsExist(repoPath) { + // repo already exists - We have two or three options. + // 1. We fail stating that the directory exists + // 2. We create the db repository to go with this data and adopt the git repo + // 3. We delete it and start afresh + // + // Previously Gitea would just delete and start afresh - this was naughty. + // So we will now fail and delegate to other functionality to adopt or delete + log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) + return models.ErrRepoFilesAlreadyExist{ + Uname: u.Name, + Name: repo.Name, } + } + + if err := initRepository(ctx, repoPath, doer, repo, opts); err != nil { + if err2 := util.RemoveAll(repoPath); err2 != nil { + log.Error("initRepository: %v", err) + return fmt.Errorf( + "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + } + return fmt.Errorf("initRepository: %v", err) + } - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err = models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { - return fmt.Errorf("InitializeLabels: %v", err) + // Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { + if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { + log.Error("Rollback deleteRepository: %v", errDelete) } + return fmt.Errorf("InitializeLabels: %v", err) } + } - if stdout, err := git.NewCommand("update-server-info"). - SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("CreateRepository(git update-server-info): %v", err) + if stdout, err := git.NewCommand("update-server-info"). + SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). + RunInDir(repoPath); err != nil { + log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { + log.Error("Rollback deleteRepository: %v", errDelete) } + return fmt.Errorf("CreateRepository(git update-server-info): %v", err) } return nil - }) + }); err != nil { + return nil, err + } - return repo, err + return repo, nil } diff --git a/modules/repository/fork.go b/modules/repository/fork.go index 169c391eddda..cdd08e3d3c8e 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -46,11 +46,21 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, oldRepoPath := oldRepo.RepoPath() err = models.WithTx(func(ctx models.DBContext) error { - if err = models.CreateRepository(ctx, doer, owner, repo); err != nil { + if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil { return err } + rollbackRemoveFn := func() { + if repo.ID == 0 { + return + } + if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { + log.Error("Rollback deleteRepository: %v", errDelete) + } + } + if err = models.IncrementRepoForkNum(ctx, oldRepo.ID); err != nil { + rollbackRemoveFn() return err } @@ -60,6 +70,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())). RunInDirTimeout(10*time.Minute, ""); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err) + rollbackRemoveFn() return fmt.Errorf("git clone: %v", err) } @@ -67,10 +78,12 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). RunInDir(repoPath); err != nil { log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) + rollbackRemoveFn() return fmt.Errorf("git update-server-info: %v", err) } if err = createDelegateHooks(repoPath); err != nil { + rollbackRemoveFn() return fmt.Errorf("createDelegateHooks: %v", err) } return nil @@ -86,5 +99,12 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, if err := models.CopyLanguageStat(oldRepo, repo); err != nil { log.Error("Copy language stat from oldRepo failed") } - return repo, models.CopyLFS(ctx, repo, oldRepo) + + if err := models.CopyLFS(ctx, repo, oldRepo); err != nil { + if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { + log.Error("Rollback deleteRepository: %v", errDelete) + } + return nil, err + } + return repo, nil } diff --git a/modules/repository/generate.go b/modules/repository/generate.go index c5fb0af38377..5d1ef72b6c04 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/huandu/xstrings" + "github.com/unknwon/com" ) type transformer struct { @@ -243,14 +244,22 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template IsEmpty: !opts.GitContent || templateRepo.IsEmpty, IsFsckEnabled: templateRepo.IsFsckEnabled, TemplateID: templateRepo.ID, + TrustModel: templateRepo.TrustModel, } - if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil { + if err = models.CreateRepository(ctx, doer, owner, generateRepo, false); err != nil { return nil, err } - repoPath := models.RepoPath(owner.Name, generateRepo.Name) - if err = checkInitRepository(repoPath); err != nil { + repoPath := generateRepo.RepoPath() + if com.IsExist(repoPath) { + return nil, models.ErrRepoFilesAlreadyExist{ + Uname: generateRepo.OwnerName, + Name: generateRepo.Name, + } + } + + if err = checkInitRepository(owner.Name, generateRepo.Name); err != nil { return generateRepo, err } diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go index 2cefd560693f..faf9c98f8ae8 100644 --- a/modules/repository/hooks.go +++ b/modules/repository/hooks.go @@ -28,9 +28,9 @@ func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) { fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), } giteaHookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s pre-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), + fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s update $1 $2 $3\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), + fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s post-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), } return } diff --git a/modules/repository/init.go b/modules/repository/init.go index 8c8827c511ac..569069ee1fcc 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -19,7 +19,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "github.com/mcuadros/go-version" "github.com/unknwon/com" ) @@ -110,10 +109,10 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def "GIT_AUTHOR_NAME="+sig.Name, "GIT_AUTHOR_EMAIL="+sig.Email, "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+sig.Name, - "GIT_COMMITTER_EMAIL="+sig.Email, "GIT_COMMITTER_DATE="+commitTimeStr, ) + committerName := sig.Name + committerEmail := sig.Email if stdout, err := git.NewCommand("add", "--all"). SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). @@ -122,7 +121,7 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def return fmt.Errorf("git add --all: %v", err) } - binVersion, err := git.BinVersion() + err = git.LoadGitVersion() if err != nil { return fmt.Errorf("Unable to get git version: %v", err) } @@ -132,15 +131,26 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def "-m", "Initial commit", } - if version.Compare(binVersion, "1.7.9", ">=") { - sign, keyID, _ := models.SignInitialCommit(tmpPath, u) + if git.CheckGitVersionAtLeast("1.7.9") == nil { + sign, keyID, signer, _ := models.SignInitialCommit(tmpPath, u) if sign { args = append(args, "-S"+keyID) - } else if version.Compare(binVersion, "2.0.0", ">=") { + + if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel { + // need to set the committer to the KeyID owner + committerName = signer.Name + committerEmail = signer.Email + } + } else if git.CheckGitVersionAtLeast("2.0.0") == nil { args = append(args, "--no-gpg-sign") } } + env = append(env, + "GIT_COMMITTER_NAME="+committerName, + "GIT_COMMITTER_EMAIL="+committerEmail, + ) + if stdout, err := git.NewCommand(args...). SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). RunInDirWithEnv(tmpPath, env); err != nil { @@ -162,10 +172,14 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def return nil } -func checkInitRepository(repoPath string) (err error) { +func checkInitRepository(owner, name string) (err error) { // Somehow the directory could exist. + repoPath := models.RepoPath(owner, name) if com.IsExist(repoPath) { - return fmt.Errorf("checkInitRepository: path already exists: %s", repoPath) + return models.ErrRepoFilesAlreadyExist{ + Uname: owner, + Name: name, + } } // Init git bare new repository. @@ -177,9 +191,85 @@ func checkInitRepository(repoPath string) (err error) { return nil } +func adoptRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { + if !com.IsExist(repoPath) { + return fmt.Errorf("adoptRepository: path does not already exist: %s", repoPath) + } + + if err := createDelegateHooks(repoPath); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + repo.IsEmpty = false + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return fmt.Errorf("openRepository: %v", err) + } + defer gitRepo.Close() + if len(opts.DefaultBranch) > 0 { + repo.DefaultBranch = opts.DefaultBranch + + if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + return fmt.Errorf("setDefaultBranch: %v", err) + } + } else { + repo.DefaultBranch, err = gitRepo.GetDefaultBranch() + if err != nil { + repo.DefaultBranch = setting.Repository.DefaultBranch + if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + return fmt.Errorf("setDefaultBranch: %v", err) + } + } + + repo.DefaultBranch = strings.TrimPrefix(repo.DefaultBranch, git.BranchPrefix) + } + branches, _ := gitRepo.GetBranches() + found := false + hasDefault := false + hasMaster := false + for _, branch := range branches { + if branch == repo.DefaultBranch { + found = true + break + } else if branch == setting.Repository.DefaultBranch { + hasDefault = true + } else if branch == "master" { + hasMaster = true + } + } + if !found { + if hasDefault { + repo.DefaultBranch = setting.Repository.DefaultBranch + } else if hasMaster { + repo.DefaultBranch = "master" + } else if len(branches) > 0 { + repo.DefaultBranch = branches[0] + } else { + repo.IsEmpty = true + repo.DefaultBranch = setting.Repository.DefaultBranch + } + + if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + return fmt.Errorf("setDefaultBranch: %v", err) + } + } + + if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} + // InitRepository initializes README and .gitignore if needed. func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) { - if err = checkInitRepository(repoPath); err != nil { + if err = checkInitRepository(repo.OwnerName, repo.Name); err != nil { return err } @@ -215,7 +305,8 @@ func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo repo.IsEmpty = true } - repo.DefaultBranch = "master" + repo.DefaultBranch = setting.Repository.DefaultBranch + if len(opts.DefaultBranch) > 0 { repo.DefaultBranch = opts.DefaultBranch gitRepo, err := git.OpenRepository(repo.RepoPath()) diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 2d5551d9875c..b18dfddd2e5a 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -13,8 +13,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -41,7 +41,7 @@ func WikiRemoteURL(remote string) string { } // MigrateRepositoryGitData starts migrating git related data after created migrating repository -func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opts api.MigrateRepoOption) (*models.Repository, error) { +func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opts migration.MigrateOptions) (*models.Repository, error) { repoPath := models.RepoPath(u.Name, opts.RepoName) if u.IsOrganization() { @@ -102,18 +102,22 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt return repo, fmt.Errorf("git.IsEmpty: %v", err) } - if !opts.Releases && !repo.IsEmpty { - // Try to get HEAD branch and set it as default branch. - headBranch, err := gitRepo.GetHEADBranch() - if err != nil { - return repo, fmt.Errorf("GetHEADBranch: %v", err) - } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name + if !repo.IsEmpty { + if len(repo.DefaultBranch) == 0 { + // Try to get HEAD branch and set it as default branch. + headBranch, err := gitRepo.GetHEADBranch() + if err != nil { + return repo, fmt.Errorf("GetHEADBranch: %v", err) + } + if headBranch != nil { + repo.DefaultBranch = headBranch.Name + } } - if err = SyncReleasesWithTags(repo, gitRepo); err != nil { - log.Error("Failed to synchronize tags to releases for repository: %v", err) + if !opts.Releases { + if err = SyncReleasesWithTags(repo, gitRepo); err != nil { + log.Error("Failed to synchronize tags to releases for repository: %v", err) + } } } diff --git a/modules/secret/secret.go b/modules/secret/secret.go index d0e4deacb902..2b6e22cc6c7f 100644 --- a/modules/secret/secret.go +++ b/modules/secret/secret.go @@ -5,8 +5,14 @@ package secret import ( + "crypto/aes" + "crypto/cipher" "crypto/rand" + "crypto/sha256" "encoding/base64" + "encoding/hex" + "errors" + "io" ) // New creats a new secret @@ -31,3 +37,65 @@ func randomString(len int64) (string, error) { b, err := randomBytes(len) return base64.URLEncoding.EncodeToString(b), err } + +// AesEncrypt encrypts text and given key with AES. +func AesEncrypt(key, text []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + b := base64.StdEncoding.EncodeToString(text) + ciphertext := make([]byte, aes.BlockSize+len(b)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + cfb := cipher.NewCFBEncrypter(block, iv) + cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) + return ciphertext, nil +} + +// AesDecrypt decrypts text and given key with AES. +func AesDecrypt(key, text []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + if len(text) < aes.BlockSize { + return nil, errors.New("ciphertext too short") + } + iv := text[:aes.BlockSize] + text = text[aes.BlockSize:] + cfb := cipher.NewCFBDecrypter(block, iv) + cfb.XORKeyStream(text, text) + data, err := base64.StdEncoding.DecodeString(string(text)) + if err != nil { + return nil, err + } + return data, nil +} + +// EncryptSecret encrypts a string with given key into a hex string +func EncryptSecret(key string, str string) (string, error) { + keyHash := sha256.Sum256([]byte(key)) + plaintext := []byte(str) + ciphertext, err := AesEncrypt(keyHash[:], plaintext) + if err != nil { + return "", err + } + return hex.EncodeToString(ciphertext), nil +} + +// DecryptSecret decrypts a previously encrypted hex string +func DecryptSecret(key string, cipherhex string) (string, error) { + keyHash := sha256.Sum256([]byte(key)) + ciphertext, err := hex.DecodeString(cipherhex) + if err != nil { + return "", err + } + plaintext, err := AesDecrypt(keyHash[:], ciphertext) + if err != nil { + return "", err + } + return string(plaintext), nil +} diff --git a/modules/secret/secret_test.go b/modules/secret/secret_test.go index c47201f2d748..6531ffbebc74 100644 --- a/modules/secret/secret_test.go +++ b/modules/secret/secret_test.go @@ -20,3 +20,16 @@ func TestNew(t *testing.T) { // check if secrets assert.NotEqual(t, result, result2) } + +func TestEncryptDecrypt(t *testing.T) { + var hex string + var str string + + hex, _ = EncryptSecret("foo", "baz") + str, _ = DecryptSecret("foo", hex) + assert.Equal(t, str, "baz") + + hex, _ = EncryptSecret("bar", "baz") + str, _ = DecryptSecret("foo", hex) + assert.NotEqual(t, str, "baz") +} diff --git a/modules/session/memory.go b/modules/session/memory.go deleted file mode 100644 index 4f72feac9b8b..000000000000 --- a/modules/session/memory.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2013 Beego Authors -// Copyright 2014 The Macaron Authors -// Copyright 2019 The Gitea Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package session - -import ( - "container/list" - "fmt" - "sync" - "time" - - "gitea.com/macaron/session" -) - -// MemStore represents a in-memory session store implementation. -type MemStore struct { - sid string - lock sync.RWMutex - data map[interface{}]interface{} - lastAccess time.Time -} - -// NewMemStore creates and returns a memory session store. -func NewMemStore(sid string) *MemStore { - return &MemStore{ - sid: sid, - data: make(map[interface{}]interface{}), - lastAccess: time.Now(), - } -} - -// Set sets value to given key in session. -func (s *MemStore) Set(key, val interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data[key] = val - return nil -} - -// Get gets value by given key in session. -func (s *MemStore) Get(key interface{}) interface{} { - s.lock.RLock() - defer s.lock.RUnlock() - - return s.data[key] -} - -// Delete deletes a key from session. -func (s *MemStore) Delete(key interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - delete(s.data, key) - return nil -} - -// ID returns current session ID. -func (s *MemStore) ID() string { - return s.sid -} - -// Release releases resource and save data to provider. -func (*MemStore) Release() error { - return nil -} - -// Flush deletes all session data. -func (s *MemStore) Flush() error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data = make(map[interface{}]interface{}) - return nil -} - -// MemProvider represents a in-memory session provider implementation. -type MemProvider struct { - lock sync.RWMutex - maxLifetime int64 - data map[string]*list.Element - // A priority list whose lastAccess newer gets higher priority. - list *list.List -} - -// Init initializes memory session provider. -func (p *MemProvider) Init(maxLifetime int64, _ string) error { - p.lock.Lock() - p.maxLifetime = maxLifetime - p.lock.Unlock() - return nil -} - -// update expands time of session store by given ID. -func (p *MemProvider) update(sid string) error { - p.lock.Lock() - defer p.lock.Unlock() - - if e, ok := p.data[sid]; ok { - e.Value.(*MemStore).lastAccess = time.Now() - p.list.MoveToFront(e) - return nil - } - return nil -} - -// Read returns raw session store by session ID. -func (p *MemProvider) Read(sid string) (_ session.RawStore, err error) { - p.lock.RLock() - e, ok := p.data[sid] - p.lock.RUnlock() - - if ok { - if err = p.update(sid); err != nil { - return nil, err - } - return e.Value.(*MemStore), nil - } - - // Create a new session. - p.lock.Lock() - defer p.lock.Unlock() - - s := NewMemStore(sid) - p.data[sid] = p.list.PushBack(s) - return s, nil -} - -// Exist returns true if session with given ID exists. -func (p *MemProvider) Exist(sid string) bool { - p.lock.RLock() - defer p.lock.RUnlock() - - _, ok := p.data[sid] - return ok -} - -// Destroy deletes a session by session ID. -func (p *MemProvider) Destroy(sid string) error { - p.lock.Lock() - defer p.lock.Unlock() - - e, ok := p.data[sid] - if !ok { - return nil - } - - p.list.Remove(e) - delete(p.data, sid) - return nil -} - -// Regenerate regenerates a session store from old session ID to new one. -func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) { - if p.Exist(sid) { - return nil, fmt.Errorf("new sid '%s' already exists", sid) - } - - s, err := p.Read(oldsid) - if err != nil { - return nil, err - } - - if err = p.Destroy(oldsid); err != nil { - return nil, err - } - - s.(*MemStore).sid = sid - - p.lock.Lock() - defer p.lock.Unlock() - p.data[sid] = p.list.PushBack(s) - return s, nil -} - -// Count counts and returns number of sessions. -func (p *MemProvider) Count() int { - return p.list.Len() -} - -// GC calls GC to clean expired sessions. -func (p *MemProvider) GC() { - p.lock.RLock() - for { - // No session in the list. - e := p.list.Back() - if e == nil { - break - } - - if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() { - p.lock.RUnlock() - p.lock.Lock() - p.list.Remove(e) - delete(p.data, e.Value.(*MemStore).sid) - p.lock.Unlock() - p.lock.RLock() - } else { - break - } - } - p.lock.RUnlock() -} diff --git a/vendor/gitea.com/macaron/session/redis/redis.go b/modules/session/redis.go similarity index 82% rename from vendor/gitea.com/macaron/session/redis/redis.go rename to modules/session/redis.go index 5f242d6b37c5..c88ebd57693d 100644 --- a/vendor/gitea.com/macaron/session/redis/redis.go +++ b/modules/session/redis.go @@ -1,5 +1,6 @@ // Copyright 2013 Beego Authors // Copyright 2014 The Macaron Authors +// Copyright 2020 The Gitea Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain @@ -17,19 +18,18 @@ package session import ( "fmt" - "strings" "sync" "time" + "code.gitea.io/gitea/modules/nosql" + "gitea.com/macaron/session" - "github.com/go-redis/redis" - "github.com/unknwon/com" - "gopkg.in/ini.v1" + "github.com/go-redis/redis/v7" ) // RedisStore represents a redis session store implementation. type RedisStore struct { - c *redis.Client + c redis.UniversalClient prefix, sid string duration time.Duration lock sync.RWMutex @@ -37,7 +37,7 @@ type RedisStore struct { } // NewRedisStore creates and returns a redis session store. -func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { +func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { return &RedisStore{ c: c, prefix: prefix, @@ -104,7 +104,7 @@ func (s *RedisStore) Flush() error { // RedisProvider represents a redis session provider implementation. type RedisProvider struct { - c *redis.Client + c redis.UniversalClient duration time.Duration prefix string } @@ -117,39 +117,16 @@ func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { return err } - cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) - if err != nil { - return err - } + uri := nosql.ToRedisURI(configs) - opt := &redis.Options{ - Network: "tcp", - } - for k, v := range cfg.Section("").KeysHash() { + for k, v := range uri.Query() { switch k { - case "network": - opt.Network = v - case "addr": - opt.Addr = v - case "password": - opt.Password = v - case "db": - opt.DB = com.StrTo(v).MustInt() - case "pool_size": - opt.PoolSize = com.StrTo(v).MustInt() - case "idle_timeout": - opt.IdleTimeout, err = time.ParseDuration(v + "s") - if err != nil { - return fmt.Errorf("error parsing idle timeout: %v", err) - } case "prefix": - p.prefix = v - default: - return fmt.Errorf("session/redis: unsupported option '%s'", k) + p.prefix = v[0] } } - p.c = redis.NewClient(opt) + p.c = nosql.GetManager().GetRedisClient(uri.String()) return p.c.Ping().Err() } @@ -228,11 +205,11 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err // Count counts and returns number of sessions. func (p *RedisProvider) Count() int { - return int(p.c.DbSize().Val()) + return int(p.c.DBSize().Val()) } // GC calls GC to clean expired sessions. -func (_ *RedisProvider) GC() {} +func (*RedisProvider) GC() {} func init() { session.Register("redis", &RedisProvider{}) diff --git a/modules/session/virtual.go b/modules/session/virtual.go index 967192016740..1139cfe89cc1 100644 --- a/modules/session/virtual.go +++ b/modules/session/virtual.go @@ -5,7 +5,6 @@ package session import ( - "container/list" "encoding/json" "fmt" "sync" @@ -16,7 +15,6 @@ import ( mysql "gitea.com/macaron/session/mysql" nodb "gitea.com/macaron/session/nodb" postgres "gitea.com/macaron/session/postgres" - redis "gitea.com/macaron/session/redis" ) // VirtualSessionProvider represents a shadowed session provider implementation. @@ -37,11 +35,11 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error { // This is only slightly more wrong than modules/setting/session.go:23 switch opts.Provider { case "memory": - o.provider = &MemProvider{list: list.New(), data: make(map[string]*list.Element)} + o.provider = &session.MemProvider{} case "file": o.provider = &session.FileProvider{} case "redis": - o.provider = &redis.RedisProvider{} + o.provider = &RedisProvider{} case "mysql": o.provider = &mysql.MysqlProvider{} case "postgres": diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index 4c7368eb68b7..98c4be94e9cd 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -4,43 +4,18 @@ package setting -import ( - "path" - "path/filepath" - "strings" -) - var ( // Attachment settings Attachment = struct { - StoreType string - Path string - ServeDirect bool - Minio struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - UseSSL bool - Bucket string - Location string - BasePath string - } + Storage AllowedTypes string MaxSize int64 MaxFiles int Enabled bool }{ - StoreType: "local", - ServeDirect: false, - Minio: struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - UseSSL bool - Bucket string - Location string - BasePath string - }{}, + Storage: Storage{ + ServeDirect: false, + }, AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip", MaxSize: 4, MaxFiles: 5, @@ -50,25 +25,11 @@ var ( func newAttachmentService() { sec := Cfg.Section("attachment") - Attachment.StoreType = sec.Key("STORE_TYPE").MustString("local") - Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false) - switch Attachment.StoreType { - case "local": - Attachment.Path = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) - if !filepath.IsAbs(Attachment.Path) { - Attachment.Path = path.Join(AppWorkPath, Attachment.Path) - } - case "minio": - Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString("localhost:9000") - Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString("") - Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("") - Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString("gitea") - Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString("us-east-1") - Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/") - Attachment.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(false) - } + storageType := sec.Key("STORAGE_TYPE").MustString("") + + Attachment.Storage = getStorage("attachments", storageType, sec) - Attachment.AllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1) + Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip") Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.Enabled = sec.Key("ENABLED").MustBool(true) diff --git a/modules/setting/cache.go b/modules/setting/cache.go index 34a212db1885..af47bd085ab8 100644 --- a/modules/setting/cache.go +++ b/modules/setting/cache.go @@ -23,7 +23,7 @@ type Cache struct { var ( // CacheService the global cache CacheService = struct { - Cache + Cache `ini:"cache"` LastCommit struct { Enabled bool diff --git a/modules/setting/cron.go b/modules/setting/cron.go index c8228ddaa87a..9475887ecc7f 100644 --- a/modules/setting/cron.go +++ b/modules/setting/cron.go @@ -4,8 +4,26 @@ package setting +import "reflect" + // GetCronSettings maps the cron subsection to the provided config func GetCronSettings(name string, config interface{}) (interface{}, error) { - err := Cfg.Section("cron." + name).MapTo(config) - return config, err + if err := Cfg.Section("cron." + name).MapTo(config); err != nil { + return config, err + } + + typ := reflect.TypeOf(config).Elem() + val := reflect.ValueOf(config).Elem() + + for i := 0; i < typ.NumField(); i++ { + field := val.Field(i) + tpField := typ.Field(i) + if tpField.Type.Kind() == reflect.Struct && tpField.Anonymous { + if err := Cfg.Section("cron." + name).MapTo(field.Addr().Interface()); err != nil { + return config, err + } + } + } + + return config, nil } diff --git a/modules/setting/cron_test.go b/modules/setting/cron_test.go new file mode 100644 index 000000000000..8670a92bac96 --- /dev/null +++ b/modules/setting/cron_test.go @@ -0,0 +1,47 @@ +// 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 setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" + ini "gopkg.in/ini.v1" +) + +func Test_GetCronSettings(t *testing.T) { + + type BaseStruct struct { + Base bool + Second string + } + + type Extended struct { + BaseStruct + Extend bool + } + + iniStr := ` +[cron.test] +Base = true +Second = white rabbit +Extend = true +` + Cfg, _ = ini.Load([]byte(iniStr)) + + extended := &Extended{ + BaseStruct: BaseStruct{ + Second: "queen of hearts", + }, + } + + _, err := GetCronSettings("test", extended) + + assert.NoError(t, err) + assert.True(t, extended.Base) + assert.EqualValues(t, extended.Second, "white rabbit") + assert.True(t, extended.Extend) + +} diff --git a/modules/setting/database.go b/modules/setting/database.go index d5d03c2a306b..7d082d13795c 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -47,7 +47,8 @@ var ( ConnMaxLifetime time.Duration IterateBufferSize int }{ - Timeout: 500, + Timeout: 500, + IterateBufferSize: 50, } ) diff --git a/modules/setting/git.go b/modules/setting/git.go index f83758f3879c..968de557532b 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -9,8 +9,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - - version "github.com/mcuadros/go-version" ) var ( @@ -67,24 +65,24 @@ func newGit() { log.Fatal("Failed to map Git settings: %v", err) } if err := git.SetExecutablePath(Git.Path); err != nil { - log.Fatal("Failed to initialize Git settings", err) + log.Fatal("Failed to initialize Git settings: %v", err) } git.DefaultCommandExecutionTimeout = time.Duration(Git.Timeout.Default) * time.Second - binVersion, err := git.BinVersion() + version, err := git.LocalVersion() if err != nil { log.Fatal("Error retrieving git version: %v", err) } - if version.Compare(binVersion, "2.9", ">=") { + if git.CheckGitVersionAtLeast("2.9") == nil { // Explicitly disable credential helper, otherwise Git credentials might leak git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "credential.helper=") } var format = "Git Version: %s" - var args = []interface{}{binVersion} + var args = []interface{}{version.Original()} // Since git wire protocol has been released from git v2.18 - if Git.EnableAutoGitWireProtocol && version.Compare(binVersion, "2.18", ">=") { + if Git.EnableAutoGitWireProtocol && git.CheckGitVersionAtLeast("2.18") == nil { git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "protocol.version=2") format += ", Wire Protocol %s Enabled" args = append(args, "Version 2") // for focus color diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index 4d4df62014eb..842ef8ea4116 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -36,7 +36,10 @@ var ( StartupTimeout time.Duration RepoIndexerEnabled bool + RepoType string RepoPath string + RepoConnStr string + RepoIndexerName string UpdateQueueLength int MaxIndexerFileSize int64 IncludePatterns []glob.Glob @@ -52,6 +55,11 @@ var ( IssueQueueConnStr: "", IssueQueueBatchNumber: 20, + RepoIndexerEnabled: false, + RepoType: "bleve", + RepoPath: "indexers/repos.bleve", + RepoConnStr: "", + RepoIndexerName: "gitea_codes", MaxIndexerFileSize: 1024 * 1024, ExcludeVendored: true, } @@ -69,14 +77,18 @@ func newIndexerService() { Indexer.IssueQueueType = sec.Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(LevelQueueType) Indexer.IssueQueueDir = sec.Key("ISSUE_INDEXER_QUEUE_DIR").MustString(path.Join(AppDataPath, "indexers/issues.queue")) - Indexer.IssueQueueConnStr = sec.Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString(path.Join(AppDataPath, "")) + Indexer.IssueQueueConnStr = sec.Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString("") Indexer.IssueQueueBatchNumber = sec.Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(20) Indexer.RepoIndexerEnabled = sec.Key("REPO_INDEXER_ENABLED").MustBool(false) + Indexer.RepoType = sec.Key("REPO_INDEXER_TYPE").MustString("bleve") Indexer.RepoPath = sec.Key("REPO_INDEXER_PATH").MustString(path.Join(AppDataPath, "indexers/repos.bleve")) if !filepath.IsAbs(Indexer.RepoPath) { Indexer.RepoPath = path.Join(AppWorkPath, Indexer.RepoPath) } + Indexer.RepoConnStr = sec.Key("REPO_INDEXER_CONN_STR").MustString("") + Indexer.RepoIndexerName = sec.Key("REPO_INDEXER_NAME").MustString("gitea_codes") + Indexer.IncludePatterns = IndexerGlobFromString(sec.Key("REPO_INDEXER_INCLUDE").MustString("")) Indexer.ExcludePatterns = IndexerGlobFromString(sec.Key("REPO_INDEXER_EXCLUDE").MustString("")) Indexer.ExcludeVendored = sec.Key("REPO_INDEXER_EXCLUDE_VENDORED").MustBool(true) diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go new file mode 100644 index 000000000000..5af80c2ab16f --- /dev/null +++ b/modules/setting/lfs.go @@ -0,0 +1,107 @@ +// 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 ( + "encoding/base64" + "os" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + + "github.com/unknwon/com" + ini "gopkg.in/ini.v1" +) + +// LFS represents the configuration for Git LFS +var LFS = struct { + StartServer bool `ini:"LFS_START_SERVER"` + JWTSecretBase64 string `ini:"LFS_JWT_SECRET"` + JWTSecretBytes []byte `ini:"-"` + HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` + MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` + LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` + + Storage +}{} + +func newLFSService() { + sec := Cfg.Section("server") + if err := sec.MapTo(&LFS); err != nil { + log.Fatal("Failed to map LFS settings: %v", err) + } + + lfsSec := Cfg.Section("lfs") + storageType := lfsSec.Key("STORAGE_TYPE").MustString("") + + // Specifically default PATH to LFS_CONTENT_PATH + lfsSec.Key("PATH").MustString( + sec.Key("LFS_CONTENT_PATH").String()) + + LFS.Storage = getStorage("lfs", storageType, lfsSec) + + // Rest of LFS service settings + if LFS.LocksPagingNum == 0 { + LFS.LocksPagingNum = 50 + } + + LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(20 * time.Minute) + + if LFS.StartServer { + LFS.JWTSecretBytes = make([]byte, 32) + n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64)) + + if err != nil || n != 32 { + LFS.JWTSecretBase64, err = generate.NewJwtSecret() + if err != nil { + log.Fatal("Error generating JWT Secret for custom config: %v", err) + return + } + + // Save secret + cfg := ini.Empty() + if com.IsFile(CustomConf) { + // Keeps custom settings if there is already something. + if err := cfg.Append(CustomConf); err != nil { + log.Error("Failed to load custom conf '%s': %v", CustomConf, err) + } + } + + cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) + + if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { + log.Fatal("Failed to create '%s': %v", CustomConf, err) + } + if err := cfg.SaveTo(CustomConf); err != nil { + log.Fatal("Error saving generated JWT Secret to custom config: %v", err) + return + } + } + } +} + +// CheckLFSVersion will check lfs version, if not satisfied, then disable it. +func CheckLFSVersion() { + if LFS.StartServer { + //Disable LFS client hooks if installed for the current OS user + //Needs at least git v2.1.2 + + err := git.LoadGitVersion() + if err != nil { + log.Fatal("Error retrieving git version: %v", err) + } + + if git.CheckGitVersionAtLeast("2.1.2") != nil { + LFS.StartServer = false + log.Error("LFS server support needs at least Git v2.1.2") + } else { + git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "filter.lfs.required=", + "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") + } + } +} diff --git a/modules/setting/picture.go b/modules/setting/picture.go new file mode 100644 index 000000000000..fa97245aa144 --- /dev/null +++ b/modules/setting/picture.go @@ -0,0 +1,114 @@ +// 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 setting + +import ( + "net/url" + + "code.gitea.io/gitea/modules/log" + + "strk.kbt.io/projects/go/libravatar" +) + +// settings +var ( + // Picture settings + Avatar = struct { + Storage + + MaxWidth int + MaxHeight int + MaxFileSize int64 + }{ + MaxWidth: 4096, + MaxHeight: 3072, + MaxFileSize: 1048576, + } + + GravatarSource string + GravatarSourceURL *url.URL + DisableGravatar bool + EnableFederatedAvatar bool + LibravatarService *libravatar.Libravatar + + RepoAvatar = struct { + Storage + + Fallback string + FallbackImage string + }{} +) + +func newPictureService() { + sec := Cfg.Section("picture") + + avatarSec := Cfg.Section("avatar") + storageType := sec.Key("AVATAR_STORAGE_TYPE").MustString("") + // Specifically default PATH to AVATAR_UPLOAD_PATH + avatarSec.Key("PATH").MustString( + sec.Key("AVATAR_UPLOAD_PATH").String()) + + Avatar.Storage = getStorage("avatars", storageType, avatarSec) + + Avatar.MaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096) + Avatar.MaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072) + Avatar.MaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576) + + switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source { + case "duoshuo": + GravatarSource = "http://gravatar.duoshuo.com/avatar/" + case "gravatar": + GravatarSource = "https://secure.gravatar.com/avatar/" + case "libravatar": + GravatarSource = "https://seccdn.libravatar.org/avatar/" + default: + GravatarSource = source + } + DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool() + EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(!InstallLock) + if OfflineMode { + DisableGravatar = true + EnableFederatedAvatar = false + } + if DisableGravatar { + EnableFederatedAvatar = false + } + if EnableFederatedAvatar || !DisableGravatar { + var err error + GravatarSourceURL, err = url.Parse(GravatarSource) + if err != nil { + log.Fatal("Failed to parse Gravatar URL(%s): %v", + GravatarSource, err) + } + } + + if EnableFederatedAvatar { + LibravatarService = libravatar.New() + if GravatarSourceURL.Scheme == "https" { + LibravatarService.SetUseHTTPS(true) + LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host) + } else { + LibravatarService.SetUseHTTPS(false) + LibravatarService.SetFallbackHost(GravatarSourceURL.Host) + } + } + + newRepoAvatarService() +} + +func newRepoAvatarService() { + sec := Cfg.Section("picture") + + repoAvatarSec := Cfg.Section("repo-avatar") + storageType := sec.Key("REPOSITORY_AVATAR_STORAGE_TYPE").MustString("") + // Specifically default PATH to AVATAR_UPLOAD_PATH + repoAvatarSec.Key("PATH").MustString( + sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").String()) + + RepoAvatar.Storage = getStorage("repo-avatars", storageType, repoAvatarSec) + + RepoAvatar.Fallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none") + RepoAvatar.FallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/img/repo_default.png") +} diff --git a/modules/setting/queue.go b/modules/setting/queue.go index 8bdca1017f2e..236560456299 100644 --- a/modules/setting/queue.go +++ b/modules/setting/queue.go @@ -6,7 +6,6 @@ package setting import ( "fmt" - "path" "path/filepath" "strconv" "strings" @@ -17,8 +16,9 @@ import ( // QueueSettings represent the settings for a queue from the ini type QueueSettings struct { + Name string DataDir string - Length int + QueueLength int `ini:"LENGTH"` BatchLength int ConnectionString string Type string @@ -45,6 +45,8 @@ var Queue = QueueSettings{} func GetQueueSettings(name string) QueueSettings { q := QueueSettings{} sec := Cfg.Section("queue." + name) + q.Name = name + // DataDir is not directly inheritable q.DataDir = filepath.Join(Queue.DataDir, name) // QueueName is not directly inheritable either @@ -66,8 +68,9 @@ func GetQueueSettings(name string) QueueSettings { q.DataDir = filepath.Join(AppDataPath, q.DataDir) } _, _ = sec.NewKey("DATADIR", q.DataDir) + // The rest are... - q.Length = sec.Key("LENGTH").MustInt(Queue.Length) + q.QueueLength = sec.Key("LENGTH").MustInt(Queue.QueueLength) 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) @@ -92,9 +95,9 @@ func NewQueueService() { if !filepath.IsAbs(Queue.DataDir) { Queue.DataDir = filepath.Join(AppDataPath, Queue.DataDir) } - Queue.Length = sec.Key("LENGTH").MustInt(20) + Queue.QueueLength = sec.Key("LENGTH").MustInt(20) Queue.BatchLength = sec.Key("BATCH_LENGTH").MustInt(20) - Queue.ConnectionString = sec.Key("CONN_STR").MustString(path.Join(AppDataPath, "")) + Queue.ConnectionString = sec.Key("CONN_STR").MustString("") Queue.Type = sec.Key("TYPE").MustString("persistable-channel") Queue.Network, Queue.Addresses, Queue.Password, Queue.DBIndex, _ = ParseQueueConnStr(Queue.ConnectionString) Queue.WrapIfNecessary = sec.Key("WRAP_IF_NECESSARY").MustBool(true) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index eb1501d7b86e..0bb10199a8d1 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -29,6 +29,7 @@ var ( AnsiCharset string ForcePrivate bool DefaultPrivate string + DefaultPushCreatePrivate bool MaxCreationLimit int MirrorQueueLength int PullRequestQueueLength int @@ -44,6 +45,8 @@ var ( PrefixArchiveFiles bool DisableMirrors bool DefaultBranch string + AllowAdoptionOfUnadoptedRepositories bool + AllowDeleteOfUnadoptedRepositories bool // Repository editor settings Editor struct { @@ -55,7 +58,7 @@ var ( Upload struct { Enabled bool TempPath string - AllowedTypes []string `delim:"|"` + AllowedTypes string FileMaxSize int64 MaxFiles int } `ini:"-"` @@ -82,14 +85,19 @@ var ( LockReasons []string } `ini:"repository.issue"` + Release struct { + AllowedTypes string + } `ini:"repository.release"` + Signing struct { - SigningKey string - SigningName string - SigningEmail string - InitialCommit []string - CRUDActions []string `ini:"CRUD_ACTIONS"` - Merges []string - Wiki []string + SigningKey string + SigningName string + SigningEmail string + InitialCommit []string + CRUDActions []string `ini:"CRUD_ACTIONS"` + Merges []string + Wiki []string + DefaultTrustModel string } `ini:"repository.signing"` }{ DetectedCharsetsOrder: []string{ @@ -131,6 +139,7 @@ var ( AnsiCharset: "", ForcePrivate: false, DefaultPrivate: RepoCreatingLastUserVisibility, + DefaultPushCreatePrivate: true, MaxCreationLimit: -1, MirrorQueueLength: 1000, PullRequestQueueLength: 1000, @@ -145,6 +154,7 @@ var ( DefaultRepoUnits: []string{}, PrefixArchiveFiles: true, DisableMirrors: false, + DefaultBranch: "master", // Repository editor settings Editor: struct { @@ -159,13 +169,13 @@ var ( Upload: struct { Enabled bool TempPath string - AllowedTypes []string `delim:"|"` + AllowedTypes string FileMaxSize int64 MaxFiles int }{ Enabled: true, TempPath: "data/tmp/uploads", - AllowedTypes: []string{}, + AllowedTypes: "", FileMaxSize: 3, MaxFiles: 5, }, @@ -207,23 +217,31 @@ var ( LockReasons: strings.Split("Too heated,Off-topic,Spam,Resolved", ","), }, + Release: struct { + AllowedTypes string + }{ + AllowedTypes: "", + }, + // Signing settings Signing: struct { - SigningKey string - SigningName string - SigningEmail string - InitialCommit []string - CRUDActions []string `ini:"CRUD_ACTIONS"` - Merges []string - Wiki []string + SigningKey string + SigningName string + SigningEmail string + InitialCommit []string + CRUDActions []string `ini:"CRUD_ACTIONS"` + Merges []string + Wiki []string + DefaultTrustModel string }{ - SigningKey: "default", - SigningName: "", - SigningEmail: "", - InitialCommit: []string{"always"}, - CRUDActions: []string{"pubkey", "twofa", "parentsigned"}, - Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"}, - Wiki: []string{"never"}, + SigningKey: "default", + SigningName: "", + SigningEmail: "", + InitialCommit: []string{"always"}, + CRUDActions: []string{"pubkey", "twofa", "parentsigned"}, + Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"}, + Wiki: []string{"never"}, + DefaultTrustModel: "collaborator", }, } RepoRootPath string @@ -235,14 +253,14 @@ func newRepository() { if err != nil { log.Fatal("Failed to get home directory: %v", err) } - homeDir = strings.Replace(homeDir, "\\", "/", -1) + homeDir = strings.ReplaceAll(homeDir, "\\", "/") // Determine and create root git repository path. sec := Cfg.Section("repository") Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool() Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool() Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) - Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString("master") + Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch) RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gitea-repositories")) forcePathSeparator(RepoRootPath) if !filepath.IsAbs(RepoRootPath) { @@ -268,6 +286,13 @@ func newRepository() { log.Fatal("Failed to map Repository.PullRequest settings: %v", err) } + // Handle default trustmodel settings + Repository.Signing.DefaultTrustModel = strings.ToLower(strings.TrimSpace(Repository.Signing.DefaultTrustModel)) + if Repository.Signing.DefaultTrustModel == "default" { + Repository.Signing.DefaultTrustModel = "collaborator" + } + + // Handle preferred charset orders preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder)) for _, charset := range Repository.DetectedCharsetsOrder { canonicalCharset := strings.ToLower(strings.TrimSpace(charset)) diff --git a/modules/setting/service.go b/modules/setting/service.go index c463b0a9d5d0..4d03df17a4bf 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -35,6 +35,8 @@ var Service struct { RecaptchaSecret string RecaptchaSitekey string RecaptchaURL string + HcaptchaSecret string + HcaptchaSitekey string DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool EnableTimetracking bool @@ -76,6 +78,8 @@ func newService() { Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("") Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("") Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/") + Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("") + Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("") Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index ae15f68faa5c..7ae8bb352de1 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -23,15 +23,13 @@ import ( "time" "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/user" shellquote "github.com/kballard/go-shellquote" - version "github.com/mcuadros/go-version" "github.com/unknwon/com" + gossh "golang.org/x/crypto/ssh" ini "gopkg.in/ini.v1" - "strk.kbt.io/projects/go/libravatar" ) // Scheme describes protocol types @@ -61,6 +59,7 @@ const ( const ( ImageCaptcha = "image" ReCaptcha = "recaptcha" + HCaptcha = "hcaptcha" ) // settings @@ -104,44 +103,42 @@ var ( StaticURLPrefix string SSH = struct { - Disabled bool `ini:"DISABLE_SSH"` - StartBuiltinServer bool `ini:"START_SSH_SERVER"` - BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` - Domain string `ini:"SSH_DOMAIN"` - Port int `ini:"SSH_PORT"` - ListenHost string `ini:"SSH_LISTEN_HOST"` - ListenPort int `ini:"SSH_LISTEN_PORT"` - RootPath string `ini:"SSH_ROOT_PATH"` - ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` - ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` - ServerMACs []string `ini:"SSH_SERVER_MACS"` - KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` - KeygenPath string `ini:"SSH_KEYGEN_PATH"` - AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` - MinimumKeySizeCheck bool `ini:"-"` - MinimumKeySizes map[string]int `ini:"-"` - CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` - ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` + Disabled bool `ini:"DISABLE_SSH"` + StartBuiltinServer bool `ini:"START_SSH_SERVER"` + BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` + Domain string `ini:"SSH_DOMAIN"` + Port int `ini:"SSH_PORT"` + ListenHost string `ini:"SSH_LISTEN_HOST"` + ListenPort int `ini:"SSH_LISTEN_PORT"` + RootPath string `ini:"SSH_ROOT_PATH"` + ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` + ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` + ServerMACs []string `ini:"SSH_SERVER_MACS"` + KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` + KeygenPath string `ini:"SSH_KEYGEN_PATH"` + AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` + AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` + MinimumKeySizeCheck bool `ini:"-"` + MinimumKeySizes map[string]int `ini:"-"` + CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` + CreateAuthorizedPrincipalsFile bool `ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE"` + ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` + AuthorizedPrincipalsAllow []string `ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW"` + AuthorizedPrincipalsEnabled bool `ini:"-"` + TrustedUserCAKeys []string `ini:"SSH_TRUSTED_USER_CA_KEYS"` + TrustedUserCAKeysFile string `ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME"` + TrustedUserCAKeysParsed []gossh.PublicKey `ini:"-"` }{ - Disabled: false, - StartBuiltinServer: false, - Domain: "", - Port: 22, - ServerCiphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128"}, - ServerKeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "curve25519-sha256@libssh.org"}, - ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96"}, - KeygenPath: "ssh-keygen", - MinimumKeySizes: map[string]int{"ed25519": 256, "ecdsa": 256, "rsa": 2048, "dsa": 1024}, - } - - LFS struct { - StartServer bool `ini:"LFS_START_SERVER"` - ContentPath string `ini:"LFS_CONTENT_PATH"` - JWTSecretBase64 string `ini:"LFS_JWT_SECRET"` - JWTSecretBytes []byte `ini:"-"` - HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` - MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` - LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` + Disabled: false, + StartBuiltinServer: false, + Domain: "", + Port: 22, + ServerCiphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128"}, + ServerKeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "curve25519-sha256@libssh.org"}, + ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96"}, + KeygenPath: "ssh-keygen", + MinimumKeySizeCheck: true, + MinimumKeySizes: map[string]int{"ed25519": 256, "ecdsa": 256, "rsa": 2048}, } // Security settings @@ -158,6 +155,7 @@ var ( OnlyAllowPushIfGiteaEnvironmentSet bool PasswordComplexity []string PasswordHashAlgo string + PasswordCheckPwn bool // UI settings UI = struct { @@ -273,20 +271,6 @@ var ( DefaultEmailNotification string } - // Picture settings - AvatarUploadPath string - AvatarMaxWidth int - AvatarMaxHeight int - GravatarSource string - GravatarSourceURL *url.URL - DisableGravatar bool - EnableFederatedAvatar bool - LibravatarService *libravatar.Libravatar - AvatarMaxFileSize int64 - RepositoryAvatarUploadPath string - RepositoryAvatarFallback string - RepositoryAvatarFallbackImage string - // Log settings LogLevel string StacktraceLogLevel string @@ -406,7 +390,7 @@ func getAppPath() (string, error) { } // Note: we don't use path.Dir here because it does not handle case // which path starts with two "/" in Windows: "//psf/Home/..." - return strings.Replace(appPath, "\\", "/", -1), err + return strings.ReplaceAll(appPath, "\\", "/"), err } func getWorkPath(appPath string) string { @@ -423,7 +407,7 @@ func getWorkPath(appPath string) string { workPath = appPath[:i] } } - return strings.Replace(workPath, "\\", "/", -1) + return strings.ReplaceAll(workPath, "\\", "/") } func init() { @@ -473,27 +457,6 @@ func createPIDFile(pidPath string) { } } -// CheckLFSVersion will check lfs version, if not satisfied, then disable it. -func CheckLFSVersion() { - if LFS.StartServer { - //Disable LFS client hooks if installed for the current OS user - //Needs at least git v2.1.2 - - binVersion, err := git.BinVersion() - if err != nil { - log.Fatal("Error retrieving git version: %v", err) - } - - if !version.Compare(binVersion, "2.1.2", ">=") { - LFS.StartServer = false - log.Error("LFS server support needs at least Git v2.1.2") - } else { - git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "filter.lfs.required=", - "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") - } - } -} - // SetCustomPathAndConf will set CustomPath and CustomConf with reference to the // GITEA_CUSTOM environment variable and with provided overrides before stepping // back to the default @@ -546,7 +509,7 @@ func NewContext() { if err != nil { log.Fatal("Failed to get home directory: %v", err) } - homeDir = strings.Replace(homeDir, "\\", "/", -1) + homeDir = strings.ReplaceAll(homeDir, "\\", "/") LogLevel = getLogLevel(Cfg.Section("log"), "LEVEL", "Info") StacktraceLogLevel = getStacktraceLogLevel(Cfg.Section("log"), "STACKTRACE_LEVEL", "None") @@ -702,15 +665,41 @@ func NewContext() { SSH.StartBuiltinServer = false } + trustedUserCaKeys := sec.Key("SSH_TRUSTED_USER_CA_KEYS").Strings(",") + for _, caKey := range trustedUserCaKeys { + pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(caKey)) + if err != nil { + log.Fatal("Failed to parse TrustedUserCaKeys: %s %v", caKey, err) + } + + SSH.TrustedUserCAKeysParsed = append(SSH.TrustedUserCAKeysParsed, pubKey) + } + if len(trustedUserCaKeys) > 0 { + // Set the default as email,username otherwise we can leave it empty + sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("username,email") + } else { + sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("off") + } + + SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(",")) + if !SSH.Disabled && !SSH.StartBuiltinServer { if err := os.MkdirAll(SSH.RootPath, 0700); err != nil { log.Fatal("Failed to create '%s': %v", SSH.RootPath, err) } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil { log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err) } + + if len(trustedUserCaKeys) > 0 && SSH.AuthorizedPrincipalsEnabled { + fname := sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem")) + if err := ioutil.WriteFile(fname, + []byte(strings.Join(trustedUserCaKeys, "\n")), 0600); err != nil { + log.Fatal("Failed to create '%s': %v", fname, err) + } + } } - SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool() + SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool(SSH.MinimumKeySizeCheck) minimumKeySizes := Cfg.Section("ssh.minimum_key_sizes").Keys() for _, key := range minimumKeySizes { if key.MustInt() != -1 { @@ -719,55 +708,18 @@ func NewContext() { delete(SSH.MinimumKeySizes, strings.ToLower(key.Name())) } } + SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true) SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) - SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) - sec = Cfg.Section("server") - if err = sec.MapTo(&LFS); err != nil { - log.Fatal("Failed to map LFS settings: %v", err) + SSH.AuthorizedPrincipalsBackup = false + SSH.CreateAuthorizedPrincipalsFile = false + if SSH.AuthorizedPrincipalsEnabled { + SSH.AuthorizedPrincipalsBackup = sec.Key("SSH_AUTHORIZED_PRINCIPALS_BACKUP").MustBool(true) + SSH.CreateAuthorizedPrincipalsFile = sec.Key("SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE").MustBool(true) } - LFS.ContentPath = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs")) - if !filepath.IsAbs(LFS.ContentPath) { - LFS.ContentPath = filepath.Join(AppWorkPath, LFS.ContentPath) - } - if LFS.LocksPagingNum == 0 { - LFS.LocksPagingNum = 50 - } - - LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(20 * time.Minute) - - if LFS.StartServer { - LFS.JWTSecretBytes = make([]byte, 32) - n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64)) - if err != nil || n != 32 { - LFS.JWTSecretBase64, err = generate.NewJwtSecret() - if err != nil { - log.Fatal("Error generating JWT Secret for custom config: %v", err) - return - } - - // Save secret - cfg := ini.Empty() - if com.IsFile(CustomConf) { - // Keeps custom settings if there is already something. - if err := cfg.Append(CustomConf); err != nil { - log.Error("Failed to load custom conf '%s': %v", CustomConf, err) - } - } - - cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) - - if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { - log.Fatal("Failed to create '%s': %v", CustomConf, err) - } - if err := cfg.SaveTo(CustomConf); err != nil { - log.Fatal("Error saving generated JWT Secret to custom config: %v", err) - return - } - } - } + SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil { log.Fatal("Failed to OAuth2 settings: %v", err) @@ -817,10 +769,11 @@ func NewContext() { ReverseProxyAuthEmail = sec.Key("REVERSE_PROXY_AUTHENTICATION_EMAIL").MustString("X-WEBAUTH-EMAIL") MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6) ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) - DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(false) + DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true) OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true) - PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2") + PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("argon2") CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) + PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) InternalToken = loadInternalToken(sec) @@ -837,6 +790,7 @@ func NewContext() { } newAttachmentService() + newLFSService() timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("") if timeFormatKey != "" { @@ -895,59 +849,7 @@ func NewContext() { newRepository() - sec = Cfg.Section("picture") - AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars")) - forcePathSeparator(AvatarUploadPath) - if !filepath.IsAbs(AvatarUploadPath) { - AvatarUploadPath = path.Join(AppWorkPath, AvatarUploadPath) - } - RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars")) - forcePathSeparator(RepositoryAvatarUploadPath) - if !filepath.IsAbs(RepositoryAvatarUploadPath) { - RepositoryAvatarUploadPath = path.Join(AppWorkPath, RepositoryAvatarUploadPath) - } - RepositoryAvatarFallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none") - RepositoryAvatarFallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/img/repo_default.png") - AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096) - AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072) - AvatarMaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576) - switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source { - case "duoshuo": - GravatarSource = "http://gravatar.duoshuo.com/avatar/" - case "gravatar": - GravatarSource = "https://secure.gravatar.com/avatar/" - case "libravatar": - GravatarSource = "https://seccdn.libravatar.org/avatar/" - default: - GravatarSource = source - } - DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool() - EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(!InstallLock) - if OfflineMode { - DisableGravatar = true - EnableFederatedAvatar = false - } - if DisableGravatar { - EnableFederatedAvatar = false - } - if EnableFederatedAvatar || !DisableGravatar { - GravatarSourceURL, err = url.Parse(GravatarSource) - if err != nil { - log.Fatal("Failed to parse Gravatar URL(%s): %v", - GravatarSource, err) - } - } - - if EnableFederatedAvatar { - LibravatarService = libravatar.New() - if GravatarSourceURL.Scheme == "https" { - LibravatarService.SetUseHTTPS(true) - LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host) - } else { - LibravatarService.SetUseHTTPS(false) - LibravatarService.SetFallbackHost(GravatarSourceURL.Host) - } - } + newPictureService() if err = Cfg.Section("ui").MapTo(&UI); err != nil { log.Fatal("Failed to map UI settings: %v", err) @@ -1008,8 +910,8 @@ func NewContext() { newMarkup() sec = Cfg.Section("U2F") - U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimRight(AppURL, "/"))) - U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/")) + U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimSuffix(AppURL, AppSubURL+"/"))) + U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/")) UI.ReactionsMap = make(map[string]bool) for _, reaction := range UI.Reactions { @@ -1017,6 +919,38 @@ func NewContext() { } } +func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { + anything := false + email := false + username := false + for _, value := range values { + v := strings.ToLower(strings.TrimSpace(value)) + switch v { + case "off": + return []string{"off"}, false + case "email": + email = true + case "username": + username = true + case "anything": + anything = true + } + } + if anything { + return []string{"anything"}, true + } + + authorizedPrincipalsAllow := []string{} + if username { + authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "username") + } + if email { + authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "email") + } + + return authorizedPrincipalsAllow, true +} + func loadInternalToken(sec *ini.Section) string { uri := sec.Key("INTERNAL_TOKEN_URI").String() if len(uri) == 0 { @@ -1087,20 +1021,11 @@ func loadOrGenerateInternalToken(sec *ini.Section) string { return token } -func ensureLFSDirectory() { - if LFS.StartServer { - if err := os.MkdirAll(LFS.ContentPath, 0700); err != nil { - log.Fatal("Failed to create '%s': %v", LFS.ContentPath, err) - } - } -} - // NewServices initializes the services func NewServices() { InitDBConfig() newService() NewLogServices(false) - ensureLFSDirectory() newCacheService() newSessionService() newCORSService() diff --git a/modules/setting/storage.go b/modules/setting/storage.go new file mode 100644 index 000000000000..ab0598ccf8d4 --- /dev/null +++ b/modules/setting/storage.go @@ -0,0 +1,81 @@ +// 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 setting + +import ( + "path/filepath" + "reflect" + + ini "gopkg.in/ini.v1" +) + +// Storage represents configuration of storages +type Storage struct { + Type string + Path string + Section *ini.Section + ServeDirect bool +} + +// MapTo implements the Mappable interface +func (s *Storage) MapTo(v interface{}) error { + pathValue := reflect.ValueOf(v).FieldByName("Path") + if pathValue.IsValid() && pathValue.Kind() == reflect.String { + pathValue.SetString(s.Path) + } + if s.Section != nil { + return s.Section.MapTo(v) + } + return nil +} + +func getStorage(name, typ string, overrides ...*ini.Section) Storage { + sectionName := "storage" + if len(name) > 0 { + sectionName = sectionName + "." + typ + } + sec := Cfg.Section(sectionName) + + if len(overrides) == 0 { + overrides = []*ini.Section{ + Cfg.Section(sectionName + "." + name), + } + } + + var storage Storage + + storage.Type = sec.Key("STORAGE_TYPE").MustString("") + storage.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false) + + // Global Defaults + sec.Key("MINIO_ENDPOINT").MustString("localhost:9000") + sec.Key("MINIO_ACCESS_KEY_ID").MustString("") + sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("") + sec.Key("MINIO_BUCKET").MustString("gitea") + sec.Key("MINIO_LOCATION").MustString("us-east-1") + sec.Key("MINIO_USE_SSL").MustBool(false) + + storage.Section = sec + + for _, override := range overrides { + for _, key := range storage.Section.Keys() { + if !override.HasKey(key.Name()) { + _, _ = override.NewKey(key.Name(), key.Value()) + } + } + storage.ServeDirect = override.Key("SERVE_DIRECT").MustBool(false) + storage.Section = override + } + + // Specific defaults + storage.Path = storage.Section.Key("PATH").MustString(filepath.Join(AppDataPath, name)) + if !filepath.IsAbs(storage.Path) { + storage.Path = filepath.Join(AppWorkPath, storage.Path) + storage.Section.Key("PATH").SetValue(storage.Path) + } + storage.Section.Key("MINIO_BASE_PATH").MustString(name + "/") + + return storage +} diff --git a/modules/setting/task.go b/modules/setting/task.go index 81ed39a9fb90..d9329dbb7608 100644 --- a/modules/setting/task.go +++ b/modules/setting/task.go @@ -10,9 +10,10 @@ func newTaskService() { switch taskSec.Key("QUEUE_TYPE").MustString(ChannelQueueType) { case ChannelQueueType: queueTaskSec.Key("TYPE").MustString("persistable-channel") + queueTaskSec.Key("CONN_STR").MustString(taskSec.Key("QUEUE_CONN_STR").MustString("")) case RedisQueueType: queueTaskSec.Key("TYPE").MustString("redis") + queueTaskSec.Key("CONN_STR").MustString(taskSec.Key("QUEUE_CONN_STR").MustString("addrs=127.0.0.1:6379 db=0")) } 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/ssh/ssh.go b/modules/ssh/ssh.go index e7a694683ab4..7a449dd41b0a 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -5,6 +5,7 @@ package ssh import ( + "bytes" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -136,6 +137,52 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { return false } + // check if we have a certificate + if cert, ok := key.(*gossh.Certificate); ok { + if len(setting.SSH.TrustedUserCAKeys) == 0 { + return false + } + + // look for the exact principal + for _, principal := range cert.ValidPrincipals { + pkey, err := models.SearchPublicKeyByContentExact(principal) + if err != nil { + log.Error("SearchPublicKeyByContentExact: %v", err) + return false + } + + if models.IsErrKeyNotExist(err) { + continue + } + + c := &gossh.CertChecker{ + IsUserAuthority: func(auth gossh.PublicKey) bool { + for _, k := range setting.SSH.TrustedUserCAKeysParsed { + if bytes.Equal(auth.Marshal(), k.Marshal()) { + return true + } + } + + return false + }, + } + + // check the CA of the cert + if !c.IsUserAuthority(cert.SignatureKey) { + return false + } + + // validate the cert for this principal + if err := c.CheckCert(principal, cert); err != nil { + return false + } + + ctx.SetValue(giteaKeyID, pkey.ID) + + return true + } + } + pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))) if err != nil { log.Error("SearchPublicKeyByContent: %v", err) diff --git a/modules/storage/helper.go b/modules/storage/helper.go new file mode 100644 index 000000000000..93f22734e57f --- /dev/null +++ b/modules/storage/helper.go @@ -0,0 +1,65 @@ +// 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 storage + +import ( + "encoding/json" + "reflect" +) + +// Mappable represents an interface that can MapTo another interface +type Mappable interface { + MapTo(v interface{}) error +} + +// toConfig will attempt to convert a given configuration cfg into the provided exemplar type. +// +// It will tolerate the cfg being passed as a []byte or string of a json representation of the +// exemplar or the correct type of the exemplar itself +func toConfig(exemplar, cfg interface{}) (interface{}, error) { + + // First of all check if we've got the same type as the exemplar - if so it's all fine. + if reflect.TypeOf(cfg).AssignableTo(reflect.TypeOf(exemplar)) { + return cfg, nil + } + + // Now if not - does it provide a MapTo function we can try? + if mappable, ok := cfg.(Mappable); ok { + newVal := reflect.New(reflect.TypeOf(exemplar)) + if err := mappable.MapTo(newVal.Interface()); err == nil { + return newVal.Elem().Interface(), nil + } + // MapTo has failed us ... let's try the json route ... + } + + // OK we've been passed a byte array right? + configBytes, ok := cfg.([]byte) + if !ok { + // oh ... it's a string then? + var configStr string + + configStr, ok = cfg.(string) + configBytes = []byte(configStr) + } + if !ok { + // hmm ... can we marshal it to json? + var err error + + configBytes, err = json.Marshal(cfg) + ok = (err == nil) + } + if !ok { + // no ... we've tried hard enough at this point - throw an error! + return nil, ErrInvalidConfiguration{cfg: cfg} + } + + // OK unmarshal the byte array into a new copy of the exemplar + newVal := reflect.New(reflect.TypeOf(exemplar)) + if err := json.Unmarshal(configBytes, newVal.Interface()); err != nil { + // If we can't unmarshal it then return an error! + return nil, ErrInvalidConfiguration{cfg: cfg, err: err} + } + return newVal.Elem().Interface(), nil +} diff --git a/modules/storage/local.go b/modules/storage/local.go index 4c830211d96c..93af13ee33a2 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -5,11 +5,13 @@ package storage import ( + "context" "io" "net/url" "os" "path/filepath" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" ) @@ -17,24 +19,41 @@ var ( _ ObjectStorage = &LocalStorage{} ) +// LocalStorageType is the type descriptor for local storage +const LocalStorageType Type = "local" + +// LocalStorageConfig represents the configuration for a local storage +type LocalStorageConfig struct { + Path string `ini:"PATH"` +} + // LocalStorage represents a local files storage type LocalStorage struct { + ctx context.Context dir string } // NewLocalStorage returns a local files -func NewLocalStorage(bucket string) (*LocalStorage, error) { - if err := os.MkdirAll(bucket, os.ModePerm); err != nil { +func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error) { + configInterface, err := toConfig(LocalStorageConfig{}, cfg) + if err != nil { + return nil, err + } + config := configInterface.(LocalStorageConfig) + + log.Info("Creating new Local Storage at %s", config.Path) + if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { return nil, err } return &LocalStorage{ - dir: bucket, + ctx: ctx, + dir: config.Path, }, nil } // Open a file -func (l *LocalStorage) Open(path string) (io.ReadCloser, error) { +func (l *LocalStorage) Open(path string) (Object, error) { return os.Open(filepath.Join(l.dir, path)) } @@ -58,6 +77,11 @@ func (l *LocalStorage) Save(path string, r io.Reader) (int64, error) { return io.Copy(f, r) } +// Stat returns the info of the file +func (l *LocalStorage) Stat(path string) (os.FileInfo, error) { + return os.Stat(filepath.Join(l.dir, path)) +} + // Delete delete a file func (l *LocalStorage) Delete(path string) error { p := filepath.Join(l.dir, path) @@ -68,3 +92,37 @@ func (l *LocalStorage) Delete(path string) error { func (l *LocalStorage) URL(path, name string) (*url.URL, error) { return nil, ErrURLNotSupported } + +// IterateObjects iterates across the objects in the local storage +func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error { + return filepath.Walk(l.dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + select { + case <-l.ctx.Done(): + return l.ctx.Err() + default: + } + if path == l.dir { + return nil + } + if info.IsDir() { + return nil + } + relPath, err := filepath.Rel(l.dir, path) + if err != nil { + return err + } + obj, err := os.Open(path) + if err != nil { + return err + } + defer obj.Close() + return fn(relPath, obj) + }) +} + +func init() { + RegisterStorageType(LocalStorageType, NewLocalStorage) +} diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 77d24e6b732d..0e0cb3690da6 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -8,19 +8,50 @@ import ( "context" "io" "net/url" + "os" "path" "strings" "time" + "code.gitea.io/gitea/modules/log" + "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) var ( - _ ObjectStorage = &MinioStorage{} - quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + _ ObjectStorage = &MinioStorage{} + + quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") ) +type minioObject struct { + *minio.Object +} + +func (m *minioObject) Stat() (os.FileInfo, error) { + oi, err := m.Object.Stat() + if err != nil { + return nil, convertMinioErr(err) + } + + return &minioFileInfo{oi}, nil +} + +// MinioStorageType is the type descriptor for minio storage +const MinioStorageType Type = "minio" + +// MinioStorageConfig represents the configuration for a minio storage +type MinioStorageConfig struct { + Endpoint string `ini:"MINIO_ENDPOINT"` + AccessKeyID string `ini:"MINIO_ACCESS_KEY_ID"` + SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY"` + Bucket string `ini:"MINIO_BUCKET"` + Location string `ini:"MINIO_LOCATION"` + BasePath string `ini:"MINIO_BASE_PATH"` + UseSSL bool `ini:"MINIO_USE_SSL"` +} + // MinioStorage returns a minio bucket storage type MinioStorage struct { ctx context.Context @@ -29,31 +60,59 @@ type MinioStorage struct { basePath string } +func convertMinioErr(err error) error { + if err == nil { + return nil + } + errResp, ok := err.(minio.ErrorResponse) + if !ok { + return err + } + + // Convert two responses to standard analogues + switch errResp.Code { + case "NoSuchKey": + return os.ErrNotExist + case "AccessDenied": + return os.ErrPermission + } + + return err +} + // NewMinioStorage returns a minio storage -func NewMinioStorage(ctx context.Context, endpoint, accessKeyID, secretAccessKey, bucket, location, basePath string, useSSL bool) (*MinioStorage, error) { - minioClient, err := minio.New(endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), - Secure: useSSL, +func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error) { + configInterface, err := toConfig(MinioStorageConfig{}, cfg) + if err != nil { + return nil, convertMinioErr(err) + } + config := configInterface.(MinioStorageConfig) + + log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath) + + minioClient, err := minio.New(config.Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""), + Secure: config.UseSSL, }) if err != nil { - return nil, err + return nil, convertMinioErr(err) } - if err := minioClient.MakeBucket(ctx, bucket, minio.MakeBucketOptions{ - Region: location, + if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{ + Region: config.Location, }); err != nil { // Check to see if we already own this bucket (which happens if you run this twice) - exists, errBucketExists := minioClient.BucketExists(ctx, bucket) + exists, errBucketExists := minioClient.BucketExists(ctx, config.Bucket) if !exists || errBucketExists != nil { - return nil, err + return nil, convertMinioErr(err) } } return &MinioStorage{ ctx: ctx, client: minioClient, - bucket: bucket, - basePath: basePath, + bucket: config.Bucket, + basePath: config.BasePath, }, nil } @@ -62,13 +121,13 @@ func (m *MinioStorage) buildMinioPath(p string) string { } // Open open a file -func (m *MinioStorage) Open(path string) (io.ReadCloser, error) { +func (m *MinioStorage) Open(path string) (Object, error) { var opts = minio.GetObjectOptions{} object, err := m.client.GetObject(m.ctx, m.bucket, m.buildMinioPath(path), opts) if err != nil { - return nil, err + return nil, convertMinioErr(err) } - return object, nil + return &minioObject{object}, nil } // Save save a file to minio @@ -82,14 +141,58 @@ func (m *MinioStorage) Save(path string, r io.Reader) (int64, error) { minio.PutObjectOptions{ContentType: "application/octet-stream"}, ) if err != nil { - return 0, err + return 0, convertMinioErr(err) } return uploadInfo.Size, nil } +type minioFileInfo struct { + minio.ObjectInfo +} + +func (m minioFileInfo) Name() string { + return m.ObjectInfo.Key +} + +func (m minioFileInfo) Size() int64 { + return m.ObjectInfo.Size +} + +func (m minioFileInfo) ModTime() time.Time { + return m.LastModified +} + +func (m minioFileInfo) IsDir() bool { + return strings.HasSuffix(m.ObjectInfo.Key, "/") +} + +func (m minioFileInfo) Mode() os.FileMode { + return os.ModePerm +} + +func (m minioFileInfo) Sys() interface{} { + return nil +} + +// Stat returns the stat information of the object +func (m *MinioStorage) Stat(path string) (os.FileInfo, error) { + info, err := m.client.StatObject( + m.ctx, + m.bucket, + m.buildMinioPath(path), + minio.StatObjectOptions{}, + ) + if err != nil { + return nil, convertMinioErr(err) + } + return &minioFileInfo{info}, nil +} + // Delete delete a file func (m *MinioStorage) Delete(path string) error { - return m.client.RemoveObject(m.ctx, m.bucket, m.buildMinioPath(path), minio.RemoveObjectOptions{}) + err := m.client.RemoveObject(m.ctx, m.bucket, m.buildMinioPath(path), minio.RemoveObjectOptions{}) + + return convertMinioErr(err) } // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. @@ -97,5 +200,33 @@ func (m *MinioStorage) URL(path, name string) (*url.URL, error) { reqParams := make(url.Values) // TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we? reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"") - return m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams) + u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams) + return u, convertMinioErr(err) +} + +// IterateObjects iterates across the objects in the miniostorage +func (m *MinioStorage) IterateObjects(fn func(path string, obj Object) error) error { + var opts = minio.GetObjectOptions{} + lobjectCtx, cancel := context.WithCancel(m.ctx) + defer cancel() + for mObjInfo := range m.client.ListObjects(lobjectCtx, m.bucket, minio.ListObjectsOptions{ + Prefix: m.basePath, + Recursive: true, + }) { + object, err := m.client.GetObject(lobjectCtx, m.bucket, mObjInfo.Key, opts) + if err != nil { + return convertMinioErr(err) + } + if err := func(object *minio.Object, fn func(path string, obj Object) error) error { + defer object.Close() + return fn(strings.TrimPrefix(m.basePath, mObjInfo.Key), &minioObject{object}) + }(object, fn); err != nil { + return convertMinioErr(err) + } + } + return nil +} + +func init() { + RegisterStorageType(MinioStorageType, NewMinioStorage) } diff --git a/modules/storage/storage.go b/modules/storage/storage.go index 8528ebc5cb31..45582f3915c0 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -10,21 +10,66 @@ import ( "fmt" "io" "net/url" + "os" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) var ( // ErrURLNotSupported represents url is not supported ErrURLNotSupported = errors.New("url method not supported") + // ErrIterateObjectsNotSupported represents IterateObjects not supported + ErrIterateObjectsNotSupported = errors.New("iterateObjects method not supported") ) +// ErrInvalidConfiguration is called when there is invalid configuration for a storage +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 Storage +type Type string + +// NewStorageFunc is a function that creates a storage +type NewStorageFunc func(ctx context.Context, cfg interface{}) (ObjectStorage, error) + +var storageMap = map[Type]NewStorageFunc{} + +// RegisterStorageType registers a provided storage type with a function to create it +func RegisterStorageType(typ Type, fn func(ctx context.Context, cfg interface{}) (ObjectStorage, error)) { + storageMap[typ] = fn +} + +// Object represents the object on the storage +type Object interface { + io.ReadCloser + io.Seeker + Stat() (os.FileInfo, error) +} + // ObjectStorage represents an object storage to handle a bucket and files type ObjectStorage interface { + Open(path string) (Object, error) Save(path string, r io.Reader) (int64, error) - Open(path string) (io.ReadCloser, error) + Stat(path string) (os.FileInfo, error) Delete(path string) error URL(path, name string) (*url.URL, error) + IterateObjects(func(path string, obj Object) error) error } // Copy copys a file from source ObjectStorage to dest ObjectStorage @@ -38,36 +83,84 @@ func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, sr return dstStorage.Save(dstPath, f) } +// SaveFrom saves data to the ObjectStorage with path p from the callback +func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) error) error { + pr, pw := io.Pipe() + defer pr.Close() + go func() { + defer pw.Close() + if err := callback(pw); err != nil { + _ = pw.CloseWithError(err) + } + }() + + _, err := objStorage.Save(p, pr) + return err +} + var ( // Attachments represents attachments storage Attachments ObjectStorage + + // LFS represents lfs storage + LFS ObjectStorage + + // Avatars represents user avatars storage + Avatars ObjectStorage + // RepoAvatars represents repository avatars storage + RepoAvatars ObjectStorage ) // Init init the stoarge func Init() error { - var err error - switch setting.Attachment.StoreType { - case "local": - Attachments, err = NewLocalStorage(setting.Attachment.Path) - case "minio": - minio := setting.Attachment.Minio - Attachments, err = NewMinioStorage( - context.Background(), - minio.Endpoint, - minio.AccessKeyID, - minio.SecretAccessKey, - minio.Bucket, - minio.Location, - minio.BasePath, - minio.UseSSL, - ) - default: - return fmt.Errorf("Unsupported attachment store type: %s", setting.Attachment.StoreType) + if err := initAttachments(); err != nil { + return err } - if err != nil { + if err := initAvatars(); err != nil { + return err + } + + if err := initRepoAvatars(); err != nil { return err } - return nil + return initLFS() +} + +// NewStorage takes a storage type and some config and returns an ObjectStorage or an error +func NewStorage(typStr string, cfg interface{}) (ObjectStorage, error) { + if len(typStr) == 0 { + typStr = string(LocalStorageType) + } + fn, ok := storageMap[Type(typStr)] + if !ok { + return nil, fmt.Errorf("Unsupported storage type: %s", typStr) + } + + return fn(context.Background(), cfg) +} + +func initAvatars() (err error) { + log.Info("Initialising Avatar storage with type: %s", setting.Avatar.Storage.Type) + Avatars, err = NewStorage(setting.Avatar.Storage.Type, setting.Avatar.Storage) + return +} + +func initAttachments() (err error) { + log.Info("Initialising Attachment storage with type: %s", setting.Attachment.Storage.Type) + Attachments, err = NewStorage(setting.Attachment.Storage.Type, setting.Attachment.Storage) + return +} + +func initLFS() (err error) { + log.Info("Initialising LFS storage with type: %s", setting.LFS.Storage.Type) + LFS, err = NewStorage(setting.LFS.Storage.Type, setting.LFS.Storage) + return +} + +func initRepoAvatars() (err error) { + log.Info("Initialising Repository Avatar storage with type: %s", setting.RepoAvatar.Storage.Type) + RepoAvatars, err = NewStorage(setting.RepoAvatar.Storage.Type, setting.RepoAvatar.Storage) + return } diff --git a/modules/structs/cron.go b/modules/structs/cron.go new file mode 100644 index 000000000000..f52a5ed3c90a --- /dev/null +++ b/modules/structs/cron.go @@ -0,0 +1,16 @@ +// 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 structs + +import "time" + +// Cron represents a Cron task +type Cron struct { + Name string `json:"name"` + Schedule string `json:"schedule"` + Next time.Time `json:"next"` + Prev time.Time `json:"prev"` + ExecTimes int64 `json:"exec_times"` +} diff --git a/modules/structs/hook.go b/modules/structs/hook.go index a10dd4281c6c..45ae94f98521 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -338,7 +338,7 @@ func ParsePushHook(raw []byte) (*PushPayload, error) { // Branch returns branch name from a payload func (p *PushPayload) Branch() string { - return strings.Replace(p.Ref, "refs/heads/", "", -1) + return strings.ReplaceAll(p.Ref, "refs/heads/", "") } // .___ diff --git a/modules/structs/issue.go b/modules/structs/issue.go index dc633dedce1f..54b0f31d8a5b 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -5,6 +5,7 @@ package structs import ( + "strings" "time" ) @@ -119,3 +120,19 @@ type IssueDeadline struct { // swagger:strfmt date-time Deadline *time.Time `json:"due_date"` } + +// IssueTemplate represents an issue template for a repository +// swagger:model +type IssueTemplate struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + About string `json:"about" yaml:"about"` + Labels []string `json:"labels" yaml:"labels"` + Content string `json:"content" yaml:"-"` + FileName string `json:"file_name" yaml:"-"` +} + +// Valid checks whether an IssueTemplate is considered valid, e.g. at least name and about +func (it IssueTemplate) Valid() bool { + return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != "" +} diff --git a/modules/structs/issue_milestone.go b/modules/structs/issue_milestone.go index ec940c26049d..ace783ebbcfe 100644 --- a/modules/structs/issue_milestone.go +++ b/modules/structs/issue_milestone.go @@ -17,6 +17,10 @@ type Milestone struct { OpenIssues int `json:"open_issues"` ClosedIssues int `json:"closed_issues"` // swagger:strfmt date-time + Created time.Time `json:"created_at"` + // swagger:strfmt date-time + Updated *time.Time `json:"updated_at"` + // swagger:strfmt date-time Closed *time.Time `json:"closed_at"` // swagger:strfmt date-time Deadline *time.Time `json:"due_on"` diff --git a/modules/structs/issue_stopwatch.go b/modules/structs/issue_stopwatch.go index 10510e36efbd..8599e072731f 100644 --- a/modules/structs/issue_stopwatch.go +++ b/modules/structs/issue_stopwatch.go @@ -11,8 +11,11 @@ import ( // StopWatch represent a running stopwatch type StopWatch struct { // swagger:strfmt date-time - Created time.Time `json:"created"` - IssueIndex int64 `json:"issue_index"` + Created time.Time `json:"created"` + IssueIndex int64 `json:"issue_index"` + IssueTitle string `json:"issue_title"` + RepoOwnerName string `json:"repo_owner_name"` + RepoName string `json:"repo_name"` } // StopWatches represent a list of stopwatches diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index b6c9774a97ad..8daa6de1686f 100644 --- a/modules/structs/notifications.go +++ b/modules/structs/notifications.go @@ -21,10 +21,11 @@ type NotificationThread struct { // NotificationSubject contains the notification subject (Issue/Pull/Commit) type NotificationSubject struct { - Title string `json:"title"` - URL string `json:"url"` - LatestCommentURL string `json:"latest_comment_url"` - Type string `json:"type" binding:"In(Issue,Pull,Commit)"` + Title string `json:"title"` + URL string `json:"url"` + LatestCommentURL string `json:"latest_comment_url"` + Type string `json:"type" binding:"In(Issue,Pull,Commit)"` + State StateType `json:"state"` } // NotificationCount number of unread notifications diff --git a/modules/structs/pull_review.go b/modules/structs/pull_review.go index bf9eafc24364..07fa968d2877 100644 --- a/modules/structs/pull_review.go +++ b/modules/structs/pull_review.go @@ -30,6 +30,7 @@ const ( type PullReview struct { ID int64 `json:"id"` Reviewer *User `json:"user"` + ReviewerTeam *Team `json:"team"` State ReviewStateType `json:"state"` Body string `json:"body"` CommitID string `json:"commit_id"` @@ -90,3 +91,9 @@ type SubmitPullReviewOptions struct { Event ReviewStateType `json:"event"` Body string `json:"body"` } + +// PullReviewRequestOptions are options to add or remove pull review requests +type PullReviewRequestOptions struct { + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` +} diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6668c482b9ee..4410ca6c2286 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -5,6 +5,7 @@ package structs import ( + "strings" "time" ) @@ -108,6 +109,8 @@ type CreateRepoOption struct { IssueLabels string `json:"issue_labels"` // Whether the repository should be auto-intialized? AutoInit bool `json:"auto_init"` + // Whether the repository is template + Template bool `json:"template"` // Gitignores to use Gitignores string `json:"gitignores"` // License to use @@ -116,6 +119,9 @@ type CreateRepoOption struct { Readme string `json:"readme"` // DefaultBranch of the repository (used when initializes and in template) DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"` + // TrustModel of the repository + // enum: default,collaborator,committer,collaboratorcommitter + TrustModel string `json:"trust_model"` } // EditRepoOption options when editing a repository's properties @@ -209,49 +215,70 @@ const ( // Name represents the service type's name // WARNNING: the name have to be equal to that on goth's library func (gt GitServiceType) Name() string { + return strings.ToLower(gt.Title()) +} + +// Title represents the service type's proper title +func (gt GitServiceType) Title() string { switch gt { case GithubService: - return "github" + return "GitHub" case GiteaService: - return "gitea" + return "Gitea" case GitlabService: - return "gitlab" + return "GitLab" case GogsService: - return "gogs" + return "Gogs" + case PlainGitService: + return "Git" } return "" } +// MigrateRepoOptions options for migrating repository's +// this is used to interact with api v1 +type MigrateRepoOptions struct { + // required: true + CloneAddr string `json:"clone_addr" binding:"Required"` + // deprecated (only for backwards compatibility) + RepoOwnerID int64 `json:"uid"` + // Name of User or Organisation who will own Repo after migration + RepoOwner string `json:"repo_owner"` + // required: true + RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + + // enum: git,github,gitea,gitlab + Service string `json:"service"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` + + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description" binding:"MaxSize(255)"` + Wiki bool `json:"wiki"` + Milestones bool `json:"milestones"` + Labels bool `json:"labels"` + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` +} + +// TokenAuth represents whether a service type supports token-based auth +func (gt GitServiceType) TokenAuth() bool { + switch gt { + case GithubService, GiteaService, GitlabService: + return true + } + return false +} + var ( // SupportedFullGitService represents all git services supported to migrate issues/labels/prs and etc. // TODO: add to this list after new git service added SupportedFullGitService = []GitServiceType{ GithubService, GitlabService, + GiteaService, } ) - -// MigrateRepoOption options for migrating a repository from an external service -type MigrateRepoOption struct { - // required: true - CloneAddr string `json:"clone_addr" binding:"Required"` - AuthUsername string `json:"auth_username"` - AuthPassword string `json:"auth_password"` - // required: true - UID int `json:"uid" binding:"Required"` - // required: true - RepoName string `json:"repo_name" binding:"Required"` - Mirror bool `json:"mirror"` - Private bool `json:"private"` - Description string `json:"description"` - OriginalURL string - GitServiceType GitServiceType - Wiki bool - Issues bool - Milestones bool - Labels bool - Releases bool - Comments bool - PullRequests bool - MigrateToRepoID int64 -} diff --git a/modules/structs/repo_commit.go b/modules/structs/repo_commit.go index 088ccdf5afad..b9607b185bd6 100644 --- a/modules/structs/repo_commit.go +++ b/modules/structs/repo_commit.go @@ -20,6 +20,8 @@ type Identity struct { type CommitMeta struct { URL string `json:"url"` SHA string `json:"sha"` + // swagger:strfmt date-time + Created time.Time `json:"created"` } // CommitUser contains information of a user in the context of a commit. diff --git a/modules/structs/settings.go b/modules/structs/settings.go index 4a6e0ce5a84b..6874d6705bf4 100644 --- a/modules/structs/settings.go +++ b/modules/structs/settings.go @@ -14,3 +14,19 @@ type GeneralRepoSettings struct { type GeneralUISettings struct { AllowedReactions []string `json:"allowed_reactions"` } + +// GeneralAPISettings contains global api settings exposed by it +type GeneralAPISettings struct { + MaxResponseItems int `json:"max_response_items"` + DefaultPagingNum int `json:"default_paging_num"` + DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` + DefaultMaxBlobSize int64 `json:"default_max_blob_size"` +} + +// GeneralAttachmentSettings contains global Attachment settings exposed by API +type GeneralAttachmentSettings struct { + Enabled bool `json:"enabled"` + AllowedTypes string `json:"allowed_types"` + MaxSize int64 `json:"max_size"` + MaxFiles int `json:"max_files"` +} diff --git a/modules/task/migrate.go b/modules/task/migrate.go index d3b4fa45f01a..d25decaa0011 100644 --- a/modules/task/migrate.go +++ b/modules/task/migrate.go @@ -5,7 +5,6 @@ package task import ( - "bytes" "errors" "fmt" "strings" @@ -14,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations" + migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -38,10 +38,8 @@ func handleCreateError(owner *models.User, err error, name string) error { func runMigrateTask(t *models.Task) (err error) { defer func() { if e := recover(); e != nil { - var buf bytes.Buffer - fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2)) - - err = errors.New(buf.String()) + err = fmt.Errorf("PANIC whilst trying to do migrate task: %v\nStacktrace: %v", err, log.Stack(2)) + log.Critical("PANIC during runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d]: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err) } if err == nil { @@ -51,14 +49,14 @@ func runMigrateTask(t *models.Task) (err error) { return } - log.Error("FinishMigrateTask failed: %s", err.Error()) + log.Error("FinishMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d] failed: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err) } t.EndTime = timeutil.TimeStampNow() t.Status = structs.TaskStatusFailed t.Errors = err.Error() if err := t.UpdateCols("status", "errors", "end_time"); err != nil { - log.Error("Task UpdateCols failed: %s", err.Error()) + log.Error("Task UpdateCols failed: %v", err) } if t.Repo != nil { @@ -89,7 +87,7 @@ func runMigrateTask(t *models.Task) (err error) { return err } - var opts *structs.MigrateRepoOption + var opts *migration.MigrateOptions opts, err = t.MigrateConfig() if err != nil { return err diff --git a/modules/templates/helper.go b/modules/templates/helper.go index f86287f10bef..63be27d98735 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -120,8 +120,9 @@ func NewFuncMap() []template.FuncMap { "DateFmtShort": func(t time.Time) string { return t.Format("Jan 02, 2006") }, - "SizeFmt": base.FileSize, - "List": List, + "SizeFmt": base.FileSize, + "CountFmt": base.FormatNumberSI, + "List": List, "SubStr": func(str string, start, length int) string { if len(str) == 0 { return "" @@ -468,13 +469,26 @@ func NewTextFuncMap() []texttmpl.FuncMap { var widthRe = regexp.MustCompile(`width="[0-9]+?"`) var heightRe = regexp.MustCompile(`height="[0-9]+?"`) -// SVG render icons -func SVG(icon string, size int) template.HTML { +// SVG render icons - arguments icon name (string), size (int), class (string) +func SVG(icon string, others ...interface{}) template.HTML { + size := 16 + if len(others) > 0 && others[0].(int) != 0 { + size = others[0].(int) + } + + class := "" + if len(others) > 1 && others[1].(string) != "" { + class = others[1].(string) + } + if svgStr, ok := svg.SVGs[icon]; ok { if size != 16 { svgStr = widthRe.ReplaceAllString(svgStr, fmt.Sprintf(`width="%d"`, size)) svgStr = heightRe.ReplaceAllString(svgStr, fmt.Sprintf(`height="%d"`, size)) } + if class != "" { + svgStr = strings.Replace(svgStr, `class="`, fmt.Sprintf(`class="%s `, class), 1) + } return template.HTML(svgStr) } return template.HTML("") @@ -684,7 +698,7 @@ func ActionContent2Commits(act Actioner) *repository.PushCommits { // DiffTypeToStr returns diff type name func DiffTypeToStr(diffType int) string { diffTypes := map[int]string{ - 1: "add", 2: "modify", 3: "del", 4: "rename", + 1: "add", 2: "modify", 3: "del", 4: "rename", 5: "copy", } return diffTypes[diffType] } diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index af47369ee1a7..874d7db196eb 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -117,6 +117,10 @@ func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) { b(rw) } +func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error { + return nil +} + type mockRender struct { http.ResponseWriter } diff --git a/modules/upload/filetype.go b/modules/upload/filetype.go deleted file mode 100644 index 2ab326d11690..000000000000 --- a/modules/upload/filetype.go +++ /dev/null @@ -1,46 +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 upload - -import ( - "fmt" - "net/http" - "strings" - - "code.gitea.io/gitea/modules/log" -) - -// ErrFileTypeForbidden not allowed file type error -type ErrFileTypeForbidden struct { - Type string -} - -// IsErrFileTypeForbidden checks if an error is a ErrFileTypeForbidden. -func IsErrFileTypeForbidden(err error) bool { - _, ok := err.(ErrFileTypeForbidden) - return ok -} - -func (err ErrFileTypeForbidden) Error() string { - return fmt.Sprintf("File type is not allowed: %s", err.Type) -} - -// VerifyAllowedContentType validates a file is allowed to be uploaded. -func VerifyAllowedContentType(buf []byte, allowedTypes []string) error { - fileType := http.DetectContentType(buf) - - for _, t := range allowedTypes { - t := strings.Trim(t, " ") - - if t == "*/*" || t == fileType || - // Allow directives after type, like 'text/plain; charset=utf-8' - strings.HasPrefix(fileType, t+";") { - return nil - } - } - - log.Info("Attachment with type %s blocked from upload", fileType) - return ErrFileTypeForbidden{Type: fileType} -} diff --git a/modules/upload/filetype_test.go b/modules/upload/filetype_test.go deleted file mode 100644 index f93a1c5cc3bc..000000000000 --- a/modules/upload/filetype_test.go +++ /dev/null @@ -1,47 +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 upload - -import ( - "bytes" - "compress/gzip" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUpload(t *testing.T) { - testContent := []byte(`This is a plain text file.`) - var b bytes.Buffer - w := gzip.NewWriter(&b) - w.Write(testContent) - w.Close() - - kases := []struct { - data []byte - allowedTypes []string - err error - }{ - { - data: testContent, - allowedTypes: []string{"text/plain"}, - err: nil, - }, - { - data: testContent, - allowedTypes: []string{"application/x-gzip"}, - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: b.Bytes(), - allowedTypes: []string{"application/x-gzip"}, - err: nil, - }, - } - - for _, kase := range kases { - assert.Equal(t, kase.err, VerifyAllowedContentType(kase.data, kase.allowedTypes)) - } -} diff --git a/modules/upload/upload.go b/modules/upload/upload.go new file mode 100644 index 000000000000..e430a5d8b3c2 --- /dev/null +++ b/modules/upload/upload.go @@ -0,0 +1,101 @@ +// 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 upload + +import ( + "net/http" + "path" + "regexp" + "strings" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +// ErrFileTypeForbidden not allowed file type error +type ErrFileTypeForbidden struct { + Type string +} + +// IsErrFileTypeForbidden checks if an error is a ErrFileTypeForbidden. +func IsErrFileTypeForbidden(err error) bool { + _, ok := err.(ErrFileTypeForbidden) + return ok +} + +func (err ErrFileTypeForbidden) Error() string { + return "This file extension or type is not allowed to be uploaded." +} + +var mimeTypeSuffixRe = regexp.MustCompile(`;.*$`) +var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`) + +// Verify validates whether a file is allowed to be uploaded. +func Verify(buf []byte, fileName string, allowedTypesStr string) error { + allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format + + allowedTypes := []string{} + for _, entry := range strings.Split(allowedTypesStr, ",") { + entry = strings.ToLower(strings.TrimSpace(entry)) + if entry != "" { + allowedTypes = append(allowedTypes, entry) + } + } + + if len(allowedTypes) == 0 { + return nil // everything is allowed + } + + fullMimeType := http.DetectContentType(buf) + mimeType := strings.TrimSpace(mimeTypeSuffixRe.ReplaceAllString(fullMimeType, "")) + extension := strings.ToLower(path.Ext(fileName)) + + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers + for _, allowEntry := range allowedTypes { + if allowEntry == "*/*" { + return nil // everything allowed + } else if strings.HasPrefix(allowEntry, ".") && allowEntry == extension { + return nil // extension is allowed + } else if mimeType == allowEntry { + return nil // mime type is allowed + } else if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) { + return nil // wildcard match, e.g. image/* + } + } + + log.Info("Attachment with type %s blocked from upload", fullMimeType) + return ErrFileTypeForbidden{Type: fullMimeType} +} + +// AddUploadContext renders template values for dropzone +func AddUploadContext(ctx *context.Context, uploadType string) { + if uploadType == "release" { + ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments" + ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove" + ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/releases/attachments" + ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Release.AllowedTypes, "|", ",") + ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles + ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize + } else if uploadType == "comment" { + ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments" + ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove" + if len(ctx.Params(":index")) > 0 { + ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + ctx.Params(":index") + "/attachments" + } else { + ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments" + } + ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",") + ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles + ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize + } else if uploadType == "repo" { + ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file" + ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove" + ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/upload-file" + ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Upload.AllowedTypes, "|", ",") + ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles + ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize + } +} diff --git a/modules/upload/upload_test.go b/modules/upload/upload_test.go new file mode 100644 index 000000000000..d258b04f774a --- /dev/null +++ b/modules/upload/upload_test.go @@ -0,0 +1,195 @@ +// 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 upload + +import ( + "bytes" + "compress/gzip" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpload(t *testing.T) { + testContent := []byte(`This is a plain text file.`) + var b bytes.Buffer + w := gzip.NewWriter(&b) + w.Write(testContent) + w.Close() + + kases := []struct { + data []byte + fileName string + allowedTypes string + err error + }{ + { + data: testContent, + fileName: "test.txt", + allowedTypes: "", + err: nil, + }, + { + data: testContent, + fileName: "dir/test.txt", + allowedTypes: "", + err: nil, + }, + { + data: testContent, + fileName: "../../../test.txt", + allowedTypes: "", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: ",", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "|", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "*/*", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "*/*,", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "*/*|", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "text/plain", + err: nil, + }, + { + data: testContent, + fileName: "dir/test.txt", + allowedTypes: "text/plain", + err: nil, + }, + { + data: testContent, + fileName: "/dir.txt/test.js", + allowedTypes: ".js", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: " text/plain ", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: ".txt", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: " .txt,.js", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: " .txt|.js", + err: nil, + }, + { + data: testContent, + fileName: "../../test.txt", + allowedTypes: " .txt|.js", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: " .txt ,.js ", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "text/plain, .txt", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "text/*", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "text/*,.js", + err: nil, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "text/**", + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: "application/x-gzip", + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: ".zip", + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: ".zip,.txtx", + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: testContent, + fileName: "test.txt", + allowedTypes: ".zip|.txtx", + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: b.Bytes(), + fileName: "test.txt", + allowedTypes: "application/x-gzip", + err: nil, + }, + } + + for _, kase := range kases { + assert.Equal(t, kase.err, Verify(kase.data, kase.fileName, kase.allowedTypes)) + } +} diff --git a/modules/util/sanitize.go b/modules/util/sanitize.go index b1c17b29cf59..a4f5479dfb74 100644 --- a/modules/util/sanitize.go +++ b/modules/util/sanitize.go @@ -28,7 +28,7 @@ func URLSanitizedError(err error, unsanitizedURL string) error { // SanitizeMessage sanitizes a message which may contains a sensitive URL func SanitizeMessage(message, unsanitizedURL string) string { sanitizedURL := SanitizeURLCredentials(unsanitizedURL, true) - return strings.Replace(message, unsanitizedURL, sanitizedURL, -1) + return strings.ReplaceAll(message, unsanitizedURL, sanitizedURL) } // SanitizeURLCredentials sanitizes a url, either removing user credentials diff --git a/modules/util/shellquote.go b/modules/util/shellquote.go new file mode 100644 index 000000000000..bde24a151772 --- /dev/null +++ b/modules/util/shellquote.go @@ -0,0 +1,100 @@ +// 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 util + +import "strings" + +// Bash has the definition of a metacharacter: +// * A character that, when unquoted, separates words. +// A metacharacter is one of: " \t\n|&;()<>" +// +// The following characters also have addition special meaning when unescaped: +// * ‘${[*?!"'`\’ +// +// Double Quotes preserve the literal value of all characters with then quotes +// excepting: ‘$’, ‘`’, ‘\’, and, when history expansion is enabled, ‘!’. +// The backslash retains its special meaning only when followed by one of the +// following characters: ‘$’, ‘`’, ‘"’, ‘\’, or newline. +// Backslashes preceding characters without a special meaning are left +// unmodified. A double quote may be quoted within double quotes by preceding +// it with a backslash. If enabled, history expansion will be performed unless +// an ‘!’ appearing in double quotes is escaped using a backslash. The +// backslash preceding the ‘!’ is not removed. +// +// -> This means that `!\n` cannot be safely expressed in `"`. +// +// Looking at the man page for Dash and ash the situation is similar. +// +// Now zsh requires that ‘}’, and ‘]’ are also enclosed in doublequotes or escaped +// +// Single quotes escape everything except a ‘'’ +// +// There's one other gotcha - ‘~’ at the start of a string needs to be expanded +// because people always expect that - of course if there is a special character before '/' +// this is not going to work + +const ( + tildePrefix = '~' + needsEscape = " \t\n|&;()<>${}[]*?!\"'`\\" + needsSingleQuote = "!\n" +) + +var doubleQuoteEscaper = strings.NewReplacer(`$`, `\$`, "`", "\\`", `"`, `\"`, `\`, `\\`) +var singleQuoteEscaper = strings.NewReplacer(`'`, `'\''`) +var singleQuoteCoalescer = strings.NewReplacer(`''\'`, `\'`, `\'''`, `\'`) + +// ShellEscape will escape the provided string. +// We can't just use go-shellquote here because our preferences for escaping differ from those in that we want: +// +// * If the string doesn't require any escaping just leave it as it is. +// * If the string requires any escaping prefer double quote escaping +// * If we have ! or newlines then we need to use single quote escaping +func ShellEscape(toEscape string) string { + if len(toEscape) == 0 { + return toEscape + } + + start := 0 + + if toEscape[0] == tildePrefix { + // We're in the forcibly non-escaped section... + idx := strings.IndexRune(toEscape, '/') + if idx < 0 { + idx = len(toEscape) + } else { + idx++ + } + if !strings.ContainsAny(toEscape[:idx], needsEscape) { + // We'll assume that they intend ~ expansion to occur + start = idx + } + } + + // Now for simplicity we'll look at the rest of the string + if !strings.ContainsAny(toEscape[start:], needsEscape) { + return toEscape + } + + // OK we have to do some escaping + sb := &strings.Builder{} + _, _ = sb.WriteString(toEscape[:start]) + + // Do we have any characters which absolutely need to be within single quotes - that is simply ! or \n? + if strings.ContainsAny(toEscape[start:], needsSingleQuote) { + // We need to single quote escape. + sb2 := &strings.Builder{} + _, _ = sb2.WriteRune('\'') + _, _ = singleQuoteEscaper.WriteString(sb2, toEscape[start:]) + _, _ = sb2.WriteRune('\'') + _, _ = singleQuoteCoalescer.WriteString(sb, sb2.String()) + return sb.String() + } + + // OK we can just use " just escape the things that need escaping + _, _ = sb.WriteRune('"') + _, _ = doubleQuoteEscaper.WriteString(sb, toEscape[start:]) + _, _ = sb.WriteRune('"') + return sb.String() +} diff --git a/modules/util/shellquote_test.go b/modules/util/shellquote_test.go new file mode 100644 index 000000000000..2ddc6d763d86 --- /dev/null +++ b/modules/util/shellquote_test.go @@ -0,0 +1,92 @@ +// 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 util + +import "testing" + +func TestShellEscape(t *testing.T) { + tests := []struct { + name string + toEscape string + want string + }{ + { + "Simplest case - nothing to escape", + "a/b/c/d", + "a/b/c/d", + }, { + "Prefixed tilde - with normal stuff - should not escape", + "~/src/go/gitea/gitea", + "~/src/go/gitea/gitea", + }, { + "Typical windows path with spaces - should get doublequote escaped", + `C:\Program Files\Gitea v1.13 - I like lots of spaces\gitea`, + `"C:\\Program Files\\Gitea v1.13 - I like lots of spaces\\gitea"`, + }, { + "Forward-slashed windows path with spaces - should get doublequote escaped", + "C:/Program Files/Gitea v1.13 - I like lots of spaces/gitea", + `"C:/Program Files/Gitea v1.13 - I like lots of spaces/gitea"`, + }, { + "Prefixed tilde - but then a space filled path", + "~git/Gitea v1.13/gitea", + `~git/"Gitea v1.13/gitea"`, + }, { + "Bangs are unforutunately not predictable so need to be singlequoted", + "C:/Program Files/Gitea!/gitea", + `'C:/Program Files/Gitea!/gitea'`, + }, { + "Newlines are just irritating", + "/home/git/Gitea\n\nWHY-WOULD-YOU-DO-THIS\n\nGitea/gitea", + "'/home/git/Gitea\n\nWHY-WOULD-YOU-DO-THIS\n\nGitea/gitea'", + }, { + "Similarly we should nicely handle mutiple single quotes if we have to single-quote", + "'!''!'''!''!'!'", + `\''!'\'\''!'\'\'\''!'\'\''!'\''!'\'`, + }, { + "Double quote < ...", + "~/ ...", + "~/gitea>", + "~/\"gitea>\"", + }, { + "Double quote and escape $ ...", + "~/$gitea", + "~/\"\\$gitea\"", + }, { + "Double quote {...", + "~/{gitea", + "~/\"{gitea\"", + }, { + "Double quote }...", + "~/gitea}", + "~/\"gitea}\"", + }, { + "Double quote ()...", + "~/(gitea)", + "~/\"(gitea)\"", + }, { + "Double quote and escape `...", + "~/gitea`", + "~/\"gitea\\`\"", + }, { + "Double quotes can handle a number of things without having to escape them but not everything ...", + "~/ ${gitea} `gitea` [gitea] (gitea) \"gitea\" \\gitea\\ 'gitea'", + "~/\" \\${gitea} \\`gitea\\` [gitea] (gitea) \\\"gitea\\\" \\\\gitea\\\\ 'gitea'\"", + }, { + "Single quotes don't need to escape except for '...", + "~/ ${gitea} `gitea` (gitea) !gitea! \"gitea\" \\gitea\\ 'gitea'", + "~/' ${gitea} `gitea` (gitea) !gitea! \"gitea\" \\gitea\\ '\\''gitea'\\'", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ShellEscape(tt.toEscape); got != tt.want { + t.Errorf("ShellEscape(%q):\nGot: %s\nWanted: %s", tt.toEscape, got, tt.want) + } + }) + } +} diff --git a/modules/webhook/dingtalk.go b/modules/webhook/dingtalk.go index 4e0e52451abb..a9032db046fe 100644 --- a/modules/webhook/dingtalk.go +++ b/modules/webhook/dingtalk.go @@ -21,19 +21,24 @@ type ( DingtalkPayload dingtalk.Payload ) +var ( + _ PayloadConvertor = &DingtalkPayload{} +) + // SetSecret sets the dingtalk secret -func (p *DingtalkPayload) SetSecret(_ string) {} +func (d *DingtalkPayload) SetSecret(_ string) {} // JSONPayload Marshals the DingtalkPayload to json -func (p *DingtalkPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (d *DingtalkPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(d, "", " ") if err != nil { return []byte{}, err } return data, nil } -func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) { +// Create implements PayloadConvertor Create method +func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -50,7 +55,8 @@ func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) { }, nil } -func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) { +// Delete implements PayloadConvertor Delete method +func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -67,7 +73,8 @@ func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) { }, nil } -func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) { +// Fork implements PayloadConvertor Fork method +func (d *DingtalkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return &DingtalkPayload{ @@ -82,7 +89,8 @@ func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) { }, nil } -func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { +// Push implements PayloadConvertor Push method +func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) { var ( branchName = git.RefEndName(p.Ref) commitDesc string @@ -131,7 +139,8 @@ func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) { }, nil } -func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) { +// Issue implements PayloadConvertor Issue method +func (d *DingtalkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ @@ -147,7 +156,8 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) { }, nil } -func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (d *DingtalkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ @@ -162,7 +172,8 @@ func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayloa }, nil } -func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (d *DingtalkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ @@ -178,7 +189,8 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, }, nil } -func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*DingtalkPayload, error) { +// Review implements PayloadConvertor Review method +func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { var text, title string switch p.Action { case api.HookIssueReviewed: @@ -204,7 +216,8 @@ func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event mode }, nil } -func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, error) { +// Repository implements PayloadConvertor Repository method +func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { var title, url string switch p.Action { case api.HookRepoCreated: @@ -235,7 +248,8 @@ func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, e return nil, nil } -func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) { +// Release implements PayloadConvertor Release method +func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return &DingtalkPayload{ @@ -251,36 +265,6 @@ func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error) } // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload -func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string) (*DingtalkPayload, error) { - s := new(DingtalkPayload) - - switch event { - case models.HookEventCreate: - return getDingtalkCreatePayload(p.(*api.CreatePayload)) - case models.HookEventDelete: - return getDingtalkDeletePayload(p.(*api.DeletePayload)) - case models.HookEventFork: - return getDingtalkForkPayload(p.(*api.ForkPayload)) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getDingtalkIssuesPayload(p.(*api.IssuePayload)) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getDingtalkIssueCommentPayload(pl) - } - return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPush: - return getDingtalkPushPayload(p.(*api.PushPayload)) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewComment: - return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) - case models.HookEventRepository: - return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload)) - case models.HookEventRelease: - return getDingtalkReleasePayload(p.(*api.ReleasePayload)) - } - - return s, nil +func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { + return convertPayloader(new(DingtalkPayload), p, event) } diff --git a/modules/webhook/dingtalk_test.go b/modules/webhook/dingtalk_test.go index 4cb7a913e6a8..e5aa0fca36ab 100644 --- a/modules/webhook/dingtalk_test.go +++ b/modules/webhook/dingtalk_test.go @@ -14,18 +14,18 @@ import ( func TestGetDingTalkIssuesPayload(t *testing.T) { p := issueTestPayload() - + d := new(DingtalkPayload) p.Action = api.HookIssueOpened - pl, err := getDingtalkIssuesPayload(p) + pl, err := d.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "#2 crash", pl.ActionCard.Title) - assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\n", pl.ActionCard.Text) + assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) + assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\n", pl.(*DingtalkPayload).ActionCard.Text) p.Action = api.HookIssueClosed - pl, err = getDingtalkIssuesPayload(p) + pl, err = d.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "#2 crash", pl.ActionCard.Title) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1\r\n\r\n", pl.ActionCard.Text) + assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) + assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1\r\n\r\n", pl.(*DingtalkPayload).ActionCard.Text) } diff --git a/modules/webhook/discord.go b/modules/webhook/discord.go index 761129d8d9e7..530e7adbda4d 100644 --- a/modules/webhook/discord.go +++ b/modules/webhook/discord.go @@ -97,25 +97,30 @@ var ( ) // SetSecret sets the discord secret -func (p *DiscordPayload) SetSecret(_ string) {} +func (d *DiscordPayload) SetSecret(_ string) {} // JSONPayload Marshals the DiscordPayload to json -func (p *DiscordPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (d *DiscordPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(d, "", " ") if err != nil { return []byte{}, err } return data, nil } -func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordPayload, error) { +var ( + _ PayloadConvertor = &DiscordPayload{} +) + +// Create implements PayloadConvertor Create method +func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -131,14 +136,15 @@ func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordP }, nil } -func getDiscordDeletePayload(p *api.DeletePayload, meta *DiscordMeta) (*DiscordPayload, error) { +// Delete implements PayloadConvertor Delete method +func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { // deleted tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -154,13 +160,13 @@ func getDiscordDeletePayload(p *api.DeletePayload, meta *DiscordMeta) (*DiscordP }, nil } -func getDiscordForkPayload(p *api.ForkPayload, meta *DiscordMeta) (*DiscordPayload, error) { - // fork +// Fork implements PayloadConvertor Fork method +func (d *DiscordPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -176,7 +182,8 @@ func getDiscordForkPayload(p *api.ForkPayload, meta *DiscordMeta) (*DiscordPaylo }, nil } -func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) { +// Push implements PayloadConvertor Push method +func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) { var ( branchName = git.RefEndName(p.Ref) commitDesc string @@ -208,8 +215,8 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo } return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -226,12 +233,13 @@ func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPaylo }, nil } -func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) { +// Issue implements PayloadConvertor Issue method +func (d *DiscordPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: text, @@ -248,12 +256,13 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa }, nil } -func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (d *DiscordPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ - Username: discord.Username, - AvatarURL: discord.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: text, @@ -270,12 +279,13 @@ func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordM }, nil } -func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (d *DiscordPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: text, @@ -292,7 +302,8 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) }, nil } -func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event models.HookEventType) (*DiscordPayload, error) { +// Review implements PayloadConvertor Review method +func (d *DiscordPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { var text, title string var color int switch p.Action { @@ -318,8 +329,8 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco } return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -336,7 +347,8 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco }, nil } -func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) { +// Repository implements PayloadConvertor Repository method +func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { var title, url string var color int switch p.Action { @@ -350,8 +362,8 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (* } return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: title, @@ -367,12 +379,13 @@ func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (* }, nil } -func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) { +// Release implements PayloadConvertor Release method +func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return &DiscordPayload{ - Username: meta.Username, - AvatarURL: meta.IconURL, + Username: d.Username, + AvatarURL: d.AvatarURL, Embeds: []DiscordEmbed{ { Title: text, @@ -390,47 +403,20 @@ func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*Discor } // GetDiscordPayload converts a discord webhook into a DiscordPayload -func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string) (*DiscordPayload, error) { +func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { s := new(DiscordPayload) discord := &DiscordMeta{} if err := json.Unmarshal([]byte(meta), &discord); err != nil { return s, errors.New("GetDiscordPayload meta json:" + err.Error()) } + s.Username = discord.Username + s.AvatarURL = discord.IconURL - switch event { - case models.HookEventCreate: - return getDiscordCreatePayload(p.(*api.CreatePayload), discord) - case models.HookEventDelete: - return getDiscordDeletePayload(p.(*api.DeletePayload), discord) - case models.HookEventFork: - return getDiscordForkPayload(p.(*api.ForkPayload), discord) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getDiscordIssuesPayload(p.(*api.IssuePayload), discord) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getDiscordIssueCommentPayload(pl, discord) - } - return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) - case models.HookEventPush: - return getDiscordPushPayload(p.(*api.PushPayload), discord) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord) - case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment: - return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event) - case models.HookEventRepository: - return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord) - case models.HookEventRelease: - return getDiscordReleasePayload(p.(*api.ReleasePayload), discord) - } - - return s, nil + return convertPayloader(s, p, event) } func parseHookPullRequestEventType(event models.HookEventType) (string, error) { - switch event { case models.HookEventPullRequestReviewApproved: diff --git a/modules/webhook/feishu.go b/modules/webhook/feishu.go index 4beda9014c54..8e60dbba1359 100644 --- a/modules/webhook/feishu.go +++ b/modules/webhook/feishu.go @@ -23,18 +23,23 @@ type ( ) // SetSecret sets the Feishu secret -func (p *FeishuPayload) SetSecret(_ string) {} +func (f *FeishuPayload) SetSecret(_ string) {} // JSONPayload Marshals the FeishuPayload to json -func (p *FeishuPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (f *FeishuPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(f, "", " ") if err != nil { return []byte{}, err } return data, nil } -func getFeishuCreatePayload(p *api.CreatePayload) (*FeishuPayload, error) { +var ( + _ PayloadConvertor = &FeishuPayload{} +) + +// Create implements PayloadConvertor Create method +func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -45,7 +50,8 @@ func getFeishuCreatePayload(p *api.CreatePayload) (*FeishuPayload, error) { }, nil } -func getFeishuDeletePayload(p *api.DeletePayload) (*FeishuPayload, error) { +// Delete implements PayloadConvertor Delete method +func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -56,7 +62,8 @@ func getFeishuDeletePayload(p *api.DeletePayload) (*FeishuPayload, error) { }, nil } -func getFeishuForkPayload(p *api.ForkPayload) (*FeishuPayload, error) { +// Fork implements PayloadConvertor Fork method +func (f *FeishuPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return &FeishuPayload{ @@ -65,7 +72,8 @@ func getFeishuForkPayload(p *api.ForkPayload) (*FeishuPayload, error) { }, nil } -func getFeishuPushPayload(p *api.PushPayload) (*FeishuPayload, error) { +// Push implements PayloadConvertor Push method +func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) { var ( branchName = git.RefEndName(p.Ref) commitDesc string @@ -94,7 +102,8 @@ func getFeishuPushPayload(p *api.PushPayload) (*FeishuPayload, error) { }, nil } -func getFeishuIssuesPayload(p *api.IssuePayload) (*FeishuPayload, error) { +// Issue implements PayloadConvertor Issue method +func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) return &FeishuPayload{ @@ -103,7 +112,8 @@ func getFeishuIssuesPayload(p *api.IssuePayload) (*FeishuPayload, error) { }, nil } -func getFeishuIssueCommentPayload(p *api.IssueCommentPayload) (*FeishuPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) return &FeishuPayload{ @@ -112,7 +122,8 @@ func getFeishuIssueCommentPayload(p *api.IssueCommentPayload) (*FeishuPayload, e }, nil } -func getFeishuPullRequestPayload(p *api.PullRequestPayload) (*FeishuPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) return &FeishuPayload{ @@ -121,7 +132,8 @@ func getFeishuPullRequestPayload(p *api.PullRequestPayload) (*FeishuPayload, err }, nil } -func getFeishuPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*FeishuPayload, error) { +// Review implements PayloadConvertor Review method +func (f *FeishuPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { var text, title string switch p.Action { case api.HookIssueSynchronized: @@ -141,7 +153,8 @@ func getFeishuPullRequestApprovalPayload(p *api.PullRequestPayload, event models }, nil } -func getFeishuRepositoryPayload(p *api.RepositoryPayload) (*FeishuPayload, error) { +// Repository implements PayloadConvertor Repository method +func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { var title string switch p.Action { case api.HookRepoCreated: @@ -161,7 +174,8 @@ func getFeishuRepositoryPayload(p *api.RepositoryPayload) (*FeishuPayload, error return nil, nil } -func getFeishuReleasePayload(p *api.ReleasePayload) (*FeishuPayload, error) { +// Release implements PayloadConvertor Release method +func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) return &FeishuPayload{ @@ -171,35 +185,6 @@ func getFeishuReleasePayload(p *api.ReleasePayload) (*FeishuPayload, error) { } // GetFeishuPayload converts a ding talk webhook into a FeishuPayload -func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string) (*FeishuPayload, error) { - s := new(FeishuPayload) - - switch event { - case models.HookEventCreate: - return getFeishuCreatePayload(p.(*api.CreatePayload)) - case models.HookEventDelete: - return getFeishuDeletePayload(p.(*api.DeletePayload)) - case models.HookEventFork: - return getFeishuForkPayload(p.(*api.ForkPayload)) - case models.HookEventIssues: - return getFeishuIssuesPayload(p.(*api.IssuePayload)) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getFeishuIssueCommentPayload(pl) - } - return getFeishuPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPush: - return getFeishuPushPayload(p.(*api.PushPayload)) - case models.HookEventPullRequest: - return getFeishuPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected: - return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) - case models.HookEventRepository: - return getFeishuRepositoryPayload(p.(*api.RepositoryPayload)) - case models.HookEventRelease: - return getFeishuReleasePayload(p.(*api.ReleasePayload)) - } - - return s, nil +func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { + return convertPayloader(new(FeishuPayload), p, event) } diff --git a/modules/webhook/matrix.go b/modules/webhook/matrix.go index d6309000a863..063147198ae6 100644 --- a/modules/webhook/matrix.go +++ b/modules/webhook/matrix.go @@ -51,14 +51,18 @@ type MatrixPayloadUnsafe struct { AccessToken string `json:"access_token"` } +var ( + _ PayloadConvertor = &MatrixPayloadUnsafe{} +) + // safePayload "converts" a unsafe payload to a safe payload -func (p *MatrixPayloadUnsafe) safePayload() *MatrixPayloadSafe { +func (m *MatrixPayloadUnsafe) safePayload() *MatrixPayloadSafe { return &MatrixPayloadSafe{ - Body: p.Body, - MsgType: p.MsgType, - Format: p.Format, - FormattedBody: p.FormattedBody, - Commits: p.Commits, + Body: m.Body, + MsgType: m.MsgType, + Format: m.Format, + FormattedBody: m.FormattedBody, + Commits: m.Commits, } } @@ -72,11 +76,11 @@ type MatrixPayloadSafe struct { } // SetSecret sets the Matrix secret -func (p *MatrixPayloadUnsafe) SetSecret(_ string) {} +func (m *MatrixPayloadUnsafe) SetSecret(_ string) {} // JSONPayload Marshals the MatrixPayloadUnsafe to json -func (p *MatrixPayloadUnsafe) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (m *MatrixPayloadUnsafe) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(m, "", " ") if err != nil { return []byte{}, err } @@ -101,51 +105,56 @@ func MatrixLinkToRef(repoURL, ref string) string { } } -func getMatrixCreatePayload(p *api.CreatePayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Create implements PayloadConvertor Create method +func (m *MatrixPayloadUnsafe) Create(p *api.CreatePayload) (api.Payloader, error) { repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) refLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -// getMatrixDeletePayload composes Matrix payload for delete a branch or tag. -func getMatrixDeletePayload(p *api.DeletePayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Delete composes Matrix payload for delete a branch or tag. +func (m *MatrixPayloadUnsafe) Delete(p *api.DeletePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -// getMatrixForkPayload composes Matrix payload for forked by a repository. -func getMatrixForkPayload(p *api.ForkPayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Fork composes Matrix payload for forked by a repository. +func (m *MatrixPayloadUnsafe) Fork(p *api.ForkPayload) (api.Payloader, error) { baseLink := MatrixLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) forkLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixIssuesPayload(p *api.IssuePayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Issue implements PayloadConvertor Issue method +func (m *MatrixPayloadUnsafe) Issue(p *api.IssuePayload) (api.Payloader, error) { text, _, _, _ := getIssuesPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixIssueCommentPayload(p *api.IssueCommentPayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (m *MatrixPayloadUnsafe) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, _, _ := getIssueCommentPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixReleasePayload(p *api.ReleasePayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Release implements PayloadConvertor Release method +func (m *MatrixPayloadUnsafe) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixPushPayload(p *api.PushPayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Push implements PayloadConvertor Push method +func (m *MatrixPayloadUnsafe) Push(p *api.PushPayload) (api.Payloader, error) { var commitDesc string if len(p.Commits) == 1 { @@ -168,16 +177,18 @@ func getMatrixPushPayload(p *api.PushPayload, matrix *MatrixMeta) (*MatrixPayloa } - return getMatrixPayloadUnsafe(text, p.Commits, matrix), nil + return getMatrixPayloadUnsafe(text, p.Commits, m.AccessToken, m.MsgType), nil } -func getMatrixPullRequestPayload(p *api.PullRequestPayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (m *MatrixPayloadUnsafe) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, _, _, _ := getPullRequestPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixPullRequestApprovalPayload(p *api.PullRequestPayload, matrix *MatrixMeta, event models.HookEventType) (*MatrixPayloadUnsafe, error) { +// Review implements PayloadConvertor Review method +func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) @@ -194,10 +205,11 @@ func getMatrixPullRequestApprovalPayload(p *api.PullRequestPayload, matrix *Matr text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink) } - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } -func getMatrixRepositoryPayload(p *api.RepositoryPayload, matrix *MatrixMeta) (*MatrixPayloadUnsafe, error) { +// Repository implements PayloadConvertor Repository method +func (m *MatrixPayloadUnsafe) Repository(p *api.RepositoryPayload) (api.Payloader, error) { senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string @@ -209,11 +221,11 @@ func getMatrixRepositoryPayload(p *api.RepositoryPayload, matrix *MatrixMeta) (* text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink) } - return getMatrixPayloadUnsafe(text, nil, matrix), nil + return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil } // GetMatrixPayload converts a Matrix webhook into a MatrixPayloadUnsafe -func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string) (*MatrixPayloadUnsafe, error) { +func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { s := new(MatrixPayloadUnsafe) matrix := &MatrixMeta{} @@ -221,44 +233,19 @@ func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string) return s, errors.New("GetMatrixPayload meta json:" + err.Error()) } - switch event { - case models.HookEventCreate: - return getMatrixCreatePayload(p.(*api.CreatePayload), matrix) - case models.HookEventDelete: - return getMatrixDeletePayload(p.(*api.DeletePayload), matrix) - case models.HookEventFork: - return getMatrixForkPayload(p.(*api.ForkPayload), matrix) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getMatrixIssuesPayload(p.(*api.IssuePayload), matrix) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getMatrixIssueCommentPayload(pl, matrix) - } - return getMatrixPullRequestPayload(p.(*api.PullRequestPayload), matrix) - case models.HookEventPush: - return getMatrixPushPayload(p.(*api.PushPayload), matrix) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getMatrixPullRequestPayload(p.(*api.PullRequestPayload), matrix) - case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment: - return getMatrixPullRequestApprovalPayload(p.(*api.PullRequestPayload), matrix, event) - case models.HookEventRepository: - return getMatrixRepositoryPayload(p.(*api.RepositoryPayload), matrix) - case models.HookEventRelease: - return getMatrixReleasePayload(p.(*api.ReleasePayload), matrix) - } + s.AccessToken = matrix.AccessToken + s.MsgType = messageTypeText[matrix.MessageType] - return s, nil + return convertPayloader(s, p, event) } -func getMatrixPayloadUnsafe(text string, commits []*api.PayloadCommit, matrix *MatrixMeta) *MatrixPayloadUnsafe { +func getMatrixPayloadUnsafe(text string, commits []*api.PayloadCommit, accessToken, msgType string) *MatrixPayloadUnsafe { p := MatrixPayloadUnsafe{} - p.AccessToken = matrix.AccessToken + p.AccessToken = accessToken p.FormattedBody = text p.Body = getMessageBody(text) p.Format = "org.matrix.custom.html" - p.MsgType = messageTypeText[matrix.MessageType] + p.MsgType = msgType p.Commits = commits return &p } diff --git a/modules/webhook/matrix_test.go b/modules/webhook/matrix_test.go index 3d1c660126c3..771146f2f30f 100644 --- a/modules/webhook/matrix_test.go +++ b/modules/webhook/matrix_test.go @@ -16,73 +16,69 @@ import ( func TestMatrixIssuesPayloadOpened(t *testing.T) { p := issueTestPayload() - sl := &MatrixMeta{} + m := new(MatrixPayloadUnsafe) p.Action = api.HookIssueOpened - pl, err := getMatrixIssuesPayload(p, sl) + pl, err := m.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) p.Action = api.HookIssueClosed - pl, err = getMatrixIssuesPayload(p, sl) + pl, err = m.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) } func TestMatrixIssueCommentPayload(t *testing.T) { p := issueCommentTestPayload() + m := new(MatrixPayloadUnsafe) - sl := &MatrixMeta{} - - pl, err := getMatrixIssueCommentPayload(p, sl) + pl, err := m.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) } func TestMatrixPullRequestCommentPayload(t *testing.T) { p := pullRequestCommentTestPayload() + m := new(MatrixPayloadUnsafe) - sl := &MatrixMeta{} - - pl, err := getMatrixIssueCommentPayload(p, sl) + pl, err := m.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#2 Fix bug](http://localhost:3000/test/repo/pulls/2) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] New comment on pull request #2 Fix bug by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#2 Fix bug](http://localhost:3000/test/repo/pulls/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] New comment on pull request #2 Fix bug by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) } func TestMatrixReleasePayload(t *testing.T) { p := pullReleaseTestPayload() + m := new(MatrixPayloadUnsafe) - sl := &MatrixMeta{} - - pl, err := getMatrixReleasePayload(p, sl) + pl, err := m.Release(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/src/v1.0) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/src/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) } func TestMatrixPullRequestPayload(t *testing.T) { p := pullRequestTestPayload() + m := new(MatrixPayloadUnsafe) - sl := &MatrixMeta{} - - pl, err := getMatrixPullRequestPayload(p, sl) + pl, err := m.PullRequest(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#2 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.Body) - assert.Equal(t, "[test/repo] Pull request opened: #2 Fix bug by user1", pl.FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#2 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) + assert.Equal(t, "[test/repo] Pull request opened: #2 Fix bug by user1", pl.(*MatrixPayloadUnsafe).FormattedBody) } func TestMatrixHookRequest(t *testing.T) { diff --git a/modules/webhook/msteams.go b/modules/webhook/msteams.go index e7ec396f2941..a68c97ea37ce 100644 --- a/modules/webhook/msteams.go +++ b/modules/webhook/msteams.go @@ -56,18 +56,23 @@ type ( ) // SetSecret sets the MSTeams secret -func (p *MSTeamsPayload) SetSecret(_ string) {} +func (m *MSTeamsPayload) SetSecret(_ string) {} // JSONPayload Marshals the MSTeamsPayload to json -func (p *MSTeamsPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (m *MSTeamsPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(m, "", " ") if err != nil { return []byte{}, err } return data, nil } -func getMSTeamsCreatePayload(p *api.CreatePayload) (*MSTeamsPayload, error) { +var ( + _ PayloadConvertor = &MSTeamsPayload{} +) + +// Create implements PayloadConvertor Create method +func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) @@ -110,7 +115,8 @@ func getMSTeamsCreatePayload(p *api.CreatePayload) (*MSTeamsPayload, error) { }, nil } -func getMSTeamsDeletePayload(p *api.DeletePayload) (*MSTeamsPayload, error) { +// Delete implements PayloadConvertor Delete method +func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { // deleted tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) @@ -153,8 +159,8 @@ func getMSTeamsDeletePayload(p *api.DeletePayload) (*MSTeamsPayload, error) { }, nil } -func getMSTeamsForkPayload(p *api.ForkPayload) (*MSTeamsPayload, error) { - // fork +// Fork implements PayloadConvertor Fork method +func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) return &MSTeamsPayload{ @@ -195,7 +201,8 @@ func getMSTeamsForkPayload(p *api.ForkPayload) (*MSTeamsPayload, error) { }, nil } -func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { +// Push implements PayloadConvertor Push method +func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) { var ( branchName = git.RefEndName(p.Ref) commitDesc string @@ -222,7 +229,7 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { - text += "\n" + text += "\n\n" } } @@ -265,7 +272,8 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { }, nil } -func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) { +// Issue implements PayloadConvertor Issue method +func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ @@ -307,7 +315,8 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) { }, nil } -func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ @@ -349,7 +358,8 @@ func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, }, nil } -func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ @@ -391,7 +401,8 @@ func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, e }, nil } -func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*MSTeamsPayload, error) { +// Review implements PayloadConvertor Review method +func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { var text, title string var color int switch p.Action { @@ -455,7 +466,8 @@ func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event model }, nil } -func getMSTeamsRepositoryPayload(p *api.RepositoryPayload) (*MSTeamsPayload, error) { +// Repository implements PayloadConvertor Repository method +func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { var title, url string var color int switch p.Action { @@ -502,7 +514,8 @@ func getMSTeamsRepositoryPayload(p *api.RepositoryPayload) (*MSTeamsPayload, err }, nil } -func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) { +// Release implements PayloadConvertor Release method +func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) return &MSTeamsPayload{ @@ -545,36 +558,6 @@ func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) { } // GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload -func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (*MSTeamsPayload, error) { - s := new(MSTeamsPayload) - - switch event { - case models.HookEventCreate: - return getMSTeamsCreatePayload(p.(*api.CreatePayload)) - case models.HookEventDelete: - return getMSTeamsDeletePayload(p.(*api.DeletePayload)) - case models.HookEventFork: - return getMSTeamsForkPayload(p.(*api.ForkPayload)) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getMSTeamsIssuesPayload(p.(*api.IssuePayload)) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getMSTeamsIssueCommentPayload(pl) - } - return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPush: - return getMSTeamsPushPayload(p.(*api.PushPayload)) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment: - return getMSTeamsPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) - case models.HookEventRepository: - return getMSTeamsRepositoryPayload(p.(*api.RepositoryPayload)) - case models.HookEventRelease: - return getMSTeamsReleasePayload(p.(*api.ReleasePayload)) - } - - return s, nil +func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { + return convertPayloader(new(MSTeamsPayload), p, event) } diff --git a/modules/webhook/payloader.go b/modules/webhook/payloader.go new file mode 100644 index 000000000000..f1cdaf65956d --- /dev/null +++ b/modules/webhook/payloader.go @@ -0,0 +1,56 @@ +// 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 webhook + +import ( + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" +) + +// PayloadConvertor defines the interface to convert system webhook payload to external payload +type PayloadConvertor interface { + api.Payloader + Create(*api.CreatePayload) (api.Payloader, error) + Delete(*api.DeletePayload) (api.Payloader, error) + Fork(*api.ForkPayload) (api.Payloader, error) + Issue(*api.IssuePayload) (api.Payloader, error) + IssueComment(*api.IssueCommentPayload) (api.Payloader, error) + Push(*api.PushPayload) (api.Payloader, error) + PullRequest(*api.PullRequestPayload) (api.Payloader, error) + Review(*api.PullRequestPayload, models.HookEventType) (api.Payloader, error) + Repository(*api.RepositoryPayload) (api.Payloader, error) + Release(*api.ReleasePayload) (api.Payloader, error) +} + +func convertPayloader(s PayloadConvertor, p api.Payloader, event models.HookEventType) (api.Payloader, error) { + switch event { + case models.HookEventCreate: + return s.Create(p.(*api.CreatePayload)) + case models.HookEventDelete: + return s.Delete(p.(*api.DeletePayload)) + case models.HookEventFork: + return s.Fork(p.(*api.ForkPayload)) + case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: + return s.Issue(p.(*api.IssuePayload)) + case models.HookEventIssueComment, models.HookEventPullRequestComment: + pl, ok := p.(*api.IssueCommentPayload) + if ok { + return s.IssueComment(pl) + } + return s.PullRequest(p.(*api.PullRequestPayload)) + case models.HookEventPush: + return s.Push(p.(*api.PushPayload)) + case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, + models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: + return s.PullRequest(p.(*api.PullRequestPayload)) + case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewComment: + return s.Review(p.(*api.PullRequestPayload), event) + case models.HookEventRepository: + return s.Repository(p.(*api.RepositoryPayload)) + case models.HookEventRelease: + return s.Release(p.(*api.ReleasePayload)) + } + return s, nil +} diff --git a/modules/webhook/slack.go b/modules/webhook/slack.go index 4177bd1250e9..aaecad6c678f 100644 --- a/modules/webhook/slack.go +++ b/modules/webhook/slack.go @@ -38,6 +38,7 @@ func GetSlackHook(w *models.Webhook) *SlackMeta { type SlackPayload struct { Channel string `json:"channel"` Text string `json:"text"` + Color string `json:"-"` Username string `json:"username"` IconURL string `json:"icon_url"` UnfurlLinks int `json:"unfurl_links"` @@ -55,11 +56,11 @@ type SlackAttachment struct { } // SetSecret sets the slack secret -func (p *SlackPayload) SetSecret(_ string) {} +func (s *SlackPayload) SetSecret(_ string) {} // JSONPayload Marshals the SlackPayload to json -func (p *SlackPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(p, "", " ") +func (s *SlackPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(s, "", " ") if err != nil { return []byte{}, err } @@ -70,9 +71,9 @@ func (p *SlackPayload) JSONPayload() ([]byte, error) { // see: https://api.slack.com/docs/formatting func SlackTextFormatter(s string) string { // replace & < > - s = strings.Replace(s, "&", "&", -1) - s = strings.Replace(s, "<", "<", -1) - s = strings.Replace(s, ">", ">", -1) + s = strings.ReplaceAll(s, "&", "&") + s = strings.ReplaceAll(s, "<", "<") + s = strings.ReplaceAll(s, ">", ">") return s } @@ -80,9 +81,9 @@ func SlackTextFormatter(s string) string { func SlackShortTextFormatter(s string) string { s = strings.Split(s, "\n")[0] // replace & < > - s = strings.Replace(s, "&", "&", -1) - s = strings.Replace(s, "<", "<", -1) - s = strings.Replace(s, ">", ">", -1) + s = strings.ReplaceAll(s, "&", "&") + s = strings.ReplaceAll(s, "<", "<") + s = strings.ReplaceAll(s, ">", ">") return s } @@ -98,53 +99,59 @@ func SlackLinkToRef(repoURL, ref string) string { return SlackLinkFormatter(url, refName) } -func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) { +var ( + _ PayloadConvertor = &SlackPayload{} +) + +// Create implements PayloadConvertor Create method +func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) { repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } -// getSlackDeletePayload composes Slack payload for delete a branch or tag. -func getSlackDeletePayload(p *api.DeletePayload, slack *SlackMeta) (*SlackPayload, error) { +// Delete composes Slack payload for delete a branch or tag. +func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } -// getSlackForkPayload composes Slack payload for forked by a repository. -func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, error) { +// Fork composes Slack payload for forked by a repository. +func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { baseLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) forkLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } -func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) { +// Issue implements PayloadConvertor Issue method +func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true) pl := &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, } if attachmentText != "" { attachmentText = SlackTextFormatter(attachmentText) @@ -160,14 +167,15 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload return pl, nil } -func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true) return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, Attachments: []SlackAttachment{{ Color: fmt.Sprintf("%x", color), Title: issueTitle, @@ -177,18 +185,20 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) ( }, nil } -func getSlackReleasePayload(p *api.ReleasePayload, slack *SlackMeta) (*SlackPayload, error) { +// Release implements PayloadConvertor Release method +func (s *SlackPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true) return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } -func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { +// Push implements PayloadConvertor Push method +func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) { // n new commits var ( commitDesc string @@ -221,12 +231,12 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e } return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, Attachments: []SlackAttachment{{ - Color: slack.Color, + Color: s.Color, Title: p.Repo.HTMLURL, TitleLink: p.Repo.HTMLURL, Text: attachmentText, @@ -234,14 +244,15 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e }, nil } -func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true) pl := &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, } if attachmentText != "" { attachmentText = SlackTextFormatter(p.PullRequest.Body) @@ -257,7 +268,8 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S return pl, nil } -func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event models.HookEventType) (*SlackPayload, error) { +// Review implements PayloadConvertor Review method +func (s *SlackPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) @@ -275,14 +287,15 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM } return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } -func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) { +// Repository implements PayloadConvertor Repository method +func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string @@ -295,15 +308,15 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla } return &SlackPayload{ - Channel: slack.Channel, + Channel: s.Channel, Text: text, - Username: slack.Username, - IconURL: slack.IconURL, + Username: s.Username, + IconURL: s.IconURL, }, nil } // GetSlackPayload converts a slack webhook into a SlackPayload -func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (*SlackPayload, error) { +func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { s := new(SlackPayload) slack := &SlackMeta{} @@ -311,33 +324,10 @@ func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) ( return s, errors.New("GetSlackPayload meta json:" + err.Error()) } - switch event { - case models.HookEventCreate: - return getSlackCreatePayload(p.(*api.CreatePayload), slack) - case models.HookEventDelete: - return getSlackDeletePayload(p.(*api.DeletePayload), slack) - case models.HookEventFork: - return getSlackForkPayload(p.(*api.ForkPayload), slack) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getSlackIssuesPayload(p.(*api.IssuePayload), slack) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getSlackIssueCommentPayload(pl, slack) - } - return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) - case models.HookEventPush: - return getSlackPushPayload(p.(*api.PushPayload), slack) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack) - case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment: - return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event) - case models.HookEventRepository: - return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack) - case models.HookEventRelease: - return getSlackReleasePayload(p.(*api.ReleasePayload), slack) - } + s.Channel = slack.Channel + s.Username = slack.Username + s.IconURL = slack.IconURL + s.Color = slack.Color - return s, nil + return convertPayloader(s, p, event) } diff --git a/modules/webhook/slack_test.go b/modules/webhook/slack_test.go index 15503434f83f..20de80bd656d 100644 --- a/modules/webhook/slack_test.go +++ b/modules/webhook/slack_test.go @@ -14,75 +14,67 @@ import ( func TestSlackIssuesPayloadOpened(t *testing.T) { p := issueTestPayload() - sl := &SlackMeta{ - Username: p.Sender.UserName, - } - p.Action = api.HookIssueOpened - pl, err := getSlackIssuesPayload(p, sl) + + s := new(SlackPayload) + s.Username = p.Sender.UserName + + pl, err := s.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Issue opened: by ", pl.Text) + assert.Equal(t, "[] Issue opened: by ", pl.(*SlackPayload).Text) p.Action = api.HookIssueClosed - pl, err = getSlackIssuesPayload(p, sl) + pl, err = s.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Issue closed: by ", pl.Text) + assert.Equal(t, "[] Issue closed: by ", pl.(*SlackPayload).Text) } func TestSlackIssueCommentPayload(t *testing.T) { p := issueCommentTestPayload() + s := new(SlackPayload) + s.Username = p.Sender.UserName - sl := &SlackMeta{ - Username: p.Sender.UserName, - } - - pl, err := getSlackIssueCommentPayload(p, sl) + pl, err := s.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] New comment on issue by ", pl.Text) + assert.Equal(t, "[] New comment on issue by ", pl.(*SlackPayload).Text) } func TestSlackPullRequestCommentPayload(t *testing.T) { p := pullRequestCommentTestPayload() + s := new(SlackPayload) + s.Username = p.Sender.UserName - sl := &SlackMeta{ - Username: p.Sender.UserName, - } - - pl, err := getSlackIssueCommentPayload(p, sl) + pl, err := s.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] New comment on pull request by ", pl.Text) + assert.Equal(t, "[] New comment on pull request by ", pl.(*SlackPayload).Text) } func TestSlackReleasePayload(t *testing.T) { p := pullReleaseTestPayload() + s := new(SlackPayload) + s.Username = p.Sender.UserName - sl := &SlackMeta{ - Username: p.Sender.UserName, - } - - pl, err := getSlackReleasePayload(p, sl) + pl, err := s.Release(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Release created: by ", pl.Text) + assert.Equal(t, "[] Release created: by ", pl.(*SlackPayload).Text) } func TestSlackPullRequestPayload(t *testing.T) { p := pullRequestTestPayload() + s := new(SlackPayload) + s.Username = p.Sender.UserName - sl := &SlackMeta{ - Username: p.Sender.UserName, - } - - pl, err := getSlackPullRequestPayload(p, sl) + pl, err := s.PullRequest(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[] Pull request opened: by ", pl.Text) + assert.Equal(t, "[] Pull request opened: by ", pl.(*SlackPayload).Text) } diff --git a/modules/webhook/telegram.go b/modules/webhook/telegram.go index 6d2f804a7023..84fc210042c6 100644 --- a/modules/webhook/telegram.go +++ b/modules/webhook/telegram.go @@ -40,22 +40,27 @@ func GetTelegramHook(w *models.Webhook) *TelegramMeta { return s } +var ( + _ PayloadConvertor = &TelegramPayload{} +) + // SetSecret sets the telegram secret -func (p *TelegramPayload) SetSecret(_ string) {} +func (t *TelegramPayload) SetSecret(_ string) {} // JSONPayload Marshals the TelegramPayload to json -func (p *TelegramPayload) JSONPayload() ([]byte, error) { - p.ParseMode = "HTML" - p.DisableWebPreview = true - p.Message = markup.Sanitize(p.Message) - data, err := json.MarshalIndent(p, "", " ") +func (t *TelegramPayload) JSONPayload() ([]byte, error) { + t.ParseMode = "HTML" + t.DisableWebPreview = true + t.Message = markup.Sanitize(t.Message) + data, err := json.MarshalIndent(t, "", " ") if err != nil { return []byte{}, err } return data, nil } -func getTelegramCreatePayload(p *api.CreatePayload) (*TelegramPayload, error) { +// Create implements PayloadConvertor Create method +func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf(`[%s] %s %s created`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, @@ -66,7 +71,8 @@ func getTelegramCreatePayload(p *api.CreatePayload) (*TelegramPayload, error) { }, nil } -func getTelegramDeletePayload(p *api.DeletePayload) (*TelegramPayload, error) { +// Delete implements PayloadConvertor Delete method +func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { // created tag/branch refName := git.RefEndName(p.Ref) title := fmt.Sprintf(`[%s] %s %s deleted`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, @@ -77,7 +83,8 @@ func getTelegramDeletePayload(p *api.DeletePayload) (*TelegramPayload, error) { }, nil } -func getTelegramForkPayload(p *api.ForkPayload) (*TelegramPayload, error) { +// Fork implements PayloadConvertor Fork method +func (t *TelegramPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { title := fmt.Sprintf(`%s is forked to %s`, p.Forkee.FullName, p.Repo.HTMLURL, p.Repo.FullName) return &TelegramPayload{ @@ -85,7 +92,8 @@ func getTelegramForkPayload(p *api.ForkPayload) (*TelegramPayload, error) { }, nil } -func getTelegramPushPayload(p *api.PushPayload) (*TelegramPayload, error) { +// Push implements PayloadConvertor Push method +func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) { var ( branchName = git.RefEndName(p.Ref) commitDesc string @@ -124,7 +132,8 @@ func getTelegramPushPayload(p *api.PushPayload) (*TelegramPayload, error) { }, nil } -func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) { +// Issue implements PayloadConvertor Issue method +func (t *TelegramPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ @@ -132,7 +141,8 @@ func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) { }, nil } -func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) { +// IssueComment implements PayloadConvertor IssueComment method +func (t *TelegramPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ @@ -140,7 +150,8 @@ func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayloa }, nil } -func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload, error) { +// PullRequest implements PayloadConvertor PullRequest method +func (t *TelegramPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ @@ -148,7 +159,8 @@ func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload, }, nil } -func getTelegramPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*TelegramPayload, error) { +// Review implements PayloadConvertor Review method +func (t *TelegramPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { var text, attachmentText string switch p.Action { case api.HookIssueReviewed: @@ -167,7 +179,8 @@ func getTelegramPullRequestApprovalPayload(p *api.PullRequestPayload, event mode }, nil } -func getTelegramRepositoryPayload(p *api.RepositoryPayload) (*TelegramPayload, error) { +// Repository implements PayloadConvertor Repository method +func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { var title string switch p.Action { case api.HookRepoCreated: @@ -184,7 +197,8 @@ func getTelegramRepositoryPayload(p *api.RepositoryPayload) (*TelegramPayload, e return nil, nil } -func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error) { +// Release implements PayloadConvertor Release method +func (t *TelegramPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) return &TelegramPayload{ @@ -193,36 +207,6 @@ func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error) } // GetTelegramPayload converts a telegram webhook into a TelegramPayload -func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string) (*TelegramPayload, error) { - s := new(TelegramPayload) - - switch event { - case models.HookEventCreate: - return getTelegramCreatePayload(p.(*api.CreatePayload)) - case models.HookEventDelete: - return getTelegramDeletePayload(p.(*api.DeletePayload)) - case models.HookEventFork: - return getTelegramForkPayload(p.(*api.ForkPayload)) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return getTelegramIssuesPayload(p.(*api.IssuePayload)) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return getTelegramIssueCommentPayload(pl) - } - return getTelegramPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPush: - return getTelegramPushPayload(p.(*api.PushPayload)) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return getTelegramPullRequestPayload(p.(*api.PullRequestPayload)) - case models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewComment: - return getTelegramPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) - case models.HookEventRepository: - return getTelegramRepositoryPayload(p.(*api.RepositoryPayload)) - case models.HookEventRelease: - return getTelegramReleasePayload(p.(*api.ReleasePayload)) - } - - return s, nil +func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { + return convertPayloader(new(TelegramPayload), p, event) } diff --git a/modules/webhook/telegram_test.go b/modules/webhook/telegram_test.go index 1686188b9dda..0e909343a86c 100644 --- a/modules/webhook/telegram_test.go +++ b/modules/webhook/telegram_test.go @@ -16,9 +16,9 @@ func TestGetTelegramIssuesPayload(t *testing.T) { p := issueTestPayload() p.Action = api.HookIssueClosed - pl, err := getTelegramIssuesPayload(p) + pl, err := new(TelegramPayload).Issue(p) require.NoError(t, err) require.NotNil(t, pl) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1\n\n", pl.Message) + assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1\n\n", pl.(*TelegramPayload).Message) } diff --git a/modules/webhook/webhook.go b/modules/webhook/webhook.go index 974872145035..2ef150210e71 100644 --- a/modules/webhook/webhook.go +++ b/modules/webhook/webhook.go @@ -76,6 +76,14 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo } } + // Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.). + // Integration webhooks (e.g. drone) still receive the required data. + if pushEvent, ok := p.(*api.PushPayload); ok && + w.HookTaskType != models.GITEA && w.HookTaskType != models.GOGS && + len(pushEvent.Commits) == 0 { + return nil + } + // If payload has no associated branch (e.g. it's a new tag, issue, etc.), // branch filter has no effect. if branch := getPayloadBranch(p); branch != "" { diff --git a/modules/webhook/webhook_test.go b/modules/webhook/webhook_test.go index e88e67e9bfef..10c32a9485be 100644 --- a/modules/webhook/webhook_test.go +++ b/modules/webhook/webhook_test.go @@ -34,7 +34,7 @@ func TestPrepareWebhooks(t *testing.T) { for _, hookTask := range hookTasks { models.AssertNotExistsBean(t, hookTask) } - assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{})) + assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Commits: []*api.PayloadCommit{{}}})) for _, hookTask := range hookTasks { models.AssertExistsAndLoadBean(t, hookTask) } @@ -51,7 +51,7 @@ func TestPrepareWebhooksBranchFilterMatch(t *testing.T) { models.AssertNotExistsBean(t, hookTask) } // this test also ensures that * doesn't handle / in any special way (like shell would) - assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791"})) + assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}})) for _, hookTask := range hookTasks { models.AssertExistsAndLoadBean(t, hookTask) } diff --git a/options/gitignore/Android b/options/gitignore/Android index 56cc6425e036..23de6e20a61f 100644 --- a/options/gitignore/Android +++ b/options/gitignore/Android @@ -83,3 +83,6 @@ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ + +# Android Profiling +*.hprof diff --git a/options/gitignore/Autotools b/options/gitignore/Autotools index 3523288c493b..f2c137d046a6 100644 --- a/options/gitignore/Autotools +++ b/options/gitignore/Autotools @@ -6,6 +6,8 @@ Makefile.in /py-compile /test-driver /ylwrap +.deps/ +.dirstamp # http://www.gnu.org/software/autoconf diff --git a/options/gitignore/Concrete5 b/options/gitignore/Concrete5 index 1fe53611e5d7..d6c11ad2b48d 100644 --- a/options/gitignore/Concrete5 +++ b/options/gitignore/Concrete5 @@ -1,4 +1,21 @@ +# ignore the error log and .htaccess and others +error_log +.htaccess + +# concrete5 5.6 specific + config/site.php files/cache/* files/tmp/* -.htaccess + +# concrete5 5.7 specific + +# ignore everything but the index.html +/application/files/* +!/application/files/index.html + +# ignore updates folder +/updates/* + +# ignore sitemap.xml +/sitemap.xml diff --git a/options/gitignore/Coq b/options/gitignore/Coq index a3e2ac49dd11..829ac44a1c7b 100644 --- a/options/gitignore/Coq +++ b/options/gitignore/Coq @@ -31,3 +31,13 @@ lia.cache nia.cache nlia.cache nra.cache + +# generated timing files +*.timing.diff +*.v.after-timing +*.v.before-timing +*.v.timing +time-of-build-after.log +time-of-build-before.log +time-of-build-both.log +time-of-build-pretty.log diff --git a/options/gitignore/Dart b/options/gitignore/Dart index dbef116d224d..6d21af37c97b 100644 --- a/options/gitignore/Dart +++ b/options/gitignore/Dart @@ -19,3 +19,6 @@ doc/api/ *.js_ *.js.deps *.js.map + +.flutter-plugins +.flutter-plugins-dependencies diff --git a/options/gitignore/Drupal b/options/gitignore/Drupal index 1c101273f555..8421b8641257 100644 --- a/options/gitignore/Drupal +++ b/options/gitignore/Drupal @@ -23,25 +23,25 @@ # Ignore drupal core (if not versioning drupal sources) /core +/vendor /modules/README.txt /profiles/README.txt +/themes/README.txt /sites/README.txt /sites/example.sites.php /sites/example.settings.local.php /sites/development.services.yml -/themes/README.txt -/vendor /.csslintrc /.editorconfig /.eslintignore /.eslintrc.json /.gitattributes +/.ht.router.php /.htaccess /autoload.php -/composer.json -/composer.lock /example.gitignore /index.php +/INSTALL.txt /LICENSE.txt /README.txt /robots.txt diff --git a/options/gitignore/Eclipse b/options/gitignore/Eclipse index a5f94da0621e..acec74ac06fb 100644 --- a/options/gitignore/Eclipse +++ b/options/gitignore/Eclipse @@ -54,3 +54,7 @@ local.properties .cache-main .scala_dependencies .worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project diff --git a/options/gitignore/Godot b/options/gitignore/Godot index 79d3eb446efd..4f48ad79f8fb 100644 --- a/options/gitignore/Godot +++ b/options/gitignore/Godot @@ -1,9 +1,11 @@ - # Godot-specific ignores .import/ export.cfg export_presets.cfg +# Imported translations (automatically generated from CSV files) +*.translation + # Mono-specific ignores .mono/ data_*/ diff --git a/options/gitignore/Gradle b/options/gitignore/Gradle index a1fc39c070f4..8d68edc977c1 100644 --- a/options/gitignore/Gradle +++ b/options/gitignore/Gradle @@ -1,5 +1,6 @@ .gradle -/build/ +**/build/ +!src/**/build/ # Ignore Gradle GUI config gradle-app.setting diff --git a/options/gitignore/JENKINS_HOME b/options/gitignore/JENKINS_HOME index 6df01d621053..2516c0994903 100644 --- a/options/gitignore/JENKINS_HOME +++ b/options/gitignore/JENKINS_HOME @@ -1,25 +1,50 @@ -#Learn more about Jenkins and JENKINS_HOME directory for which this file is intended. +# Learn more about Jenkins and JENKINS_HOME directory for which this file is +# intended. +# # http://jenkins-ci.org/ # https://wiki.jenkins-ci.org/display/JENKINS/Administering+Jenkins +# +# Note: secret.key is purposefully not tracked by git. This should be backed up +# separately because configs may contain secrets which were encrypted using the +# secret.key. To back up secrets use 'tar -czf /tmp/secrets.tgz secret*' and +# save the file separate from your repository. If you want secrets backed up +# with configuration, then see the bottom of this file for an example. -#ignore all JENKINS_HOME except jobs directory, root xml config, and .gitignore file +# Ignore all JENKINS_HOME except jobs directory, root xml config, and +# .gitignore file. /* !/jobs !/.gitignore !/*.xml -#ignore all files in jobs subdirectories except for folders -#note: git doesn't track folders, only file content +# Ignore all files in jobs subdirectories except for folders. +# Note: git doesn't track folders, only file content. jobs/** !jobs/**/ -#uncomment the following line to save next build numbers with config +# Uncomment the following line to save next build numbers with config. + #!jobs/**/nextBuildNumber -#exclude only config.xml files in repository subdirectories +# For performance reasons, we want to ignore builds in Jenkins jobs because it +# contains many tiny files on large installations. This can impact git +# performance when running even basic commands like 'git status'. +builds +indexing + +# Exclude only config.xml files in repository subdirectories. !config.xml -#don't track workspaces (when users build on the master) +# Don't track workspaces (when users build on the master). jobs/**/*workspace -#as a result only settings and job config.xml files in JENKINS_HOME will be tracked by git +# Security warning: If secrets are included with your configuration, then an +# adversary will be able to decrypt all encrypted secrets within Jenkins +# config. Including secrets is a bad practice, but the example is included in +# case someone still wants it for convenience. Uncomment the following line to +# include secrets for decryption with repository configuration in Git. + +#!/secret* + +# As a result, only Jenkins settings and job config.xml files in JENKINS_HOME +# will be tracked by git. diff --git a/options/gitignore/JetBrains b/options/gitignore/JetBrains index 37be74a974aa..8da0824ba549 100644 --- a/options/gitignore/JetBrains +++ b/options/gitignore/JetBrains @@ -1,4 +1,4 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff diff --git a/options/gitignore/Jigsaw b/options/gitignore/Jigsaw new file mode 100644 index 000000000000..7cf61e81b125 --- /dev/null +++ b/options/gitignore/Jigsaw @@ -0,0 +1,6 @@ +# gitignore template for Jigsaw Static Site Generator +# +# website - https://jigsaw.tighten.co + +# Ignore build folder +build_* diff --git a/options/gitignore/KiCad b/options/gitignore/KiCad index 853ee63f784d..bd70969a0f59 100644 --- a/options/gitignore/KiCad +++ b/options/gitignore/KiCad @@ -6,6 +6,8 @@ *.bak *.bck *.kicad_pcb-bak +*.kicad_sch-bak +*.kicad_prl *.sch-bak *~ _autosave-* diff --git a/options/gitignore/Metals b/options/gitignore/Metals new file mode 100644 index 000000000000..516e7e33f242 --- /dev/null +++ b/options/gitignore/Metals @@ -0,0 +1,5 @@ + # Generated Metals (Scala Language Server) files + # Reference: https://scalameta.org/metals/ +.metals/ +.bloop/ +project/metals.sbt diff --git a/options/gitignore/Node b/options/gitignore/Node index 9b9ca9eb6b59..1f22b9c26a3d 100644 --- a/options/gitignore/Node +++ b/options/gitignore/Node @@ -41,8 +41,8 @@ build/Release node_modules/ jspm_packages/ -# TypeScript v1 declaration files -typings/ +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ # TypeScript cache *.tsbuildinfo @@ -74,9 +74,11 @@ typings/ # parcel-bundler cache (https://parceljs.org/) .cache +.parcel-cache # Next.js build output .next +out # Nuxt.js build / generate output .nuxt @@ -105,3 +107,10 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/options/gitignore/Prestashop b/options/gitignore/Prestashop index 81f45e19ebad..9da6d29ad42c 100644 --- a/options/gitignore/Prestashop +++ b/options/gitignore/Prestashop @@ -1,34 +1,173 @@ -# Private files -# The following files contain your database credentials and other personal data. +# Cache, temp and personal files -config/settings.*.php +/.htaccess +*.log -# Cache, temp and generated files -# The following files are generated by PrestaShop. - -admin-dev/autoupgrade/ +# Cache /cache/* -!/cache/index.php -!/cache/*/ -/cache/*/* +!/cache/.htaccess !/cache/cachefs/index.php +!/cache/deprecated.txt +!/cache/index.php !/cache/purifier/index.php +!/cache/push/activity !/cache/push/index.php +!/cache/push/trends !/cache/sandbox/index.php +!/cache/smarty/cache/index.php +!/cache/smarty/compile/index.php !/cache/smarty/index.php !/cache/tcpdf/index.php -config/xml/*.xml -/log/* -*sitemap.xml -themes/*/cache/ -modules/*/config*.xml -# Site content -# The following folders contain product images, virtual products, CSV's, etc. +# Download +/download/* +!/download/.htaccess +!/download/index.php -admin-dev/backups/ -admin-dev/export/ -admin-dev/import/ -download/ +# Images /img/* -upload/ +!/img/.htaccess +!/img/index.php +!/img/404.gif +!/img/bg_500.png +!/img/bg_loader.png +!/img/favicon.ico +!/img/loader.gif +!/img/loadingAnimation.gif +!/img/logo.jpg +!/img/logo.png +!/img/logo_invoice.jpg +!/img/logo_stores.png +!/img/macFFBgHack.png +!/img/prestashop-avatar.png +!/img/prestashop@2x.png +!/img/preston-login-wink@2x.png +!/img/preston-login@2x.png +!/img/questionmark.png +!/img/genders/index.php +!/img/admin/index.php +!/img/c/index.php +!/img/cms/index.php +!/img/co/index.php +!/img/jquery-ui +!/img/l/index.php +!/img/m/index.php +!/img/os/index.php +!/img/p/index.php +!/img/s/index.php +!/img/scenes +!/img/st/index.php +!/img/su/index.php +!/img/t/index.php +!/img/tmp/index.php + +# Upload +/upload/* +!/upload/.htaccess + +/vendor/* +/docs/phpdoc-sf/ +/composer.lock +*.hot-update.js +*.hot-update.json + + +/admin-dev/autoupgrade/* +!/admin-dev/autoupgrade/index.php +!/admin-dev/autoupgrade/backup/index.php + +/admin-dev/backups/* +!/admin-dev/backups/.htaccess + +/admin-dev/import/* +!/admin-dev/import/.htaccess +!/admin-dev/import/index.php + +/admin-dev/export/* +!/admin-dev/export/.htaccess +!/admin-dev/export/index.php + +# Downloaded RTL files +/admin-dev/themes/default/css/bundle/default_rtl.css +/admin-dev/themes/default/css/bundle/shared_rtl.css +/admin-dev/themes/default/css/font_rtl.css +/admin-dev/themes/default/css/overrides_rtl.css +/admin-dev/themes/default/css/vendor/font-awesome/font-awesome_rtl.css +/admin-dev/themes/default/css/vendor/nv.d3_rtl.css +/admin-dev/themes/default/css/vendor/titatoggle-min_rtl.css +/admin-dev/themes/default/public/theme_rtl.css +/admin-dev/themes/new-theme/css/module/drop_rtl.css +/admin-dev/themes/new-theme/css/right-sidebar_rtl.css + +themes/*/cache/* + +# Config + +config/settings.inc.php +config/settings.old.php +config/xml/* +config/themes/* +!config/xml/themes/default.xml +themes/*/config/settings_*.json +app/config/parameters.old.yml +app/config/config.php + +# Themes, modules and overrides + +modules/* +override/* +themes/*/ +!themes/classic +!themes/_core +!themes/_libraries + +# Vendors and dependencies + +bower_components/ +node_modules/ +composer.phar +php-cs-fixer +.grunt/* + +# Translations and emails templates + +translations/* +mails/* +!mails/themes/ +!mails/_partials/ +themes/default-bootstrap/lang/* +themes/default-bootstrap/modules/*/translations/*.php +themes/default-bootstrap/mails/* +!themes/default-bootstrap/mails/en/ +themes/default-bootstrap/modules/*/mails/* +!themes/default-bootstrap/modules/*/mails/en + +# MISC + +*sitemap.xml +/robots.txt + +# Symfony + +/bin/ +/app/Resources/geoip/GeoLite2-City.mmdb +/app/Resources/translations/* +!/app/Resources/translations/default +/app/config/parameters.yml +/app/config/parameters.php +/build/ +/phpunit.xml +/var/* +!/var/cache +/var/cache/* +!var/cache/.gitkeep +!/var/logs +/var/logs/* +!var/logs/.gitkeep +!/var/sessions +/var/sessions/* +!var/sessions/.gitkeep +!var/SymfonyRequirements.php +/vendor/ +/web/bundles/ + diff --git a/options/gitignore/PureScript b/options/gitignore/PureScript index 361cf5277bac..de86604d370f 100644 --- a/options/gitignore/PureScript +++ b/options/gitignore/PureScript @@ -1,5 +1,6 @@ # Dependencies .psci_modules +.spago bower_components node_modules diff --git a/options/gitignore/Python b/options/gitignore/Python index b6e47617de11..a81c8ee12195 100644 --- a/options/gitignore/Python +++ b/options/gitignore/Python @@ -20,7 +20,6 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -50,6 +49,7 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -72,6 +72,7 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook @@ -82,7 +83,9 @@ profile_default/ ipython_config.py # pyenv -.python-version +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -127,3 +130,9 @@ dmypy.json # Pyre type checker .pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/options/gitignore/Qt b/options/gitignore/Qt index f147edf387bc..101354027ac8 100644 --- a/options/gitignore/Qt +++ b/options/gitignore/Qt @@ -50,3 +50,5 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* + +*_qmlcache.qrc diff --git a/options/gitignore/R b/options/gitignore/R index fae8299aeacc..da10c745ee1d 100644 --- a/options/gitignore/R +++ b/options/gitignore/R @@ -37,3 +37,9 @@ vignettes/*.pdf # R Environment Variables .Renviron + +# pkgdown site +docs/ + +# translation temp files +po/*~ diff --git a/options/gitignore/Rails b/options/gitignore/Rails index 8969914f0110..ae9df644840c 100644 --- a/options/gitignore/Rails +++ b/options/gitignore/Rails @@ -66,3 +66,4 @@ yarn-debug.log* # Ignore uploaded files in development /storage/* !/storage/.keep +/public/uploads diff --git a/options/gitignore/Raku b/options/gitignore/Raku new file mode 100644 index 000000000000..e792f6e4664c --- /dev/null +++ b/options/gitignore/Raku @@ -0,0 +1,7 @@ +# Gitignore for Raku (https://raku.org) +# As part of https://github.com/github/gitignore + +# precompiled files +.precomp +lib/.precomp + diff --git a/options/gitignore/Rust b/options/gitignore/Rust index 088ba6ba7d34..ff47c2d77d91 100644 --- a/options/gitignore/Rust +++ b/options/gitignore/Rust @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables -/target/ +debug/ +target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/options/gitignore/SAM b/options/gitignore/SAM new file mode 100644 index 000000000000..dc9d020aee1e --- /dev/null +++ b/options/gitignore/SAM @@ -0,0 +1,5 @@ +# gitignore template for AWS Serverless Application Model project +# website: https://docs.aws.amazon.com/serverless-application-model + +# Ignore build folder +.aws-sam/ diff --git a/options/gitignore/SublimeText b/options/gitignore/SublimeText index 86c3fa455aa8..fdf0abb442a5 100644 --- a/options/gitignore/SublimeText +++ b/options/gitignore/SublimeText @@ -12,6 +12,7 @@ # SFTP configuration file sftp-config.json +sftp-config-alt*.json # Package control specific files Package Control.last-run diff --git a/options/gitignore/TeX b/options/gitignore/TeX index 859f705bdb6e..8a42ebbd98cd 100644 --- a/options/gitignore/TeX +++ b/options/gitignore/TeX @@ -135,8 +135,8 @@ acs-*.bib # knitr *-concordance.tex -# TODO Comment the next line if you want to keep your tikz graphics files -*.tikz +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz *-tikzDictionary # listings @@ -274,3 +274,11 @@ TSWLatexianTemp* # Makeindex log files *.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib diff --git a/options/gitignore/Terraform b/options/gitignore/Terraform index 7a3e2fd0945d..beb38d84d0ca 100644 --- a/options/gitignore/Terraform +++ b/options/gitignore/Terraform @@ -8,11 +8,12 @@ # Crash log files crash.log -# Ignore any .tfvars files that are generated automatically for each Terraform run. Most -# .tfvars files are managed as part of configuration and so should be included in -# version control. +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. # -# example.tfvars +*.tfvars # Ignore override files as they are usually used to override resources locally and so # are not checked in @@ -27,3 +28,7 @@ override.tf.json # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan # example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/options/gitignore/Umbraco b/options/gitignore/Umbraco index cd90af3071a7..c286845766db 100644 --- a/options/gitignore/Umbraco +++ b/options/gitignore/Umbraco @@ -14,10 +14,16 @@ # Ignore Umbraco content cache file **/App_Data/umbraco.config +## this [Uu]mbraco/ folder should be created by cmd like `Install-Package UmbracoCms -Version 8.5.3` +## you can find your umbraco version at your Web.config. (i.e. ) +## Uncomment this line if you think it fits the way you work on your project. +## **/[Uu]mbraco/ + # Don't ignore Umbraco packages (VisualStudio.gitignore mistakes this for a NuGet packages folder) # Make sure to include details from VisualStudio.gitignore BEFORE this !**/App_Data/[Pp]ackages/* !**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages/* +!**/[Uu]mbraco/[Vv]iews/[Pp]ackages/* # ImageProcessor DiskCache **/App_Data/cache/ diff --git a/options/gitignore/Unity b/options/gitignore/Unity index b3433dae6507..72c27e4fe2f0 100644 --- a/options/gitignore/Unity +++ b/options/gitignore/Unity @@ -8,6 +8,10 @@ /[Bb]uild/ /[Bb]uilds/ /[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data /[Mm]emoryCaptures/ # Asset meta data should only be ignored when the corresponding asset is also ignored @@ -53,8 +57,15 @@ sysinfo.txt # Builds *.apk +*.aab *.unitypackage # Crashlytics generated file crashlytics-build.properties +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* diff --git a/options/gitignore/VisualStudio b/options/gitignore/VisualStudio index 611428faba1f..1ee53850b84c 100644 --- a/options/gitignore/VisualStudio +++ b/options/gitignore/VisualStudio @@ -23,6 +23,7 @@ mono_crash.* [Rr]eleases/ x64/ x86/ +[Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ @@ -61,6 +62,9 @@ project.lock.json project.fragment.lock.json artifacts/ +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + # StyleCop StyleCopReport.xml @@ -138,7 +142,9 @@ _TeamCity* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool -coverage*[.json, .xml, .info] +coverage*.json +coverage*.xml +coverage*.info # Visual Studio code coverage results *.coverage @@ -351,3 +357,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/options/gitignore/VisualStudioCode b/options/gitignore/VisualStudioCode index afba0a7c6161..85813e1cbc84 100644 --- a/options/gitignore/VisualStudioCode +++ b/options/gitignore/VisualStudioCode @@ -4,3 +4,6 @@ !.vscode/launch.json !.vscode/extensions.json *.code-workspace + +# Local History for Visual Studio Code +.history/ diff --git a/options/license/Apache-1.1 b/options/license/Apache-1.1 index 62c4061a3187..6736b34d5a86 100644 --- a/options/license/Apache-1.1 +++ b/options/license/Apache-1.1 @@ -26,8 +26,7 @@ permission. For written permission, please contact apache@apache.org . 5. Products derived from this software may not be called "Apache" [ex. "Jakarta," "Apache," or "Apache Commons,"] nor may "Apache" [ex. the names] appear in -their name, without prior written permission of the Apache Software Foundation -. +their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND diff --git a/options/license/BSD-2-Clause-Views b/options/license/BSD-2-Clause-Views new file mode 100644 index 000000000000..2e924ddc70ac --- /dev/null +++ b/options/license/BSD-2-Clause-Views @@ -0,0 +1,26 @@ +Copyright (c) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are +those of the authors and should not be interpreted as representing official +policies, either expressed or implied, of the copyright holders or contributors. diff --git a/options/license/BSD-3-Clause-LBNL b/options/license/BSD-3-Clause-LBNL index a5d72e464e64..8a8e3e6888f1 100644 --- a/options/license/BSD-3-Clause-LBNL +++ b/options/license/BSD-3-Clause-LBNL @@ -1,8 +1,9 @@ Copyright (c) 2003, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals -from the U.S. Dept. of Energy). All rights reserved. Redistribution and use -in source and binary forms, with or without modification, are permitted provided -that the following conditions are met: +from the U.S. Dept. of Energy). All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -19,21 +20,20 @@ prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER , THE UNITED STATES +GOVERNMENT, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features, functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to make your Enhancements -available either publicly, or directly to Lawrence Berkeley National Laboratory, -without imposing a separate written license agreement for such Enhancements, +available either publicly, or directly to Lawrence Berkeley National Laboratory +, without imposing a separate written license agreement for such Enhancements, then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such Enhancements diff --git a/options/license/CAL-1.0 b/options/license/CAL-1.0 new file mode 100644 index 000000000000..538f9d961001 --- /dev/null +++ b/options/license/CAL-1.0 @@ -0,0 +1,352 @@ +# The Cryptographic Autonomy License, v. 1.0 + +*This Cryptographic Autonomy License (the "License") applies to any Work whose +owner has marked it with any of the following notices, or a similar demonstration +of intent: * + + + +SPDX-License-Identifier: CAL-1.0 + +Licensed under the Cryptographic Autonomy License version 1.0 + + + +*or* + + + +SPDX-License-Identifier: CAL-1.0-Combined-Work-Exception + +Licensed under the Cryptographic Autonomy License version 1.0, with Combined +Work Exception + +______________________________________________________________________ + + + + ## 1. Purpose + +This License gives You unlimited permission to use and modify the software +to which it applies (the "Work"), either as-is or in modified form, for Your +private purposes, while protecting the owners and contributors to the software +from liability. + +This License also strives to protect the freedom and autonomy of third parties +who receive the Work from you. If any non-affiliated third party receives +any part, aspect, or element of the Work from You, this License requires that +You provide that third party all the permissions and materials needed to independently +use and modify the Work without that third party having a loss of data or +capability due to your actions. + + The full permissions, conditions, and other terms are laid out below. + + ## 2. Receiving a License + +In order to receive this License, You must agree to its rules. The rules of +this License are both obligations of Your agreement with the Licensor and +conditions to your License. You must not do anything with the Work that triggers +a rule You cannot or will not follow. + + ### 2.1. Application + +The terms of this License apply to the Work as you receive it from Licensor, +as well as to any modifications, elaborations, or implementations created +by You that contain any licensable portion of the Work (a "Modified Work"). +Unless specified, any reference to the Work also applies to a Modified Work. + + ### 2.2. Offer and Acceptance + +This License is automatically offered to every person and organization. You +show that you accept this License and agree to its conditions by taking any +action with the Work that, absent this License, would infringe any intellectual +property right held by Licensor. + + ### 2.3. Compliance and Remedies + +Any failure to act according to the terms and conditions of this License places +Your use of the Work outside the scope of the License and infringes the intellectual +property rights of the Licensor. In the event of infringement, the terms and +conditions of this License may be enforced by Licensor under the intellectual +property laws of any jurisdiction to which You are subject. You also agree +that either the Licensor or a Recipient (as an intended third-party beneficiary) +may enforce the terms and conditions of this License against You via specific +performance. + + ## 3. Permissions + + + + ### 3.1. Permissions Granted + +Conditioned on compliance with section 4, and subject to the limitations of +section 3.2, Licensor grants You the world-wide, royalty-free, non-exclusive +permission to: + ++ a) Take any action with the Work that would infringe the non-patent intellectual +property laws of any jurisdiction to which You are subject; and + ++ b) claims that Licensor can license or becomes able to license, to the extent +that those claims are embodied in the Work as distributed by Licensor. + + ### 3.2. Limitations on Permissions Granted + +The following limitations apply to the permissions granted in section 3.1: + + + ++ a) Licensor does not grant any patent license for claims that are only infringed +due to modification of the Work as provided by Licensor, or the combination +of the Work as provided by Licensor, directly or indirectly, with any other +component, including other software or hardware. + + + ++ b) Licensor does not grant any license to the trademarks, service marks, +or logos of Licensor, except to the extent necessary to comply with the attribution +conditions in section 4.1 of this License. + + ## 4. Conditions + +If You exercise any permission granted by this License, such that the Work, +or any part, aspect, or element of the Work, is distributed, communicated, +made available, or made perceptible to a non-Affiliate third party (a "Recipient"), +either via physical delivery or via a network connection to the Recipient, +You must comply with the following conditions: + + ### 4.1. Provide Access to Source Code + +Subject to the exception in section 4.4, You must provide to each Recipient +a copy of, or no-charge unrestricted network access to, the Source Code corresponding +to the Work ("Access"). + +The "Source Code" of the Work means the form of the Work preferred for making +modifications, including any comments, configuration information, documentation, +help materials, installation instructions, cryptographic seeds or keys, and +any information reasonably necessary for the Recipient to independently compile +and use the Source Code and to have full access to the functionality contained +in the Work. + + #### 4.1.1. Providing Network Access to the Source Code + +Network Access to the Notices and Source Code may be provided by You or by +a third party, such as a public software repository, and must persist during +the same period in which You exercise any of the permissions granted to You +under this License and for at least one year thereafter. + + #### 4.1.2. Source Code for a Modified Work + +Subject to the exception in section 4.5, You must provide to each Recipient +of a Modified Work Access to Source Code corresponding to those portions of +the Work remaining in the Modified Work as well as the modifications used +by You to create the Modified Work. The Source Code corresponding to the modifications +in the Modified Work must be provided to the Recipient either a) under this +License, or b) under a Compatible Open Source License. + + #### 4.1.3. Coordinated Disclosure of Security Vulnerabilities + +You may delay providing the Source Code corresponding to a particular modification +of the Work for up to ninety (90) days (the "Embargo Period") if: + + + ++ a) the modification is intended to address a newly-identified vulnerability +or a security flaw in the Work, + + + ++ b) disclosure of the vulnerability or security flaw before the end of the +Embargo Period would put the data, identity, or autonomy of one or more Recipients +of the Work at significant risk, + + + ++ c) You are participating in a coordinated disclosure of the vulnerability +or security flaw with one or more additional Licensees, and + + + ++ d) Access to the Source Code pertaining to the modification is provided +to all Recipients at the end of the Embargo Period. + + ### 4.2. Maintain User Autonomy + +In addition to providing each Recipient the opportunity to have Access to +the Source Code, You cannot use the permissions given under this License to +interfere with a Recipient's ability to fully use an independent copy of the +Work generated from the Source Code You provide with the Recipient's own User +Data. + +"User Data" means any data that is an input to or an output from the Work, +where the presence of the data is necessary for substantially identical use +of the Work in an equivalent context chosen by the Recipient, and where the +Recipient has an existing ownership interest, an existing right to possess, +or where the data has been generated by, for, or has been assigned to the +Recipient. + + #### 4.2.1. No Withholding User Data + +Throughout any period in which You exercise any of the permissions granted +to You under this License, You must also provide to any Recipient to whom +you provide services via the Work, a no-charge copy, provided in a commonly +used electronic form, of the Recipient's User Data in your possession, to +the extent that such User Data is available to You for use in conjunction +with the Work. + + #### 4.2.2. No Technical Measures that Limit Access + +You may not, by means of the use cryptographic methods applied to anything +provided to the Recipient, by possession or control of cryptographic keys, +seeds, hashes, by any other technological protection measures, or by any other +method, limit a Recipient's ability to access any functionality present in +Recipient's independent copy of the Work, or to deny a Recipient full control +of the Recipient's User Data. + + #### 4.2.3. No Legal or Contractual Measures that Limit Access + +You may not contractually restrict a Recipient's ability to independently +exercise the permissions granted under this License. You waive any legal power +to forbid circumvention of technical protection measures that include use +of the Work, and You waive any claim that the capabilities of the Work were +limited or modified as a means of enforcing the legal rights of third parties +against Recipients. + + ### 4.3. Provide Notices and Attribution + +You must retain all licensing, authorship, or attribution notices contained +in the Source Code (the "Notices"), and provide all such Notices to each Recipient, +together with a statement acknowledging the use of the Work. Notices may be +provided directly to a Recipient or via an easy-to-find hyperlink to an Internet +location also providing Access to Source Code. + + ### 4.4. Scope of Conditions in this License + +You are required to uphold the conditions of this License only relative to +those who are Recipients of the Work from You. Other than providing Recipients +with the applicable Notices, Access to Source Code, and a copy of and full +control of their User Data, nothing in this License requires You to provide +processing services to or engage in network interactions with anyone. + + ### 4.5. Combined Work Exception + +As an exception to condition that You provide Recipients Access to Source +Code, any Source Code files marked by the Licensor as having the "Combined +Work Exception," or any object code exclusively resulting from Source Code +files so marked, may be combined with other Software into a "Larger Work." +So long as you comply with the requirements to provide Recipients the applicable +Notices and Access to the Source Code provided to You by Licensor, and you +provide Recipients access to their User Data and do not limit Recipient's +ability to independently work with their User Data, any other Software in +the Larger Work as well as the Larger Work as a whole may be licensed under +the terms of your choice. + + ## 5. Term and Termination + +The term of this License begins when You receive the Work, and continues until +terminated for any of the reasons described herein, or until all Licensor's +intellectual property rights in the Software expire, whichever comes first +("Term"). This License cannot be revoked, only terminated for the reasons +listed below. + + ### 5.1. Effect of Termination + +If this License is terminated for any reason, all permissions granted to You +under Section 3 by any Licensor automatically terminate. You will immediately +cease exercising any permissions granted in this License relative to the Work, +including as part of any Modified Work. + + ### 5.2. Termination for Non-Compliance; Reinstatement + +This License terminates automatically if You fail to comply with any of the +conditions in section 4. As a special exception to termination for non-compliance, +Your permissions for the Work under this License will automatically be reinstated +if You come into compliance with all the conditions in section 2 within sixty +(60) days of being notified by Licensor or an intended third-party beneficiary +of Your noncompliance. You are eligible for reinstatement of permissions for +the Work one time only, and only for the sixty days immediately after becoming +aware of noncompliance. Loss of permissions granted for the Work under this +License due to either a) sustained noncompliance lasting more than sixty days +or b) subsequent termination for noncompliance after reinstatement, is permanent, +unless rights are specifically restored by Licensor in writing. + + ### 5.3. Termination Due to Litigation + +If You initiate litigation against Licensor, or any Recipient of the Work, +either direct or indirect, asserting that the Work directly or indirectly +infringes any patent, then all permissions granted to You by this License +shall terminate. In the event of termination due to litigation, all permissions +validly granted by You under this License, directly or indirectly, shall survive +termination. Administrative review procedures, declaratory judgment actions, +counterclaims in response to patent litigation, and enforcement actions against +former Licensees terminated under this section do not cause termination due +to litigation. + + ## 6. Disclaimer of Warranty and Limit on Liability + +As far as the law allows, the Work comes AS-IS, without any warranty of any +kind, and no Licensor or contributor will be liable to anyone for any damages +related to this software or this license, under any kind of legal claim, or +for any type of damages, including indirect, special, incidental, or consequential +damages of any type arising as a result of this License or the use of the +Work including, without limitation, damages for loss of goodwill, work stoppage, +computer failure or malfunction, loss of profits, revenue, or any and all +other commercial damages or losses. + + ## 7. Other Provisions + + + + ### 7.1. Affiliates + +An "Affiliate" means any other entity that, directly or indirectly through +one or more intermediaries, controls, is controlled by, or is under common +control with, the Licensee. Employees of a Licensee and natural persons acting +as contractors exclusively providing services to Licensee are also Affiliates. + + ### 7.2. Choice of Jurisdiction and Governing Law + +A Licensor may require that any action or suit by a Licensee relating to a +Work provided by Licensor under this License may be brought only in the courts +of a particular jurisdiction and under the laws of a particular jurisdiction +(excluding its conflict-of-law provisions), if Licensor provides conspicuous +notice of the particular jurisdiction to all Licensees. + + ### 7.3. No Sublicensing + +This License is not sublicensable. Each time You provide the Work or a Modified +Work to a Recipient, the Recipient automatically receives a license under +the terms described in this License. You may not impose any further reservations, +conditions, or other provisions on any Recipients' exercise of the permissions +granted herein. + + ### 7.4. Attorneys' Fees + +In any action to enforce the terms of this License, or seeking damages relating +thereto, including by an intended third-party beneficiary, the prevailing +party shall be entitled to recover its costs and expenses, including, without +limitation, reasonable attorneys' fees and costs incurred in connection with +such action, including any appeal of such action. A "prevailing party" is +the party that achieves, or avoids, compliance with this License, including +through settlement. This section shall survive the termination of this License. + + ### 7.5. No Waiver + +Any failure by Licensor to enforce any provision of this License will not +constitute a present or future waiver of such provision nor limit Licensor's +ability to enforce such provision at a later time. + + ### 7.6. Severability + +If any provision of this License is held to be unenforceable, such provision +shall be reformed only to the extent necessary to make it enforceable. Any +invalid or unenforceable portion will be interpreted to the effect and intent +of the original portion. If such a construction is not possible, the invalid +or unenforceable portion will be severed from this License but the rest of +this License will remain in full force and effect. + + ### 7.7. License for the Text of this License + +The text of this license is released under the Creative Commons Attribution-ShareAlike +4.0 International License, with the caveat that any modifications of this +license may not use the name "Cryptographic Autonomy License" or any name +confusingly similar thereto to describe any derived work of this License. diff --git a/options/license/CAL-1.0-Combined-Work-Exception b/options/license/CAL-1.0-Combined-Work-Exception new file mode 100644 index 000000000000..538f9d961001 --- /dev/null +++ b/options/license/CAL-1.0-Combined-Work-Exception @@ -0,0 +1,352 @@ +# The Cryptographic Autonomy License, v. 1.0 + +*This Cryptographic Autonomy License (the "License") applies to any Work whose +owner has marked it with any of the following notices, or a similar demonstration +of intent: * + + + +SPDX-License-Identifier: CAL-1.0 + +Licensed under the Cryptographic Autonomy License version 1.0 + + + +*or* + + + +SPDX-License-Identifier: CAL-1.0-Combined-Work-Exception + +Licensed under the Cryptographic Autonomy License version 1.0, with Combined +Work Exception + +______________________________________________________________________ + + + + ## 1. Purpose + +This License gives You unlimited permission to use and modify the software +to which it applies (the "Work"), either as-is or in modified form, for Your +private purposes, while protecting the owners and contributors to the software +from liability. + +This License also strives to protect the freedom and autonomy of third parties +who receive the Work from you. If any non-affiliated third party receives +any part, aspect, or element of the Work from You, this License requires that +You provide that third party all the permissions and materials needed to independently +use and modify the Work without that third party having a loss of data or +capability due to your actions. + + The full permissions, conditions, and other terms are laid out below. + + ## 2. Receiving a License + +In order to receive this License, You must agree to its rules. The rules of +this License are both obligations of Your agreement with the Licensor and +conditions to your License. You must not do anything with the Work that triggers +a rule You cannot or will not follow. + + ### 2.1. Application + +The terms of this License apply to the Work as you receive it from Licensor, +as well as to any modifications, elaborations, or implementations created +by You that contain any licensable portion of the Work (a "Modified Work"). +Unless specified, any reference to the Work also applies to a Modified Work. + + ### 2.2. Offer and Acceptance + +This License is automatically offered to every person and organization. You +show that you accept this License and agree to its conditions by taking any +action with the Work that, absent this License, would infringe any intellectual +property right held by Licensor. + + ### 2.3. Compliance and Remedies + +Any failure to act according to the terms and conditions of this License places +Your use of the Work outside the scope of the License and infringes the intellectual +property rights of the Licensor. In the event of infringement, the terms and +conditions of this License may be enforced by Licensor under the intellectual +property laws of any jurisdiction to which You are subject. You also agree +that either the Licensor or a Recipient (as an intended third-party beneficiary) +may enforce the terms and conditions of this License against You via specific +performance. + + ## 3. Permissions + + + + ### 3.1. Permissions Granted + +Conditioned on compliance with section 4, and subject to the limitations of +section 3.2, Licensor grants You the world-wide, royalty-free, non-exclusive +permission to: + ++ a) Take any action with the Work that would infringe the non-patent intellectual +property laws of any jurisdiction to which You are subject; and + ++ b) claims that Licensor can license or becomes able to license, to the extent +that those claims are embodied in the Work as distributed by Licensor. + + ### 3.2. Limitations on Permissions Granted + +The following limitations apply to the permissions granted in section 3.1: + + + ++ a) Licensor does not grant any patent license for claims that are only infringed +due to modification of the Work as provided by Licensor, or the combination +of the Work as provided by Licensor, directly or indirectly, with any other +component, including other software or hardware. + + + ++ b) Licensor does not grant any license to the trademarks, service marks, +or logos of Licensor, except to the extent necessary to comply with the attribution +conditions in section 4.1 of this License. + + ## 4. Conditions + +If You exercise any permission granted by this License, such that the Work, +or any part, aspect, or element of the Work, is distributed, communicated, +made available, or made perceptible to a non-Affiliate third party (a "Recipient"), +either via physical delivery or via a network connection to the Recipient, +You must comply with the following conditions: + + ### 4.1. Provide Access to Source Code + +Subject to the exception in section 4.4, You must provide to each Recipient +a copy of, or no-charge unrestricted network access to, the Source Code corresponding +to the Work ("Access"). + +The "Source Code" of the Work means the form of the Work preferred for making +modifications, including any comments, configuration information, documentation, +help materials, installation instructions, cryptographic seeds or keys, and +any information reasonably necessary for the Recipient to independently compile +and use the Source Code and to have full access to the functionality contained +in the Work. + + #### 4.1.1. Providing Network Access to the Source Code + +Network Access to the Notices and Source Code may be provided by You or by +a third party, such as a public software repository, and must persist during +the same period in which You exercise any of the permissions granted to You +under this License and for at least one year thereafter. + + #### 4.1.2. Source Code for a Modified Work + +Subject to the exception in section 4.5, You must provide to each Recipient +of a Modified Work Access to Source Code corresponding to those portions of +the Work remaining in the Modified Work as well as the modifications used +by You to create the Modified Work. The Source Code corresponding to the modifications +in the Modified Work must be provided to the Recipient either a) under this +License, or b) under a Compatible Open Source License. + + #### 4.1.3. Coordinated Disclosure of Security Vulnerabilities + +You may delay providing the Source Code corresponding to a particular modification +of the Work for up to ninety (90) days (the "Embargo Period") if: + + + ++ a) the modification is intended to address a newly-identified vulnerability +or a security flaw in the Work, + + + ++ b) disclosure of the vulnerability or security flaw before the end of the +Embargo Period would put the data, identity, or autonomy of one or more Recipients +of the Work at significant risk, + + + ++ c) You are participating in a coordinated disclosure of the vulnerability +or security flaw with one or more additional Licensees, and + + + ++ d) Access to the Source Code pertaining to the modification is provided +to all Recipients at the end of the Embargo Period. + + ### 4.2. Maintain User Autonomy + +In addition to providing each Recipient the opportunity to have Access to +the Source Code, You cannot use the permissions given under this License to +interfere with a Recipient's ability to fully use an independent copy of the +Work generated from the Source Code You provide with the Recipient's own User +Data. + +"User Data" means any data that is an input to or an output from the Work, +where the presence of the data is necessary for substantially identical use +of the Work in an equivalent context chosen by the Recipient, and where the +Recipient has an existing ownership interest, an existing right to possess, +or where the data has been generated by, for, or has been assigned to the +Recipient. + + #### 4.2.1. No Withholding User Data + +Throughout any period in which You exercise any of the permissions granted +to You under this License, You must also provide to any Recipient to whom +you provide services via the Work, a no-charge copy, provided in a commonly +used electronic form, of the Recipient's User Data in your possession, to +the extent that such User Data is available to You for use in conjunction +with the Work. + + #### 4.2.2. No Technical Measures that Limit Access + +You may not, by means of the use cryptographic methods applied to anything +provided to the Recipient, by possession or control of cryptographic keys, +seeds, hashes, by any other technological protection measures, or by any other +method, limit a Recipient's ability to access any functionality present in +Recipient's independent copy of the Work, or to deny a Recipient full control +of the Recipient's User Data. + + #### 4.2.3. No Legal or Contractual Measures that Limit Access + +You may not contractually restrict a Recipient's ability to independently +exercise the permissions granted under this License. You waive any legal power +to forbid circumvention of technical protection measures that include use +of the Work, and You waive any claim that the capabilities of the Work were +limited or modified as a means of enforcing the legal rights of third parties +against Recipients. + + ### 4.3. Provide Notices and Attribution + +You must retain all licensing, authorship, or attribution notices contained +in the Source Code (the "Notices"), and provide all such Notices to each Recipient, +together with a statement acknowledging the use of the Work. Notices may be +provided directly to a Recipient or via an easy-to-find hyperlink to an Internet +location also providing Access to Source Code. + + ### 4.4. Scope of Conditions in this License + +You are required to uphold the conditions of this License only relative to +those who are Recipients of the Work from You. Other than providing Recipients +with the applicable Notices, Access to Source Code, and a copy of and full +control of their User Data, nothing in this License requires You to provide +processing services to or engage in network interactions with anyone. + + ### 4.5. Combined Work Exception + +As an exception to condition that You provide Recipients Access to Source +Code, any Source Code files marked by the Licensor as having the "Combined +Work Exception," or any object code exclusively resulting from Source Code +files so marked, may be combined with other Software into a "Larger Work." +So long as you comply with the requirements to provide Recipients the applicable +Notices and Access to the Source Code provided to You by Licensor, and you +provide Recipients access to their User Data and do not limit Recipient's +ability to independently work with their User Data, any other Software in +the Larger Work as well as the Larger Work as a whole may be licensed under +the terms of your choice. + + ## 5. Term and Termination + +The term of this License begins when You receive the Work, and continues until +terminated for any of the reasons described herein, or until all Licensor's +intellectual property rights in the Software expire, whichever comes first +("Term"). This License cannot be revoked, only terminated for the reasons +listed below. + + ### 5.1. Effect of Termination + +If this License is terminated for any reason, all permissions granted to You +under Section 3 by any Licensor automatically terminate. You will immediately +cease exercising any permissions granted in this License relative to the Work, +including as part of any Modified Work. + + ### 5.2. Termination for Non-Compliance; Reinstatement + +This License terminates automatically if You fail to comply with any of the +conditions in section 4. As a special exception to termination for non-compliance, +Your permissions for the Work under this License will automatically be reinstated +if You come into compliance with all the conditions in section 2 within sixty +(60) days of being notified by Licensor or an intended third-party beneficiary +of Your noncompliance. You are eligible for reinstatement of permissions for +the Work one time only, and only for the sixty days immediately after becoming +aware of noncompliance. Loss of permissions granted for the Work under this +License due to either a) sustained noncompliance lasting more than sixty days +or b) subsequent termination for noncompliance after reinstatement, is permanent, +unless rights are specifically restored by Licensor in writing. + + ### 5.3. Termination Due to Litigation + +If You initiate litigation against Licensor, or any Recipient of the Work, +either direct or indirect, asserting that the Work directly or indirectly +infringes any patent, then all permissions granted to You by this License +shall terminate. In the event of termination due to litigation, all permissions +validly granted by You under this License, directly or indirectly, shall survive +termination. Administrative review procedures, declaratory judgment actions, +counterclaims in response to patent litigation, and enforcement actions against +former Licensees terminated under this section do not cause termination due +to litigation. + + ## 6. Disclaimer of Warranty and Limit on Liability + +As far as the law allows, the Work comes AS-IS, without any warranty of any +kind, and no Licensor or contributor will be liable to anyone for any damages +related to this software or this license, under any kind of legal claim, or +for any type of damages, including indirect, special, incidental, or consequential +damages of any type arising as a result of this License or the use of the +Work including, without limitation, damages for loss of goodwill, work stoppage, +computer failure or malfunction, loss of profits, revenue, or any and all +other commercial damages or losses. + + ## 7. Other Provisions + + + + ### 7.1. Affiliates + +An "Affiliate" means any other entity that, directly or indirectly through +one or more intermediaries, controls, is controlled by, or is under common +control with, the Licensee. Employees of a Licensee and natural persons acting +as contractors exclusively providing services to Licensee are also Affiliates. + + ### 7.2. Choice of Jurisdiction and Governing Law + +A Licensor may require that any action or suit by a Licensee relating to a +Work provided by Licensor under this License may be brought only in the courts +of a particular jurisdiction and under the laws of a particular jurisdiction +(excluding its conflict-of-law provisions), if Licensor provides conspicuous +notice of the particular jurisdiction to all Licensees. + + ### 7.3. No Sublicensing + +This License is not sublicensable. Each time You provide the Work or a Modified +Work to a Recipient, the Recipient automatically receives a license under +the terms described in this License. You may not impose any further reservations, +conditions, or other provisions on any Recipients' exercise of the permissions +granted herein. + + ### 7.4. Attorneys' Fees + +In any action to enforce the terms of this License, or seeking damages relating +thereto, including by an intended third-party beneficiary, the prevailing +party shall be entitled to recover its costs and expenses, including, without +limitation, reasonable attorneys' fees and costs incurred in connection with +such action, including any appeal of such action. A "prevailing party" is +the party that achieves, or avoids, compliance with this License, including +through settlement. This section shall survive the termination of this License. + + ### 7.5. No Waiver + +Any failure by Licensor to enforce any provision of this License will not +constitute a present or future waiver of such provision nor limit Licensor's +ability to enforce such provision at a later time. + + ### 7.6. Severability + +If any provision of this License is held to be unenforceable, such provision +shall be reformed only to the extent necessary to make it enforceable. Any +invalid or unenforceable portion will be interpreted to the effect and intent +of the original portion. If such a construction is not possible, the invalid +or unenforceable portion will be severed from this License but the rest of +this License will remain in full force and effect. + + ### 7.7. License for the Text of this License + +The text of this license is released under the Creative Commons Attribution-ShareAlike +4.0 International License, with the caveat that any modifications of this +license may not use the name "Cryptographic Autonomy License" or any name +confusingly similar thereto to describe any derived work of this License. diff --git a/options/license/CC-BY-3.0-AT b/options/license/CC-BY-3.0-AT new file mode 100644 index 000000000000..4facf5f4e688 --- /dev/null +++ b/options/license/CC-BY-3.0-AT @@ -0,0 +1,318 @@ +CREATIVE COMMONS IST KEINE RECHTSANWALTSKANZLEI UND LEISTET KEINE RECHTSBERATUNG. +DIE BEREITSTELLUNG DIESER LIZENZ FÜHRT ZU KEINEM MANDATSVERHÄLTNIS. CREATIVE +COMMONS STELLT DIESE INFORMATIONEN OHNE GEWÄHR ZUR VERFÜGUNG. CREATIVE COMMONS +ÜBERNIMMT KEINE GEWÄHRLEISTUNG FÜR DIE GELIEFERTEN INFORMATIONEN UND SCHLIEßT +DIE HAFTUNG FÜR SCHÄDEN AUS, DIE SICH AUS DEREN GEBRAUCH ERGEBEN. Lizenz + +DER GEGENSTAND DIESER LIZENZ (WIE UNTER "SCHUTZGEGENSTAND" DEFINIERT) WIRD +UNTER DEN BEDINGUNGEN DIESER CREATIVE COMMONS PUBLIC LICENSE ("CCPL", "LIZENZ" +ODER "LIZENZVERTRAG") ZUR VERFÜGUNG GESTELLT. DER SCHUTZGEGENSTAND IST DURCH +DAS URHEBERRECHT UND/ODER ANDERE GESETZE GESCHÜTZT. JEDE FORM DER NUTZUNG +DES SCHUTZGEGENSTANDES, DIE NICHT AUFGRUND DIESER LIZENZ ODER DURCH GESETZE +GESTATTET IST, IST UNZULÄSSIG. + +DURCH DIE AUSÜBUNG EINES DURCH DIESE LIZENZ GEWÄHRTEN RECHTS AN DEM SCHUTZGEGENSTAND +ERKLÄREN SIE SICH MIT DEN LIZENZBEDINGUNGEN RECHTSVERBINDLICH EINVERSTANDEN. +SOWEIT DIESE LIZENZ ALS LIZENZVERTRAG ANZUSEHEN IST, GEWÄHRT IHNEN DER LIZENZGEBER +DIE IN DER LIZENZ GENANNTEN RECHTE UNENTGELTLICH UND IM AUSTAUSCH DAFÜR, DASS +SIE DAS GEBUNDENSEIN AN DIE LIZENZBEDINGUNGEN AKZEPTIEREN. + + 1. Definitionen + +a. Der Begriff "Bearbeitung" im Sinne dieser Lizenz bezeichnet das Ergebnis +jeglicher Art von Veränderung des Schutzgegenstandes, solange dieses erkennbar +vom Schutzgegenstand abgeleitet wurde. Dies kann insbesondere auch eine Umgestaltung, +Änderung, Anpassung, Übersetzung oder Heranziehung des Schutzgegenstandes +zur Vertonung von Laufbildern sein. Nicht als Bearbeitung des Schutzgegenstandes +gelten seine Aufnahme in eine Sammlung oder ein Sammelwerk und die freie Nutzung +des Schutzgegenstandes. + +b. Der Begriff "Sammelwerk" im Sinne dieser Lizenz meint eine Zusammenstellung +von literarischen, künstlerischen oder wissenschaftlichen Inhalten zu einem +einheitlichen Ganzen, sofern diese Zusammenstellung aufgrund von Auswahl und +Anordnung der darin enthaltenen selbständigen Elemente eine eigentümliche +geistige Schöpfung darstellt, unabhängig davon, ob die Elemente systematisch +oder methodisch angelegt und dadurch einzeln zugänglich sind oder nicht. + +c. "Verbreiten" im Sinne dieser Lizenz bedeutet, den Schutzgegenstand oder +Bearbeitungen im Original oder in Form von Vervielfältigungsstücken, mithin +in körperlich fixierter Form der Öffentlichkeit zugänglich zu machen oder +in Verkehr zu bringen. + +d. Der "Lizenzgeber" im Sinne dieser Lizenz ist diejenige natürliche oder +juristische Person oder Gruppe, die den Schutzgegenstand unter den Bedingungen +dieser Lizenz anbietet und insoweit als Rechteinhaberin auftritt. + +e. "Rechteinhaber" im Sinne dieser Lizenz ist der Urheber des Schutzgegenstandes +oder jede andere natürliche oder juristische Person, die am Schutzgegenstand +ein Immaterialgüterrecht erlangt hat, welches die in Abschnitt 3 genannten +Handlungen erfasst und eine Erteilung, Übertragung oder Einräumung von Nutzungsbewilligungen +bzw Nutzungsrechten an Dritte erlaubt. + +f. Der Begriff "Schutzgegenstand" bezeichnet in dieser Lizenz den literarischen, +künstlerischen oder wissenschaftlichen Inhalt, der unter den Bedingungen dieser +Lizenz angeboten wird. Das kann insbesondere eine eigentümliche geistige Schöpfung +jeglicher Art oder ein Werk der kleinen Münze, ein nachgelassenes Werk oder +auch ein Lichtbild oder anderes Objekt eines verwandten Schutzrechts sein, +unabhängig von der Art seiner Fixierung und unabhängig davon, auf welche Weise +jeweils eine Wahrnehmung erfolgen kann, gleichviel ob in analoger oder digitaler +Form. Soweit Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen +Schutz eigener Art genießen, unterfallen auch sie dem Begriff „Schutzgegenstand" +im Sinne dieser Lizenz. + +g. Mit "Sie" bzw. "Ihnen" ist die natürliche oder juristische Person gemeint, +die in dieser Lizenz im Abschnitt 3 genannte Nutzungen des Schutzgegenstandes +vornimmt und zuvor in Hinblick auf den Schutzgegenstand nicht gegen Bedingungen +dieser Lizenz verstoßen oder aber die ausdrückliche Erlaubnis des Lizenzgebers +erhalten hat, die durch diese Lizenz gewährte Nutzungsbewilligung trotz eines +vorherigen Verstoßes auszuüben. + +h. Unter "Öffentlich Wiedergeben" im Sinne dieser Lizenz sind Wahrnehmbarmachungen +des Schutzgegenstandes in unkörperlicher Form zu verstehen, die für eine Mehrzahl +von Mitgliedern der Öffentlichkeit bestimmt sind und mittels öffentlicher +Wiedergabe in Form von Vortrag, Aufführung, Vorführung, Darbietung, Sendung, +Weitersendung oder zeit- und ortsunabhängiger Zurverfügungstellung erfolgen, +unabhängig von den zum Einsatz kommenden Techniken und Verfahren, einschließlich +drahtgebundener oder drahtloser Mittel und Einstellen in das Internet. + +i. "Vervielfältigen" im Sinne dieser Lizenz bedeutet, gleichviel in welchem +Verfahren, auf welchem Träger, in welcher Menge und ob vorübergehend oder +dauerhaft, Vervielfältigungsstücke des Schutzgegenstandes herzustellen, insbesondere +durch Ton- oder Bildaufzeichnungen, und umfasst auch das erstmalige Festhalten +des Schutzgegenstandes oder dessen Wahrnehmbarmachung auf Mitteln der wiederholbaren +Wiedergabe sowie das Herstellen von Vervielfältigungsstücken dieser Festhaltung, +sowie die Speicherung einer geschützten Darbietung oder eines Bild- und/oder +Schallträgers in digitaler Form oder auf einem anderen elektronischen Medium. + + 2. Beschränkungen der Verwertungsrechte + +Diese Lizenz ist in keiner Weise darauf gerichtet, Befugnisse zur Nutzung +des Schutzgegenstandes zu vermindern, zu beschränken oder zu vereiteln, die +sich aus den Beschränkungen der Verwertungsrechte, anderen Beschränkungen +der Ausschließlichkeitsrechte des Rechtsinhabers oder anderen entsprechenden +Rechtsnormen oder sich aus dem Fehlen eines immaterialgüterrechtlichen Schutzes +ergeben. + + 3. Lizenzierung + +Unter den Bedingungen dieser Lizenz erteilt Ihnen der Lizenzgeber - unbeschadet +unverzichtbarer Rechte und vorbehaltlich des Abschnitts 3.e) - die vergütungsfreie, +räumlich und zeitlich (für die Dauer des Urheberrechts oder verwandten Schutzrechts +am Schutzgegenstand) unbeschränkte Nutzungsbewilligung, den Schutzgegenstand +in der folgenden Art und Weise zu nutzen: + +a. Den Schutzgegenstand in beliebiger Form und Menge zu vervielfältigen, ihn +in Sammelwerke zu integrieren und ihn als Teil solcher Sammelwerke zu vervielfältigen; + +b. Den Schutzgegenstand zu bearbeiten, einschließlich Übersetzungen unter +Nutzung jedweder Medien anzufertigen, sofern deutlich erkennbar gemacht wird, +dass es sich um eine Bearbeitung handelt; + +c. Den Schutzgegenstand, allein oder in Sammelwerke aufgenommen, öffentlich +wiederzugeben und zu verbreiten; und + +d. Bearbeitungen des Schutzgegenstandes zu veröffentlichen, öffentlich wiederzugeben +und zu verbreiten. + +e. Bezüglich der Vergütung für die Nutzung des Schutzgegenstandes gilt Folgendes: + +i. Unverzichtbare gesetzliche Vergütungsansprüche: Soweit unverzichtbare Vergütungsansprüche +im Gegenzug für gesetzliche Lizenzen vorgesehen oder Pauschalabgabensysteme +(zum Beispiel für Leermedien) vorhanden sind, behält sich der Lizenzgeber +das ausschließliche Recht vor, die entsprechenden Vergütungsansprüche für +jede Ausübung eines Rechts aus dieser Lizenz durch Sie geltend zu machen. + +ii. Vergütung bei Zwangslizenzen: Sofern Zwangslizenzen außerhalb dieser Lizenz +vorgesehen sind und zustande kommen, verzichtet der Lizenzgeber für alle Fälle +einer lizenzgerechten Nutzung des Schutzgegenstandes durch Sie auf jegliche +Vergütung. + +iii. Vergütung in sonstigen Fällen: Bezüglich lizenzgerechter Nutzung des +Schutzgegenstandes durch Sie, die nicht unter die beiden vorherigen Abschnitte +(i) und (ii) fällt, verzichtet der Lizenzgeber auf jegliche Vergütung, unabhängig +davon, ob eine Geltendmachung der Vergütungsansprüche durch ihn selbst oder +nur durch eine Verwertungsgesellschaft möglich wäre. + +Die vorgenannte Nutzungsbewilligung wird für alle bekannten sowie alle noch +nicht bekannten Nutzungsarten eingeräumt. Sie beinhaltet auch das Recht, solche +Änderungen am Schutzgegenstand vorzunehmen, die für bestimmte nach dieser +Lizenz zulässige Nutzungen technisch erforderlich sind. Alle sonstigen Rechte, +die über diesen Abschnitt hinaus nicht ausdrücklich vom Lizenzgeber eingeräumt +werden, bleiben diesem allein vorbehalten. Soweit Datenbanken oder Zusammenstellungen +von Daten Schutzgegenstand dieser Lizenz oder Teil dessen sind und einen immaterialgüterrechtlichen +Schutz eigener Art genießen, verzichtet der Lizenzgeber auf die Geltendmachung +sämtlicher daraus resultierender Rechte. + + 4. Bedingungen + +Die Erteilung der Nutzungsbewilligung gemäß Abschnitt 3 dieser Lizenz erfolgt +ausdrücklich nur unter den folgenden Bedingungen: + +a. Sie dürfen den Schutzgegenstand ausschließlich unter den Bedingungen dieser +Lizenz verbreiten oder öffentlich wiedergeben. Sie müssen dabei stets eine +Kopie dieser Lizenz oder deren vollständige Internetadresse in Form des Uniform-Resource-Identifier +(URI) beifügen. Sie dürfen keine Vertrags- oder Nutzungsbedingungen anbieten +oder fordern, die die Bedingungen dieser Lizenz oder die durch diese Lizenz +gewährten Rechte beschränken. Sie dürfen den Schutzgegenstand nicht unterlizenzieren. +Bei jeder Kopie des Schutzgegenstandes, die Sie verbreiten oder öffentlich +wiedergeben, müssen Sie alle Hinweise unverändert lassen, die auf diese Lizenz +und den Haftungsausschluss hinweisen. Wenn Sie den Schutzgegenstand verbreiten +oder öffentlich wiedergeben, dürfen Sie (in Bezug auf den Schutzgegenstand) +keine technischen Maßnahmen ergreifen, die den Nutzer des Schutzgegenstandes +in der Ausübung der ihm durch diese Lizenz gewährten Rechte behindern können. +Dasselbe gilt auch für den Fall, dass der Schutzgegenstand einen Bestandteil +eines Sammelwerkes bildet, was jedoch nicht bedeutet, dass das Sammelwerk +insgesamt dieser Lizenz unterstellt werden muss. Sofern Sie ein Sammelwerk +erstellen, müssen Sie - soweit dies praktikabel ist - auf die Mitteilung eines +Lizenzgebers hin aus dem Sammelwerk die in Abschnitt 4.b) aufgezählten Hinweise +entfernen. Wenn Sie eine Bearbeitung vornehmen, müssen Sie – soweit dies praktikabel +ist – auf die Mitteilung eines Lizenzgebers hin von der Bearbeitung die in +Abschnitt 4.b) aufgezählten Hinweise entfernen. + +b. Die Verbreitung und die öffentliche Wiedergabe des Schutzgegenstandes oder +auf ihm aufbauender Inhalte oder ihn enthaltender Sammelwerke ist Ihnen nur +unter der Bedingung gestattet, dass Sie, vorbehaltlich etwaiger Mitteilungen +im Sinne von Abschnitt 4.a), alle dazu gehörenden Rechtevermerke unberührt +lassen. Sie sind verpflichtet, die Urheberschaft oder die Rechteinhaberschaft +in einer der Nutzung entsprechenden, angemessenen Form anzuerkennen, indem +Sie selbst – soweit bekannt – Folgendes angeben: + +i. Den Namen (oder das Pseudonym, falls ein solches verwendet wird) Rechteinhabers, +und/oder falls der Lizenzgeber im Rechtevermerk, in den Nutzungsbedingungen +oder auf andere angemessene Weise eine Zuschreibung an Dritte vorgenommen +hat (z.B. an eine Stiftung, ein Verlagshaus oder eine Zeitung) („Zuschreibungsempfänger"), +Namen bzw. Bezeichnung dieses oder dieser Dritten; + + ii. den Titel des Inhaltes; + +iii. in einer praktikablen Form den Uniform-Resource-Identifier (URI, z.B. +Internetadresse), den der Lizenzgeber zum Schutzgegenstand angegeben hat, +es sei denn, dieser URI verweist nicht auf den Rechtevermerk oder die Lizenzinformationen +zum Schutzgegenstand; + +iv. und im Falle einer Bearbeitung des Schutzgegenstandes in Übereinstimmung +mit Abschnitt 3.b) einen Hinweis darauf, dass es sich um eine Bearbeitung +handelt. + +Die nach diesem Abschnitt 4.b) erforderlichen Angaben können in jeder angemessenen +Form gemacht werden; im Falle einer Bearbeitung des Schutzgegenstandes oder +eines Sammelwerkes müssen diese Angaben das Minimum darstellen und bei gemeinsamer +Nennung aller Beitragenden dergestalt erfolgen, dass sie zumindest ebenso +hervorgehoben sind wie die Hinweise auf die übrigen Rechteinhaber. Die Angaben +nach diesem Abschnitt dürfen Sie ausschließlich zur Angabe der Rechteinhaberschaft +in der oben bezeichneten Weise verwenden. Durch die Ausübung Ihrer Rechte +aus dieser Lizenz dürfen Sie ohne eine vorherige, separat und schriftlich +vorliegende Zustimmung des Urhebers, des Lizenzgebers und/oder des Zuschreibungsempfängers +weder implizit noch explizit irgendeine Verbindung mit dem oder eine Unterstützung +oder Billigung durch den Urheber, den Lizenzgeber oder den Zuschreibungsempfänger +andeuten oder erklären. + +c. Die oben unter 4.a) und b) genannten Einschränkungen gelten nicht für solche +Teile des Schutzgegenstandes, die allein deshalb unter den Schutzgegenstandsbegriff +fallen, weil sie als Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen +Schutz eigener Art genießen. + +d. (Urheber)Persönlichkeitsrechte bleiben - soweit sie bestehen - von dieser +Lizenz unberührt. + + 5. Gewährleistung + +SOFERN KEINE ANDERS LAUTENDE, SCHRIFTLICHE VEREINBARUNG ZWISCHEN DEM LIZENZGEBER +UND IHNEN GESCHLOSSEN WURDE UND SOWEIT MÄNGEL NICHT ARGLISTIG VERSCHWIEGEN +WURDEN, BIETET DER LIZENZGEBER DEN SCHUTZGEGENSTAND UND DIE ERTEILUNG DER +NUTZUNGSBEWILLIGUNG UNTER AUSSCHLUSS JEGLICHER GEWÄHRLEISTUNG AN UND ÜBERNIMMT +WEDER AUSDRÜCKLICH NOCH KONKLUDENT GARANTIEN IRGENDEINER ART. DIES UMFASST +INSBESONDERE DAS FREISEIN VON SACH- UND RECHTSMÄNGELN, UNABHÄNGIG VON DEREN +ERKENNBARKEIT FÜR DEN LIZENZGEBER, DIE VERKEHRSFÄHIGKEIT DES SCHUTZGEGENSTANDES, +SEINE VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK SOWIE DIE KORREKTHEIT VON +BESCHREIBUNGEN. + + 6. Haftungsbeschränkung + +ÜBER DIE IN ZIFFER 5 GENANNTE GEWÄHRLEISTUNG HINAUS HAFTET DER LIZENZGEBER +IHNEN GEGENÜBER FÜR SCHÄDEN JEGLICHER ART NUR BEI GROBER FAHRLÄSSIGKEIT ODER +VORSATZ, UND ÜBERNIMMT DARÜBER HINAUS KEINERLEI FREIWILLIGE HAFTUNG FÜR FOLGE- +ODER ANDERE SCHÄDEN, AUCH WENN ER ÜBER DIE MÖGLICHKEIT IHRES EINTRITTS UNTERRICHTET +WURDE. + + 7. Erlöschen + +a. Diese Lizenz und die durch sie erteilte Nutzungsbewilligung erlöschen mit +Wirkung für die Zukunft im Falle eines Verstoßes gegen die Lizenzbedingungen +durch Sie, ohne dass es dazu der Kenntnis des Lizenzgebers vom Verstoß oder +einer weiteren Handlung einer der Vertragsparteien bedarf. Mit natürlichen +oder juristischen Personen, die Bearbeitungen des Schutzgegenstandes oder +diesen enthaltende Sammelwerke sowie entsprechende Vervielfältigungsstücke +unter den Bedingungen dieser Lizenz von Ihnen erhalten haben, bestehen nachträglich +entstandene Lizenzbeziehungen jedoch solange weiter, wie die genannten Personen +sich ihrerseits an sämtliche Lizenzbedingungen halten. Darüber hinaus gelten +die Ziffern 1, 2, 5, 6, 7, und 8 auch nach einem Erlöschen dieser Lizenz fort. + +b. Vorbehaltlich der oben genannten Bedingungen gilt diese Lizenz unbefristet +bis der rechtliche Schutz für den Schutzgegenstand ausläuft. Davon abgesehen +behält der Lizenzgeber das Recht, den Schutzgegenstand unter anderen Lizenzbedingungen +anzubieten oder die eigene Weitergabe des Schutzgegenstandes jederzeit einzustellen, +solange die Ausübung dieses Rechts nicht einer Kündigung oder einem Widerruf +dieser Lizenz (oder irgendeiner Weiterlizenzierung, die auf Grundlage dieser +Lizenz bereits erfolgt ist bzw. zukünftig noch erfolgen muss) dient und diese +Lizenz unter Berücksichtigung der oben zum Erlöschen genannten Bedingungen +vollumfänglich wirksam bleibt. + + 8. Sonstige Bestimmungen + +a. Jedes Mal wenn Sie den Schutzgegenstand für sich genommen oder als Teil +eines Sammelwerkes verbreiten oder öffentlich wiedergeben, bietet der Lizenzgeber +dem Empfänger eine Lizenz zu den gleichen Bedingungen und im gleichen Umfang +an, wie Ihnen in Form dieser Lizenz. + +b. Jedes Mal wenn Sie eine Bearbeitung des Schutzgegenstandes verbreiten oder +öffentlich wiedergeben, bietet der Lizenzgeber dem Empfänger eine Lizenz am +ursprünglichen Schutzgegenstand zu den gleichen Bedingungen und im gleichen +Umfang an, wie Ihnen in Form dieser Lizenz. + +c. Sollte eine Bestimmung dieser Lizenz unwirksam sein, so bleibt davon die +Wirksamkeit der Lizenz im Übrigen unberührt. + +d. Keine Bestimmung dieser Lizenz soll als abbedungen und kein Verstoß gegen +sie als zulässig gelten, solange die von dem Verzicht oder von dem Verstoß +betroffene Seite nicht schriftlich zugestimmt hat. + +e. Diese Lizenz (zusammen mit in ihr ausdrücklich vorgesehenen Erlaubnissen, +Mitteilungen und Zustimmungen, soweit diese tatsächlich vorliegen) stellt +die vollständige Vereinbarung zwischen dem Lizenzgeber und Ihnen in Bezug +auf den Schutzgegenstand dar. Es bestehen keine Abreden, Vereinbarungen oder +Erklärungen in Bezug auf den Schutzgegenstand, die in dieser Lizenz nicht +genannt sind. Rechtsgeschäftliche Änderungen des Verhältnisses zwischen dem +Lizenzgeber und Ihnen sind nur über Modifikationen dieser Lizenz möglich. +Der Lizenzgeber ist an etwaige zusätzliche, einseitig durch Sie übermittelte +Bestimmungen nicht gebunden. Diese Lizenz kann nur durch schriftliche Vereinbarung +zwischen Ihnen und dem Lizenzgeber modifiziert werden. Derlei Modifikationen +wirken ausschließlich zwischen dem Lizenzgeber und Ihnen und wirken sich nicht +auf die Dritten gemäß 8.a) und b) angebotenen Lizenzen aus. + +f. Sofern zwischen Ihnen und dem Lizenzgeber keine anderweitige Vereinbarung +getroffen wurde und soweit Wahlfreiheit besteht, findet auf diesen Lizenzvertrag +das Recht der Republik Österreich Anwendung. + +Creative Commons Notice + +Creative Commons ist nicht Partei dieser Lizenz und übernimmt keinerlei Gewähr +oder dergleichen in Bezug auf den Schutzgegenstand. Creative Commons haftet +Ihnen oder einer anderen Partei unter keinem rechtlichen Gesichtspunkt für +irgendwelche Schäden, die - abstrakt oder konkret, zufällig oder vorhersehbar +- im Zusammenhang mit dieser Lizenz entstehen. Unbeschadet der vorangegangen +beiden Sätze, hat Creative Commons alle Rechte und Pflichten eines Lizenzgebers, +wenn es sich ausdrücklich als Lizenzgeber im Sinne dieser Lizenz bezeichnet. + +Creative Commons gewährt den Parteien nur insoweit das Recht, das Logo und +die Marke "Creative Commons" zu nutzen, als dies notwendig ist, um der Öffentlichkeit +gegenüber kenntlich zu machen, dass der Schutzgegenstand unter einer CCPL +steht. Ein darüber hinaus gehender Gebrauch der Marke "Creative Commons" oder +einer verwandten Marke oder eines verwandten Logos bedarf der vorherigen schriftlichen +Zustimmung von Creative Commons. Jeder erlaubte Gebrauch richtet sich nach +der Creative Commons Marken-Nutzungs-Richtlinie in der jeweils aktuellen Fassung, +die von Zeit zu Zeit auf der Website veröffentlicht oder auf andere Weise +auf Anfrage zugänglich gemacht wird. Zur Klarstellung: Die genannten Einschränkungen +der Markennutzung sind nicht Bestandteil dieser Lizenz. + +Creative Commons kann kontaktiert werden über https://creativecommons.org/. diff --git a/options/license/CC-BY-NC-ND-3.0-IGO b/options/license/CC-BY-NC-ND-3.0-IGO new file mode 100644 index 000000000000..b56ae7284a01 --- /dev/null +++ b/options/license/CC-BY-NC-ND-3.0-IGO @@ -0,0 +1,295 @@ +Attribution-NonCommercial-NoDerivs 3.0 IGO + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL +SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT +RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. +CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND +DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. THE LICENSOR IS NOT +NECESSARILY AN INTERGOVERNMENTAL ORGANIZATION (IGO), AS DEFINED IN THE LICENSE +BELOW. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS +PUBLIC LICENSE ("LICENSE"). THE LICENSOR (DEFINED BELOW) HOLDS COPYRIGHT AND +OTHER RIGHTS IN THE WORK. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER +THIS LICENSE IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO +BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION FOR YOUR ACCEPTANCE AND AGREEMENT TO THE TERMS +OF THE LICENSE. + + 1. Definitions + +a. "IGO" means, solely and exclusively for purposes of this License, an organization +established by a treaty or other instrument governed by international law +and possessing its own international legal personality. Other organizations +established to carry out activities across national borders and that accordingly +enjoy immunity from legal process are also IGOs for the sole and exclusive +purposes of this License. IGOs may include as members, in addition to states, +other entities. + +b. "Work" means the literary and/or artistic work eligible for copyright protection, +whatever may be the mode or form of its expression including digital form, +and offered under the terms of this License. It is understood that a database, +which by reason of the selection and arrangement of its contents constitutes +an intellectual creation, is considered a Work. + +c. "Licensor" means the individual, individuals, entity or entities that offer(s) +the Work under the terms of this License and may be, but is not necessarily, +an IGO. + +d. "You" means an individual or entity exercising rights under this License. + +e. "Reproduce" means to make a copy of the Work in any manner or form, and +by any means. + +f. "Distribute" means the activity of making publicly available the Work (or +copies of the Work), as applicable, by sale, rental, public lending or any +other known form of transfer of ownership or possession of the Work or copy +of the Work. + +g. "Publicly Perform" means to perform public recitations of the Work and +to communicate to the public those public recitations, by any means or process, +including by wire or wireless means or public digital performances; to make +available to the public Works in such a way that members of the public may +access these Works from a place and at a place individually chosen by them; +to perform the Work to the public by any means or process and the communication +to the public of the performances of the Work, including by public digital +performance; to broadcast and rebroadcast the Work by any means including +signs, sounds or images. + +h. "Adaptation" means a work derived from or based upon the Work, or upon +the Work and other pre-existing works. Adaptations may include works such +as translations, derivative works, or any alterations and arrangements of +any kind involving the Work. For purposes of this License, where the Work +is a musical work, performance, or phonogram, the synchronization of the Work +in timed-relation with a moving image is an Adaptation. For the avoidance +of doubt, including the Work in a Collection is not an Adaptation. + +i. "Collection" means a collection of literary or artistic works or other +works or subject matter other than works listed in Section 1(b) which by reason +of the selection and arrangement of their contents, constitute intellectual +creations, in which the Work is included in its entirety in unmodified form +along with one or more other contributions, each constituting separate and +independent works in themselves, which together are assembled into a collective +whole. For the avoidance of doubt, a Collection will not be considered as +an Adaptation. + +2. Scope of this License. Nothing in this License is intended to reduce, limit, +or restrict any uses free from copyright protection. + +3. License Grant. Subject to the terms and conditions of this License, the +Licensor hereby grants You a worldwide, royalty-free, non-exclusive license +to exercise the rights in the Work as follows: + +a. to Reproduce, Distribute and Publicly Perform the Work, to incorporate +the Work into one or more Collections, and to Reproduce, Distribute and Publicly +Perform the Work as incorporated in the Collections. This License lasts for +the duration of the term of the copyright in the Work licensed by the Licensor. +The above rights may be exercised in all media and formats whether now known +or hereafter devised. The above rights include the right to make such modifications +as are technically necessary to exercise the rights in other media and formats, +but otherwise you have no rights to make Adaptations. All rights not expressly +granted by the Licensor are hereby reserved, including but not limited to +the rights set forth in Section 4(d). + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + +a. You may Distribute or Publicly Perform the Work only under the terms of +this License. You must include a copy of, or the Uniform Resource Identifier +(URI) for, this License with every copy of the Work You Distribute or Publicly +Perform. You may not offer or impose any terms on the Work that restrict the +terms of this License or the ability of the recipient of the Work to exercise +the rights granted to that recipient under the terms of the License. You may +not sublicense the Work (see section 8(a)). You must keep intact all notices +that refer to this License and to the disclaimer of warranties with every +copy of the Work You Distribute or Publicly Perform. When You Distribute or +Publicly Perform the Work, You may not impose any effective technological +measures on the Work that restrict the ability of a recipient of the Work +from You to exercise the rights granted to that recipient under the terms +of the License. This Section 4(a) applies to the Work as incorporated in a +Collection, but this does not require the Collection apart from the Work itself +to be made subject to the terms of this License. If You create a Collection, +upon notice from a Licensor You must, to the extent practicable, remove from +the Collection any credit (inclusive of any logo, trademark, official mark +or official emblem) as required by Section 4(c), as requested. + +b. You may not exercise any of the rights granted to You in Section 3 above +in any manner that is primarily intended for or directed toward commercial +advantage or private monetary compensation. The exchange of the Work for other +copyrighted works by means of digital file-sharing or otherwise shall not +be considered to be primarily intended for or directed toward commercial advantage +or private monetary compensation, provided there is no payment of any monetary +compensation in connection with the exchange of copyrighted works. + +c. If You Distribute, or Publicly Perform the Work or any Collections, You +must, unless a request has been made pursuant to Section 4(a), keep intact +all copyright notices for the Work and provide, reasonable to the medium or +means You are utilizing: (i) any attributions that the Licensor indicates +be associated with the Work as indicated in a copyright notice, (ii) the title +of the Work if supplied; (iii) to the extent reasonably practicable, the URI, +if any, that the Licensor specifies to be associated with the Work, unless +such URI does not refer to the copyright notice or licensing information for +the Work. The credit required by this Section 4(c) may be implemented in any +reasonable manner; provided, however, that in the case of a Collection, at +a minimum such credit will appear, if a credit for all contributors to the +Collection appears, then as part of these credits and in a manner at least +as prominent as the credits for the other contributors. For the avoidance +of doubt, You may only use the credit required by this Section for the purpose +of attribution in the manner set out above and, by exercising Your rights +under this License, You may not implicitly or explicitly assert or imply any +connection with, sponsorship or endorsement by the Licensor or others designated +for attribution, of You or Your use of the Work, without the separate, express +prior written permission of the Licensor or such others. + + d. For the avoidance of doubt: + +i. Non-waivable Compulsory License Schemes. In those jurisdictions in which +the right to collect royalties through any statutory or compulsory licensing +scheme cannot be waived, the Licensor reserves the exclusive right to collect +such royalties for any exercise by You of the rights granted under this License; + +ii. Waivable Compulsory License Schemes. In those jurisdictions in which the +right to collect royalties through any statutory or compulsory licensing scheme +can be waived, the Licensor reserves the exclusive right to collect such royalties +for any exercise by You of the rights granted under this License if Your exercise +of such rights is for a purpose or use which is otherwise than noncommercial +as permitted under Section 4(b) and otherwise waives the right to collect +royalties through any statutory or compulsory licensing scheme; and, + +iii. Voluntary License Schemes. To the extent possible, the Licensor waives +the right to collect royalties from You for the exercise of the Licensed Rights, +whether directly or through a collecting society under any voluntary licensing +scheme. In all other cases the Licensor expressly reserves the right to collect +such royalties. + +e. Except as otherwise agreed in writing by the Licensor, if You Reproduce, +Distribute or Publicly Perform the Work either by itself or as part of any +Collections, You must not distort, mutilate, modify or take other derogatory +action in relation to the Work which would be prejudicial to the honor or +reputation of the Licensor where moral rights apply. + +5. Representations, Warranties and Disclaimer THE LICENSOR OFFERS THE WORK +AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE +WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, +WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, +OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. + +6. Limitation on Liability IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU +ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR +EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN +IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 7. Termination + +a. Subject to the terms and conditions set forth in this License, the license +granted here lasts for the duration of the term of the copyright in the Work +licensed by the Licensor as stated in Section 3. Notwithstanding the above, +the Licensor reserves the right to release the Work under different license +terms or to stop distributing the Work at any time; provided, however that +any such election will not serve to withdraw this License (or any other license +that has been, or is required to be, granted under the terms of this License), +and this License will continue in full force and effect unless terminated +as stated below. + +b. If You fail to comply with this License, then this License and the rights +granted hereunder will terminate automatically upon any breach by You of the +terms of this License. Individuals or entities who have received Collections +from You under this License, however, will not have their licenses terminated +provided such individuals or entities remain in full compliance with those +licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this +License. Notwithstanding the foregoing, this License reinstates automatically +as of the date the violation is cured, provided it is cured within 30 days +of You discovering the violation, or upon express reinstatement by the Licensor. +For the avoidance of doubt, this Section 7(b) does not affect any rights the +Licensor may have to seek remedies for violations of this License by You. + + 8. Miscellaneous + +a. Each time You Distribute or Publicly Perform the Work or a Collection, +the Licensor offers to the recipient a license to the Work on the same terms +and conditions as the license granted to You under this License. + +b. If any provision of this License is invalid or unenforceable, it shall +not affect the validity or enforceability of the remainder of the terms of +this License, and without further action, such provision shall be reformed +to the minimum extent necessary to make such provision valid and enforceable. + +c. No term or provision of this License shall be deemed waived and no breach +consented to unless such waiver or consent shall be in writing and signed +by the Licensor. + +d. This License constitutes the entire agreement between You and the Licensor +with respect to the Work licensed here. There are no understandings, agreements +or representations with respect to the Work not specified here. The Licensor +shall not be bound by any additional provisions that may appear in any communication +from You. This License may not be modified without the mutual written agreement +of the Licensor and You. + +e. The rights granted under, and the subject matter referenced, in this License +were drafted utilizing the terminology of the Berne Convention for the Protection +of Literary and Artistic Works (as amended on September 28, 1979), the Rome +Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances +and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised +on July 24, 1971). Interpretation of the scope of the rights granted by the +Licensor and the conditions imposed on You under this License, this License, +and the rights and conditions set forth herein shall be made with reference +to copyright as determined in accordance with general principles of international +law, including the above mentioned conventions. + +f. Nothing in this License constitutes or may be interpreted as a limitation +upon or waiver of any privileges and immunities that may apply to the Licensor +or You, including immunity from the legal processes of any jurisdiction, national +court or other authority. + +g. Where the Licensor is an IGO, any and all disputes arising under this License +that cannot be settled amicably shall be resolved in accordance with the following +procedure: + +i. Pursuant to a notice of mediation communicated by reasonable means by either +You or the Licensor to the other, the dispute shall be submitted to non-binding +mediation conducted in accordance with rules designated by the Licensor in +the copyright notice published with the Work, or if none then in accordance +with those communicated in the notice of mediation. The language used in the +mediation proceedings shall be English unless otherwise agreed. + +ii. If any such dispute has not been settled within 45 days following the +date on which the notice of mediation is provided, either You or the Licensor +may, pursuant to a notice of arbitration communicated by reasonable means +to the other, elect to have the dispute referred to and finally determined +by arbitration. The arbitration shall be conducted in accordance with the +rules designated by the Licensor in the copyright notice published with the +Work, or if none then in accordance with the UNCITRAL Arbitration Rules as +then in force. The arbitral tribunal shall consist of a sole arbitrator and +the language of the proceedings shall be English unless otherwise agreed. +The place of arbitration shall be where the Licensor has its headquarters. +The arbitral proceedings shall be conducted remotely (e.g., via telephone +conference or written submissions) whenever practicable. + +iii. Interpretation of this License in any dispute submitted to mediation +or arbitration shall be as set forth in Section 8(e), above. + +Creative Commons Notice + +Creative Commons is not a party to this License, and makes no warranty whatsoever +in connection with the Work. Creative Commons will not be liable to You or +any party on any legal theory for any damages whatsoever, including without +limitation any general, special, incidental or consequential damages arising +in connection to this license. Notwithstanding the foregoing two (2) sentences, +if Creative Commons has expressly identified itself as the Licensor hereunder, +it shall have all rights and obligations of the Licensor. + +Except for the limited purpose of indicating to the public that the Work is +licensed under the CCPL, Creative Commons does not authorize the use by either +party of the trademark "Creative Commons" or any related trademark or logo +of Creative Commons without the prior written consent of Creative Commons. +Any permitted use will be in compliance with Creative Commons' then-current +trademark usage guidelines, as may be published on its website or otherwise +made available upon request from time to time. For the avoidance of doubt, +this trademark restriction does not form part of this License. + +Creative Commons may be contacted at https://creativecommons.org/. diff --git a/options/license/CC-BY-SA-3.0-AT b/options/license/CC-BY-SA-3.0-AT new file mode 100644 index 000000000000..fb39b2e5844b --- /dev/null +++ b/options/license/CC-BY-SA-3.0-AT @@ -0,0 +1,377 @@ +CREATIVE COMMONS IST KEINE RECHTSANWALTSKANZLEI UND LEISTET KEINE RECHTSBERATUNG. +DIE BEREITSTELLUNG DIESER LIZENZ FÜHRT ZU KEINEM MANDATSVERHÄLTNIS. CREATIVE +COMMONS STELLT DIESE INFORMATIONEN OHNE GEWÄHR ZUR VERFÜGUNG. CREATIVE COMMONS +ÜBERNIMMT KEINE GEWÄHRLEISTUNG FÜR DIE GELIEFERTEN INFORMATIONEN UND SCHLIEßT +DIE HAFTUNG FÜR SCHÄDEN AUS, DIE SICH AUS DEREN GEBRAUCH ERGEBEN. Lizenz + +DER GEGENSTAND DIESER LIZENZ (WIE UNTER „SCHUTZGEGENSTAND" DEFINIERT) WIRD +UNTER DEN BEDINGUNGEN DIESER CREATIVE COMMONS PUBLIC LICENSE ("CCPL", „LIZENZ" +ODER "LIZENZVERTRAG") ZUR VERFÜGUNG GESTELLT. DER SCHUTZGEGENSTAND IST DURCH +DAS URHEBERRECHT UND/ODER ANDERE GESETZE GESCHÜTZT. JEDE FORM DER NUTZUNG +DES SCHUTZGEGENSTANDES, DIE NICHT AUFGRUND DIESER LIZENZ ODER DURCH GESETZE +GESTATTET IST, IST UNZULÄSSIG. + +DURCH DIE AUSÜBUNG EINES DURCH DIESE LIZENZ GEWÄHRTEN RECHTS AN DEM SCHUTZGEGENSTAND +ERKLÄREN SIE SICH MIT DEN LIZENZBEDINGUNGEN RECHTSVERBINDLICH EINVERSTANDEN. +SOWEIT DIESE LIZENZ ALS LIZENZVERTRAG ANZUSEHEN IST, GEWÄHRT IHNEN DER LIZENZGEBER +DIE IN DER LIZENZ GENANNTEN RECHTE UNENTGELTLICH UND IM AUSTAUSCH DAFÜR, DASS +SIE DAS GEBUNDENSEIN AN DIE LIZENZBEDINGUNGEN AKZEPTIEREN. + + 1. Definitionen + +a. Der Begriff "Bearbeitung" im Sinne dieser Lizenz bezeichnet das Ergebnis +jeglicher Art von Veränderung des Schutzgegenstandes, solange dieses erkennbar +vom Schutzgegenstand abgeleitet wurde. Dies kann insbesondere auch eine Umgestaltung, +Änderung, Anpassung, Übersetzung oder Heranziehung des Schutzgegenstandes +zur Vertonung von Laufbildern sein. Nicht als Bearbeitung des Schutzgegenstandes +gelten seine Aufnahme in eine Sammlung oder ein Sammelwerk und die freie Nutzung +des Schutzgegenstandes. + +b. Der Begriff "Sammelwerk" im Sinne dieser Lizenz meint eine Zusammenstellung +von literarischen, künstlerischen oder wissenschaftlichen Inhalten zu einem +einheitlichen Ganzen, sofern diese Zusammenstellung aufgrund von Auswahl und +Anordnung der darin enthaltenen selbständigen Elemente eine eigentümliche +geistige Schöpfung darstellt, unabhängig davon, ob die Elemente systematisch +oder methodisch angelegt und dadurch einzeln zugänglich sind oder nicht. + +c. "Verbreiten" im Sinne dieser Lizenz bedeutet, den Schutzgegenstand oder +Bearbeitungen im Original oder in Form von Vervielfältigungsstücken, mithin +in körperlich fixierter Form der Öffentlichkeit zugänglich zu machen oder +in Verkehr zu bringen. + +d. Unter "Lizenzelementen" werden im Sinne dieser Lizenz die folgenden übergeordneten +Lizenzcharakteristika verstanden, die vom Lizenzgeber ausgewählt wurden und +in der Bezeichnung der Lizenz zum Ausdruck kommen: "Namensnennung", "Weitergabe +unter gleichen Bedingungen". + +e. Der "Lizenzgeber" im Sinne dieser Lizenz ist diejenige natürliche oder +juristische Person oder Gruppe, die den Schutzgegenstand unter den Bedingungen +dieser Lizenz anbietet und insoweit als Rechteinhaberin auftritt. + +f. "Rechteinhaber" im Sinne dieser Lizenz ist der Urheber des Schutzgegenstandes +oder jede andere natürliche oder juristische Person, die am Schutzgegenstand +ein Immaterialgüterrecht erlangt hat, welches die in Abschnitt 3 genannten +Handlungen erfasst und eine Erteilung, Übertragung oder Einräumung von Nutzungsbewilligungen +bzw Nutzungsrechten an Dritte erlaubt. + +g. Der Begriff "Schutzgegenstand" bezeichnet in dieser Lizenz den literarischen, +künstlerischen oder wissenschaftlichen Inhalt, der unter den Bedingungen dieser +Lizenz angeboten wird. Das kann insbesondere eine eigentümliche geistige Schöpfung +jeglicher Art oder ein Werk der kleinen Münze, ein nachgelassenes Werk oder +auch ein Lichtbild oder anderes Objekt eines verwandten Schutzrechts sein, +unabhängig von der Art seiner Fixierung und unabhängig davon, auf welche Weise +jeweils eine Wahrnehmung erfolgen kann, gleichviel ob in analoger oder digitaler +Form. Soweit Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen +Schutz eigener Art genießen, unterfallen auch sie dem Begriff „Schutzgegenstand" +im Sinne dieser Lizenz. + +h. Mit "Sie" bzw. "Ihnen" ist die natürliche oder juristische Person gemeint, +die in dieser Lizenz im Abschnitt 3 genannte Nutzungen des Schutzgegenstandes +vornimmt und zuvor in Hinblick auf den Schutzgegenstand nicht gegen Bedingungen +dieser Lizenz verstoßen oder aber die ausdrückliche Erlaubnis des Lizenzgebers +erhalten hat, die durch diese Lizenz gewährte Nutzungsbewilligung trotz eines +vorherigen Verstoßes auszuüben. + +i. Unter "Öffentlich Wiedergeben" im Sinne dieser Lizenz sind Wahrnehmbarmachungen +des Schutzgegenstandes in unkörperlicher Form zu verstehen, die für eine Mehrzahl +von Mitgliedern der Öffentlichkeit bestimmt sind und mittels öffentlicher +Wiedergabe in Form von Vortrag, Aufführung, Vorführung, Darbietung, Sendung, +Weitersendung oder zeit- und ortsunabhängiger Zurverfügungstellung erfolgen, +unabhängig von den zum Einsatz kommenden Techniken und Verfahren, einschließlich +drahtgebundener oder drahtloser Mittel und Einstellen in das Internet. + +j. "Vervielfältigen" im Sinne dieser Lizenz bedeutet, gleichviel in welchem +Verfahren, auf welchem Träger, in welcher Menge und ob vorübergehend oder +dauerhaft, Vervielfältigungsstücke des Schutzgegenstandes herzustellen, insbesondere +durch Ton- oder Bildaufzeichnungen, und umfasst auch das erstmalige Festhalten +des Schutzgegenstandes oder dessen Wahrnehmbarmachung auf Mitteln der wiederholbaren +Wiedergabe sowie das Herstellen von Vervielfältigungsstücken dieser Festhaltung, +sowie die Speicherung einer geschützten Darbietung oder eines Bild- und/oder +Schallträgers in digitaler Form oder auf einem anderen elektronischen Medium. + +k. "Mit Creative Commons kompatible Lizenz" bezeichnet eine Lizenz, die unter +https://creativecommons.org/compatiblelicenses aufgelistet ist und die durch +Creative Commons als grundsätzlich zur vorliegenden Lizenz äquivalent akzeptiert +wurde, da zumindest folgende Voraussetzungen erfüllt sind: + + Diese mit Creative Commons kompatible Lizenz + +i. enthält Bestimmungen, welche die gleichen Ziele verfolgen, die gleiche +Bedeutung haben und die gleichen Wirkungen erzeugen wie die Lizenzelemente +der vorliegenden Lizenz; und + +ii. erlaubt ausdrücklich das Lizenzieren von ihr unterstellten Abwandlungen +unter vorliegender Lizenz, unter einer anderen rechtsordnungsspezifisch angepassten +Creative-Commons-Lizenz mit denselben Lizenzelementen wie vorliegende Lizenz +aufweist oder unter der entsprechenden Creative-Commons-Unported-Lizenz. + + 2. Beschränkungen der Verwertungsrechte + +Diese Lizenz ist in keiner Weise darauf gerichtet, Befugnisse zur Nutzung +des Schutzgegenstandes zu vermindern, zu beschränken oder zu vereiteln, die +sich aus den Beschränkungen der Verwertungsrechte, anderen Beschränkungen +der Ausschließlichkeitsrechte des Rechtsinhabers oder anderen entsprechenden +Rechtsnormen oder sich aus dem Fehlen eines immaterialgüterrechtlichen Schutzes +ergeben. + + 3. Lizenzierung + +Unter den Bedingungen dieser Lizenz erteilt Ihnen der Lizenzgeber - unbeschadet +unverzichtbarer Rechte und vorbehaltlich des Abschnitts 3.e) - die vergütungsfreie, +räumlich und zeitlich (für die Dauer des Urheberrechts oder verwandten Schutzrechts +am Schutzgegenstand) unbeschränkte Nutzungsbewilligung, den Schutzgegenstand +in der folgenden Art und Weise zu nutzen: + +a. Den Schutzgegenstand in beliebiger Form und Menge zu vervielfältigen, ihn +in Sammelwerke zu integrieren und ihn als Teil solcher Sammelwerke zu vervielfältigen; + +b. Den Schutzgegenstand zu bearbeiten, einschließlich Übersetzungen unter +Nutzung jedweder Medien anzufertigen, sofern deutlich erkennbar gemacht wird, +dass es sich um eine Bearbeitung handelt; + +c. Den Schutzgegenstand, allein oder in Sammelwerke aufgenommen, öffentlich +wiederzugeben und zu verbreiten; und + +d. Bearbeitungen des Schutzgegenstandes zu veröffentlichen, öffentlich wiederzugeben +und zu verbreiten. + +e. Bezüglich Vergütung für die Nutzung des Schutzgegenstandes gilt Folgendes: + +i. Unverzichtbare gesetzliche Vergütungsansprüche: Soweit unverzichtbare Vergütungsansprüche +im Gegenzug für gesetzliche Lizenzen vorgesehen oder Pauschalabgabensysteme +(zum Beispiel für Leermedien) vorhanden sind, behält sich der Lizenzgeber +das ausschließliche Recht vor, die entsprechenden Vergütungsansprüche für +jede Ausübung eines Rechts aus dieser Lizenz durch Sie geltend zu machen. + +ii. Vergütung bei Zwangslizenzen: Soweit Zwangslizenzen außerhalb dieser Lizenz +vorgesehen sind und zustande kommen, verzichtet der Lizenzgeber für alle Fälle +einer lizenzgerechten Nutzung des Schutzgegenstandes durch Sie auf jegliche +Vergütung. + +iii. Vergütung in sonstigen Fällen: Bezüglich lizenzgerechter Nutzung des +Schutzgegenstandes durch Sie, die nicht unter die beiden vorherigen Abschnitte +(i) und (ii) fällt, verzichtet der Lizenzgeber auf jegliche Vergütung, unabhängig +davon, ob eine Geltendmachung der Vergütungsansprüche durch ihn selbst oder +nur durch eine Verwertungsgesellschaft möglich wäre. + +Die vorgenannte Nutzungsbewilligung wird für alle bekannten sowie alle noch +nicht bekannten Nutzungsarten eingeräumt. Sie beinhaltet auch das Recht, solche +Änderungen am Schutzgegenstand vorzunehmen, die für bestimmte nach dieser +Lizenz zulässige Nutzungen technisch erforderlich sind. Alle sonstigen Rechte, +die über diesen Abschnitt hinaus nicht ausdrücklich vom Lizenzgeber eingeräumt +werden, bleiben diesem allein vorbehalten. Soweit Datenbanken oder Zusammenstellungen +von Daten Schutzgegenstand dieser Lizenz oder Teil dessen sind und einen immaterialgüterrechtlichen +Schutz eigener Art genießen, verzichtet der Lizenzgeber auf die Geltendmachung +sämtlicher daraus resultierender Rechte. + + 4. Bedingungen + +Die Erteilung der Nutzungsbewilligung gemäß Abschnitt 3 dieser Lizenz erfolgt +ausdrücklich nur unter den folgenden Bedingungen: + +a. Sie dürfen den Schutzgegenstand ausschließlich unter den Bedingungen dieser +Lizenz verbreiten oder öffentlich wiedergeben. Sie müssen dabei stets eine +Kopie dieser Lizenz oder deren vollständige Internetadresse in Form des Uniform-Resource-Identifier +(URI) beifügen. Sie dürfen keine Vertrags- oder Nutzungsbedingungen anbieten +oder fordern, die die Bedingungen dieser Lizenz oder die durch diese Lizenz +gewährten Rechte beschränken. Sie dürfen den Schutzgegenstand nicht unterlizenzieren. +Bei jeder Kopie des Schutzgegenstandes, die Sie verbreiten oder öffentlich +wiedergeben, müssen Sie alle Hinweise unverändert lassen, die auf diese Lizenz +und den Haftungsausschluss hinweisen. Wenn Sie den Schutzgegenstand verbreiten +oder öffentlich wiedergeben, dürfen Sie (in Bezug auf den Schutzgegenstand) +keine technischen Maßnahmen ergreifen, die den Nutzer des Schutzgegenstandes +in der Ausübung der ihm durch diese Lizenz gewährten Rechte behindern können. +Dasselbe gilt auch für den Fall, dass der Schutzgegenstand einen Bestandteil +eines Sammelwerkes bildet, was jedoch nicht bedeutet, dass das Sammelwerk +insgesamt dieser Lizenz unterstellt werden muss. Sofern Sie ein Sammelwerk +erstellen, müssen Sie - soweit dies praktikabel ist - auf die Mitteilung eines +Lizenzgebers hin aus dem Sammelwerk die in Abschnitt 4.c) aufgezählten Hinweise +entfernen. Wenn Sie eine Bearbeitung vornehmen, müssen Sie – soweit dies praktikabel +ist – auf die Mitteilung eines Lizenzgebers hin von der Bearbeitung die in +Abschnitt 4.c) aufgezählten Hinweise entfernen. + + b. Sie dürfen eine Bearbeitung ausschließlich unter den Bedingungen + + i. dieser Lizenz, + +ii. einer späteren Version dieser Lizenz mit denselben Lizenzelementen, + +iii. einer rechtsordnungsspezifischen Creative-Commons-Lizenz mit denselben +Lizenzelementen ab Version 3.0 aufwärts (z.B. Namensnennung - Weitergabe unter +gleichen Bedingungen 3.0 US), + +iv. der Creative-Commons-Unported-Lizenz mit denselben Lizenzelementen ab +Version 3.0 aufwärts, oder + + v. einer mit Creative Commons kompatiblen Lizenz + + verbreiten oder öffentlich wiedergeben. + +Falls Sie die Bearbeitung gemäß Abschnitt b)(v) unter einer mit Creative Commons +kompatiblen Lizenz lizenzieren, müssen Sie deren Lizenzbestimmungen Folge +leisten. + +Falls Sie die Bearbeitung unter einer der unter b)(i)-(iv) genannten Lizenzen +("Verwendbare Lizenzen") lizenzieren, müssen Sie deren Lizenzbestimmungen +sowie folgenden Bestimmungen Folge leisten: Sie müssen stets eine Kopie der +verwendbaren Lizenz oder deren vollständige Internetadresse in Form des Uniform-Resource-Identifier +(URI) beifügen, wenn Sie die Bearbeitung verbreiten oder öffentlich wiedergeben. +Sie dürfen keine Vertrags- oder Nutzungsbedingungen anbieten oder fordern, +die die Bedingungen der verwendbaren Lizenz oder die durch sie gewährten Rechte +beschränken. Bei jeder Bearbeitung, die Sie verbreiten oder öffentlich wiedergeben, +müssen Sie alle Hinweise auf die verwendbare Lizenz und den Haftungsausschluss +unverändert lassen. Wenn Sie die Bearbeitung verbreiten oder öffentlich wiedergeben, +dürfen Sie (in Bezug auf die Bearbeitung) keine technischen Maßnahmen ergreifen, +die den Nutzer der Bearbeitung in der Ausübung der ihm durch die verwendbare +Lizenz gewährten Rechte behindern können. Dieser Abschnitt 4.b) gilt auch +für den Fall, dass die Bearbeitung einen Bestandteil eines Sammelwerkes bildet; +dies bedeutet jedoch nicht, dass das Sammelwerk insgesamt der verwendbaren +Lizenz unterstellt werden muss. + +c. Die Verbreitung und die öffentliche Wiedergabe des Schutzgegenstandes oder +auf ihm aufbauender Inhalte oder ihn enthaltender Sammelwerke ist Ihnen nur +unter der Bedingung gestattet, dass Sie, vorbehaltlich etwaiger Mitteilungen +im Sinne von Abschnitt 4.a), alle dazu gehörenden Rechtevermerke unberührt +lassen. Sie sind verpflichtet, die Urheberschaft oder die Rechteinhaberschaft +in einer der Nutzung entsprechenden, angemessenen Form anzuerkennen, indem +Sie selbst – soweit bekannt – Folgendes angeben: + +i. Den Namen (oder das Pseudonym, falls ein solches verwendet wird) des Rechteinhabers, +und/oder falls der Lizenzgeber im Rechtevermerk, in den Nutzungsbedingungen +oder auf andere angemessene Weise eine Zuschreibung an Dritte vorgenommen +hat (z.B. an eine Stiftung, ein Verlagshaus oder eine Zeitung) („Zuschreibungsempfänger"), +Namen bzw. Bezeichnung dieses oder dieser Dritten; + + ii. den Titel des Inhaltes; + +iii. in einer praktikablen Form den Uniform-Resource-Identifier (URI, z.B. +Internetadresse), den der Lizenzgeber zum Schutzgegenstand angegeben hat, +es sei denn, dieser URI verweist nicht auf den Rechtevermerk oder die Lizenzinformationen +zum Schutzgegenstand; + +iv. und im Falle einer Bearbeitung des Schutzgegenstandes in Übereinstimmung +mit Abschnitt 3.b) einen Hinweis darauf, dass es sich um eine Bearbeitung +handelt. + +Die nach diesem Abschnitt 4.c) erforderlichen Angaben können in jeder angemessenen +Form gemacht werden; im Falle einer Bearbeitung des Schutzgegenstandes oder +eines Sammelwerkes müssen diese Angaben das Minimum darstellen und bei gemeinsamer +Nennung aller Beitragenden dergestalt erfolgen, dass sie zumindest ebenso +hervorgehoben sind wie die Hinweise auf die übrigen Rechteinhaber. Die Angaben +nach diesem Abschnitt dürfen Sie ausschließlich zur Angabe der Rechteinhaberschaft +in der oben bezeichneten Weise verwenden. Durch die Ausübung Ihrer Rechte +aus dieser Lizenz dürfen Sie ohne eine vorherige, separat und schriftlich +vorliegende Zustimmung des Urhebers, des Lizenzgebers und/oder des Zuschreibungsempfängers +weder implizit noch explizit irgendeine Verbindung mit dem oder eine Unterstützung +oder Billigung durch den Lizenzgeber oder den Zuschreibungsempfänger andeuten +oder erklären. + +d. Die oben unter 4.a) bis c) genannten Einschränkungen gelten nicht für solche +Teile des Schutzgegenstandes, die allein deshalb unter den Schutzgegenstandsbegriff +fallen, weil sie als Datenbanken oder Zusammenstellungen von Daten einen immaterialgüterrechtlichen +Schutz eigener Art genießen. + +e. (Urheber)Persönlichkeitsrechte bleiben - soweit sie bestehen - von dieser +Lizenz unberührt. + + 5. Gewährleistung + +SOFERN KEINE ANDERS LAUTENDE, SCHRIFTLICHE VEREINBARUNG ZWISCHEN DEM LIZENZGEBER +UND IHNEN GESCHLOSSEN WURDE UND SOWEIT MÄNGEL NICHT ARGLISTIG VERSCHWIEGEN +WURDEN, BIETET DER LIZENZGEBER DEN SCHUTZGEGENSTAND UND DIE ERTEILUNG DER +NUTZUNGSBEWILLIGUNG UNTER AUSSCHLUSS JEGLICHER GEWÄHRLEISTUNG AN UND ÜBERNIMMT +WEDER AUSDRÜCKLICH NOCH KONKLUDENT GARANTIEN IRGENDEINER ART. DIES UMFASST +INSBESONDERE DAS FREISEIN VON SACH- UND RECHTSMÄNGELN, UNABHÄNGIG VON DEREN +ERKENNBARKEIT FÜR DEN LIZENZGEBER, DIE VERKEHRSFÄHIGKEIT DES SCHUTZGEGENSTANDES, +SEINE VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK SOWIE DIE KORREKTHEIT VON +BESCHREIBUNGEN. + + 6. Haftungsbeschränkung + +ÜBER DIE IN ZIFFER 5 GENANNTE GEWÄHRLEISTUNG HINAUS HAFTET DER LIZENZGEBER +IHNEN GEGENÜBER FÜR SCHÄDEN JEGLICHER ART NUR BEI GROBER FAHRLÄSSIGKEIT ODER +VORSATZ, UND ÜBERNIMMT DARÜBER HINAUS KEINERLEI FREIWILLIGE HAFTUNG FÜR FOLGE- +ODER ANDERE SCHÄDEN, AUCH WENN ER ÜBER DIE MÖGLICHKEIT IHRES EINTRITTS UNTERRICHTET +WURDE. + + 7. Erlöschen + +a. Diese Lizenz und die durch sie erteilte Nutzungsbewilligung erlöschen mit +Wirkung für die Zukunft im Falle eines Verstoßes gegen die Lizenzbedingungen +durch Sie, ohne dass es dazu der Kenntnis des Lizenzgebers vom Verstoß oder +einer weiteren Handlung einer der Vertragsparteien bedarf. Mit natürlichen +oder juristischen Personen, die Bearbeitungen des Schutzgegenstandes oder +diesen enthaltende Sammelwerke sowie entsprechende Vervielfältigungsstücke +unter den Bedingungen dieser Lizenz von Ihnen erhalten haben, bestehen nachträglich +entstandene Lizenzbeziehungen jedoch solange weiter, wie die genannten Personen +sich ihrerseits an sämtliche Lizenzbedingungen halten. Darüber hinaus gelten +die Ziffern 1, 2, 5, 6, 7, und 8 auch nach einem Erlöschen dieser Lizenz fort. + +b. Vorbehaltlich der oben genannten Bedingungen gilt diese Lizenz unbefristet +bis der rechtliche Schutz für den Schutzgegenstand ausläuft. Davon abgesehen +behält der Lizenzgeber das Recht, den Schutzgegenstand unter anderen Lizenzbedingungen +anzubieten oder die eigene Weitergabe des Schutzgegenstandes jederzeit einzustellen, +solange die Ausübung dieses Rechts nicht einer Kündigung oder einem Widerruf +dieser Lizenz (oder irgendeiner Weiterlizenzierung, die auf Grundlage dieser +Lizenz bereits erfolgt ist bzw. zukünftig noch erfolgen muss) dient und diese +Lizenz unter Berücksichtigung der oben zum Erlöschen genannten Bedingungen +vollumfänglich wirksam bleibt. + + 8. Sonstige Bestimmungen + +a. Jedes Mal wenn Sie den Schutzgegenstand für sich genommen oder als Teil +eines Sammelwerkes verbreiten oder öffentlich wiedergeben, bietet der Lizenzgeber +dem Empfänger eine Lizenz zu den gleichen Bedingungen und im gleichen Umfang +an, wie Ihnen in Form dieser Lizenz. + +b. Jedes Mal wenn Sie eine Bearbeitung des Schutzgegenstandes verbreiten oder +öffentlich wiedergeben, bietet der Lizenzgeber dem Empfänger eine Lizenz am +ursprünglichen Schutzgegenstand zu den gleichen Bedingungen und im gleichen +Umfang an, wie Ihnen in Form dieser Lizenz. + +c. Sollte eine Bestimmung dieser Lizenz unwirksam sein, so bleibt davon die +Wirksamkeit der Lizenz im Übrigen unberührt. + +d. Keine Bestimmung dieser Lizenz soll als abbedungen und kein Verstoß gegen +sie als zulässig gelten, solange die von dem Verzicht oder von dem Verstoß +betroffene Seite nicht schriftlich zugestimmt hat. + +e. Diese Lizenz (zusammen mit in ihr ausdrücklich vorgesehenen Erlaubnissen, +Mitteilungen und Zustimmungen, soweit diese tatsächlich vorliegen) stellt +die vollständige Vereinbarung zwischen dem Lizenzgeber und Ihnen in Bezug +auf den Schutzgegenstand dar. Es bestehen keine Abreden, Vereinbarungen oder +Erklärungen in Bezug auf den Schutzgegenstand, die in dieser Lizenz nicht +genannt sind. Rechtsgeschäftliche Änderungen des Verhältnisses zwischen dem +Lizenzgeber und Ihnen sind nur über Modifikationen dieser Lizenz möglich. +Der Lizenzgeber ist an etwaige zusätzliche, einseitig durch Sie übermittelte +Bestimmungen nicht gebunden. Diese Lizenz kann nur durch schriftliche Vereinbarung +zwischen Ihnen und dem Lizenzgeber modifiziert werden. Derlei Modifikationen +wirken ausschließlich zwischen dem Lizenzgeber und Ihnen und wirken sich nicht +auf die Dritten gemäß 8.a) und b) angebotenen Lizenzen aus. + +f. Sofern zwischen Ihnen und dem Lizenzgeber keine anderweitige Vereinbarung +getroffen wurde und soweit Wahlfreiheit besteht, findet auf diesen Lizenzvertrag +das Recht der Republik Österreich Anwendung. + +Creative Commons Notice + +Creative Commons ist nicht Partei dieser Lizenz und übernimmt keinerlei Gewähr +oder dergleichen in Bezug auf den Schutzgegenstand. Creative Commons haftet +Ihnen oder einer anderen Partei unter keinem rechtlichen Gesichtspunkt für +irgendwelche Schäden, die - abstrakt oder konkret, zufällig oder vorhersehbar +- im Zusammenhang mit dieser Lizenz entstehen. Unbeschadet der vorangegangen +beiden Sätze, hat Creative Commons alle Rechte und Pflichten eines Lizenzgebers, +wenn es sich ausdrücklich als Lizenzgeber im Sinne dieser Lizenz bezeichnet. + +Creative Commons gewährt den Parteien nur insoweit das Recht, das Logo und +die Marke "Creative Commons" zu nutzen, als dies notwendig ist, um der Öffentlichkeit +gegenüber kenntlich zu machen, dass der Schutzgegenstand unter einer CCPL +steht. Ein darüber hinaus gehender Gebrauch der Marke "Creative Commons" oder +einer verwandten Marke oder eines verwandten Logos bedarf der vorherigen schriftlichen +Zustimmung von Creative Commons. Jeder erlaubte Gebrauch richtet sich nach +der Creative Commons Marken-Nutzungs-Richtlinie in der jeweils aktuellen Fassung, +die von Zeit zu Zeit auf der Website veröffentlicht oder auf andere Weise +auf Anfrage zugänglich gemacht wird. Zur Klarstellung: Die genannten Einschränkungen +der Markennutzung sind nicht Bestandteil dieser Lizenz. + +Creative Commons kann kontaktiert werden über https://creativecommons.org/. diff --git a/options/license/CERN-OHL-P-2.0 b/options/license/CERN-OHL-P-2.0 new file mode 100644 index 000000000000..941456de59a4 --- /dev/null +++ b/options/license/CERN-OHL-P-2.0 @@ -0,0 +1,168 @@ +CERN Open Hardware Licence Version 2 - Permissive + +Preamble + +CERN has developed this licence to promote collaboration among hardware designers +and to provide a legal tool which supports the freedom to use, study, modify, +share and distribute hardware designs and products based on those designs. +Version 2 of the CERN Open Hardware Licence comes in three variants: this +licence, CERN-OHL-P (permissive); and two reciprocal licences: CERN- OHL-W +(weakly reciprocal) and CERN-OHL-S (strongly reciprocal). + +The CERN-OHL-P is copyright CERN 2020. Anyone is welcome to use it, in unmodified +form only. + +Use of this Licence does not imply any endorsement by CERN of any Licensor +or their designs nor does it imply any involvement by CERN in their development. + + 1 Definitions + + 1.1 'Licence' means this CERN-OHL-P. + +1.2 'Source' means information such as design materials or digital code which +can be applied to Make or test a Product or to prepare a Product for use, +Conveyance or sale, regardless of its medium or how it is expressed. It may +include Notices. + +1.3 'Covered Source' means Source that is explicitly made available under +this Licence. + +1.4 'Product' means any device, component, work or physical object, whether +in finished or intermediate form, arising from the use, application or processing +of Covered Source. + +1.5 'Make' means to create or configure something, whether by manufacture, +assembly, compiling, loading or applying Covered Source or another Product +or otherwise. + +1.6 'Notice' means copyright, acknowledgement and trademark notices, references +to the location of any Notices, modification notices (subsection 3.3(b)) and +all notices that refer to this Licence and to the disclaimer of warranties +that are included in the Covered Source. + +1.7 'Licensee' or 'You' means any person exercising rights under this Licence. + +1.8 'Licensor' means a person who creates Source or modifies Covered Source +and subsequently Conveys the resulting Covered Source under the terms and +conditions of this Licence. A person may be a Licensee and a Licensor at the +same time. + + 1.9 'Convey' means to communicate to the public or distribute. + + 2 Applicability + +2.1 This Licence governs the use, copying, modification, Conveying of Covered +Source and Products, and the Making of Products. By exercising any right granted +under this Licence, You irrevocably accept these terms and conditions. + +2.2 This Licence is granted by the Licensor directly to You, and shall apply +worldwide and without limitation in time. + +2.3 You shall not attempt to restrict by contract or otherwise the rights +granted under this Licence to other Licensees. + +2.4 This Licence is not intended to restrict fair use, fair dealing, or any +other similar right. + + 3 Copying, modifying and Conveying Covered Source + +3.1 You may copy and Convey verbatim copies of Covered Source, in any medium, +provided You retain all Notices. + + 3.2 You may modify Covered Source, other than Notices. + +You may only delete Notices if they are no longer applicable to the corresponding +Covered Source as modified by You and You may add additional Notices applicable +to Your modifications. + +3.3 You may Convey modified Covered Source (with the effect that You shall +also become a Licensor) provided that You: + + a) retain Notices as required in subsection 3.2; and + +b) add a Notice to the modified Covered Source stating that You have modified +it, with the date and brief description of how You have modified it. + +3.4 You may Convey Covered Source or modified Covered Source under licence +terms which differ from the terms of this Licence provided that: + + a) You comply at all times with subsection 3.3; and + +b) You provide a copy of this Licence to anyone to whom You Convey Covered +Source or modified Covered Source. + + 4 Making and Conveying Products + +You may Make Products, and/or Convey them, provided that You ensure that the +recipient of the Product has access to any Notices applicable to the Product. + + 5 DISCLAIMER AND LIABILITY + +5.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products are provided +'as is' and any express or implied warranties, including, but not limited +to, implied warranties of merchantability, of satisfactory quality, non-infringement +of third party rights, and fitness for a particular purpose or use are disclaimed +in respect of any Source or Product to the maximum extent permitted by law. +The Licensor makes no representation that any Source or Product does not or +will not infringe any patent, copyright, trade secret or other proprietary +right. The entire risk as to the use, quality, and performance of any Source +or Product shall be with You and not the Licensor. This disclaimer of warranty +is an essential part of this Licence and a condition for the grant of any +rights granted under this Licence. + +5.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to the maximum +extent permitted by law, have no liability for direct, indirect, special, +incidental, consequential, exemplary, punitive or other damages of any character +including, without limitation, procurement of substitute goods or services, +loss of use, data or profits, or business interruption, however caused and +on any theory of contract, warranty, tort (including negligence), product +liability or otherwise, arising in any way in relation to the Covered Source, +modified Covered Source and/or the Making or Conveyance of a Product, even +if advised of the possibility of such damages, and You shall hold the Licensor(s) +free and harmless from any liability, costs, damages, fees and expenses, including +claims by third parties, in relation to such use. + + 6 Patents + +6.1 Subject to the terms and conditions of this Licence, each Licensor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section 6, or where terminated by the +Licensor for cause) patent license to Make, have Made, use, offer to sell, +sell, import, and otherwise transfer the Covered Source and Products, where +such licence applies only to those patent claims licensable by such Licensor +that are necessarily infringed by exercising rights under the Covered Source +as Conveyed by that Licensor. + +6.2 If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Covered Source or a Product +constitutes direct or contributory patent infringement, or You seek any declaration +that a patent licensed to You under this Licence is invalid or unenforceable +then any rights granted to You under this Licence shall terminate as of the +date such process is initiated. + + 7 General + +7.1 If any provisions of this Licence are or subsequently become invalid or +unenforceable for any reason, the remaining provisions shall remain effective. + +7.2 You shall not use any of the name (including acronyms and abbreviations), +image, or logo by which the Licensor or CERN is known, except where needed +to comply with section 3, or where the use is otherwise allowed by law. Any +such permitted use shall be factual and shall not be made so as to suggest +any kind of endorsement or implication of involvement by the Licensor or its +personnel. + +7.3 CERN may publish updated versions and variants of this Licence which it +considers to be in the spirit of this version, but may differ in detail to +address new problems or concerns. New versions will be published with a unique +version number and a variant identifier specifying the variant. If the Licensor +has specified that a given variant applies to the Covered Source without specifying +a version, You may treat that Covered Source as being released under any version +of the CERN-OHL with that variant. If no variant is specified, the Covered +Source shall be treated as being released under CERN-OHL-S. The Licensor may +also specify that the Covered Source is subject to a specific version of the +CERN-OHL or any later version in which case You may apply this or any later +version of CERN-OHL with the same variant identifier published by CERN. + +7.4 This Licence shall not be enforceable except by a Licensor acting as such, +and third party beneficiary rights are specifically excluded. diff --git a/options/license/CERN-OHL-S-2.0 b/options/license/CERN-OHL-S-2.0 new file mode 100644 index 000000000000..4d2149069373 --- /dev/null +++ b/options/license/CERN-OHL-S-2.0 @@ -0,0 +1,240 @@ +CERN Open Hardware Licence Version 2 - Strongly Reciprocal + +Preamble + +CERN has developed this licence to promote collaboration among hardware designers +and to provide a legal tool which supports the freedom to use, study, modify, +share and distribute hardware designs and products based on those designs. +Version 2 of the CERN Open Hardware Licence comes in three variants: CERN-OHL-P +(permissive); and two reciprocal licences: CERN-OHL-W (weakly reciprocal) +and this licence, CERN-OHL-S (strongly reciprocal). + +The CERN-OHL-S is copyright CERN 2020. Anyone is welcome to use it, in unmodified +form only. + +Use of this Licence does not imply any endorsement by CERN of any Licensor +or their designs nor does it imply any involvement by CERN in their development. + + 1 Definitions + + 1.1 'Licence' means this CERN-OHL-S. + + 1.2 'Compatible Licence' means + + a) any earlier version of the CERN Open Hardware licence, or + + b) any version of the CERN-OHL-S, or + +c) any licence which permits You to treat the Source to which it applies as +licensed under CERN-OHL-S provided that on Conveyance of any such Source, +or any associated Product You treat the Source in question as being licensed +under CERN-OHL-S. + +1.3 'Source' means information such as design materials or digital code which +can be applied to Make or test a Product or to prepare a Product for use, +Conveyance or sale, regardless of its medium or how it is expressed. It may +include Notices. + +1.4 'Covered Source' means Source that is explicitly made available under +this Licence. + +1.5 'Product' means any device, component, work or physical object, whether +in finished or intermediate form, arising from the use, application or processing +of Covered Source. + +1.6 'Make' means to create or configure something, whether by manufacture, +assembly, compiling, loading or applying Covered Source or another Product +or otherwise. + +1.7 'Available Component' means any part, sub-assembly, library or code which: + +a) is licensed to You as Complete Source under a Compatible Licence; or + +b) is available, at the time a Product or the Source containing it is first +Conveyed, to You and any other prospective licensees + +i) as a physical part with sufficient rights and information (including any +configuration and programming files and information about its characteristics +and interfaces) to enable it either to be Made itself, or to be sourced and +used to Make the Product; or + +ii) as part of the normal distribution of a tool used to design or Make the +Product. + +1.8 'Complete Source' means the set of all Source necessary to Make a Product, +in the preferred form for making modifications, including necessary installation +and interfacing information both for the Product, and for any included Available +Components. If the format is proprietary, it must also be made available in +a format (if the proprietary tool can create it) which is viewable with a +tool available to potential licensees and licensed under a licence approved +by the Free Software Foundation or the Open Source Initiative. Complete Source +need not include the Source of any Available Component, provided that You +include in the Complete Source sufficient information to enable a recipient +to Make or source and use the Available Component to Make the Product. + +1.9 'Source Location' means a location where a Licensor has placed Covered +Source, and which that Licensor reasonably believes will remain easily accessible +for at least three years for anyone to obtain a digital copy. + +1.10 'Notice' means copyright, acknowledgement and trademark notices, Source +Location references, modification notices (subsection 3.3(b)) and all notices +that refer to this Licence and to the disclaimer of warranties that are included +in the Covered Source. + +1.11 'Licensee' or 'You' means any person exercising rights under this Licence. + +1.12 'Licensor' means a natural or legal person who creates or modifies Covered +Source. A person may be a Licensee and a Licensor at the same time. + + 1.13 'Convey' means to communicate to the public or distribute. + + 2 Applicability + +2.1 This Licence governs the use, copying, modification, Conveying of Covered +Source and Products, and the Making of Products. By exercising any right granted +under this Licence, You irrevocably accept these terms and conditions. + +2.2 This Licence is granted by the Licensor directly to You, and shall apply +worldwide and without limitation in time. + +2.3 You shall not attempt to restrict by contract or otherwise the rights +granted under this Licence to other Licensees. + +2.4 This Licence is not intended to restrict fair use, fair dealing, or any +other similar right. + + 3 Copying, modifying and Conveying Covered Source + +3.1 You may copy and Convey verbatim copies of Covered Source, in any medium, +provided You retain all Notices. + +3.2 You may modify Covered Source, other than Notices, provided that You irrevocably +undertake to make that modified Covered Source available from a Source Location +should You Convey a Product in circumstances where the recipient does not +otherwise receive a copy of the modified Covered Source. In each case subsection +3.3 shall apply. + +You may only delete Notices if they are no longer applicable to the corresponding +Covered Source as modified by You and You may add additional Notices applicable +to Your modifications. Including Covered Source in a larger work is modifying +the Covered Source, and the larger work becomes modified Covered Source. + +3.3 You may Convey modified Covered Source (with the effect that You shall +also become a Licensor) provided that You: + + a) retain Notices as required in subsection 3.2; + +b) add a Notice to the modified Covered Source stating that You have modified +it, with the date and brief description of how You have modified it; + +c) add a Source Location Notice for the modified Covered Source if You Convey +in circumstances where the recipient does not otherwise receive a copy of +the modified Covered Source; and + +d) license the modified Covered Source under the terms and conditions of this +Licence (or, as set out in subsection 8.3, a later version, if permitted by +the licence of the original Covered Source). Such modified Covered Source +must be licensed as a whole, but excluding Available Components contained +in it, which remain licensed under their own applicable licences. + + 4 Making and Conveying Products + +You may Make Products, and/or Convey them, provided that You either provide +each recipient with a copy of the Complete Source or ensure that each recipient +is notified of the Source Location of the Complete Source. That Complete Source +is Covered Source, and You must accordingly satisfy Your obligations set out +in subsection 3.3. If specified in a Notice, the Product must visibly and +securely display the Source Location on it or its packaging or documentation +in the manner specified in that Notice. + + 5 Research and Development + +You may Convey Covered Source, modified Covered Source or Products to a legal +entity carrying out development, testing or quality assurance work on Your +behalf provided that the work is performed on terms which prevent the entity +from both using the Source or Products for its own internal purposes and Conveying +the Source or Products or any modifications to them to any person other than +You. Any modifications made by the entity shall be deemed to be made by You +pursuant to subsection 3.2. + + 6 DISCLAIMER AND LIABILITY + +6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products are provided +'as is' and any express or implied warranties, including, but not limited +to, implied warranties of merchantability, of satisfactory quality, non-infringement +of third party rights, and fitness for a particular purpose or use are disclaimed +in respect of any Source or Product to the maximum extent permitted by law. +The Licensor makes no representation that any Source or Product does not or +will not infringe any patent, copyright, trade secret or other proprietary +right. The entire risk as to the use, quality, and performance of any Source +or Product shall be with You and not the Licensor. This disclaimer of warranty +is an essential part of this Licence and a condition for the grant of any +rights granted under this Licence. + +6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to the maximum +extent permitted by law, have no liability for direct, indirect, special, +incidental, consequential, exemplary, punitive or other damages of any character +including, without limitation, procurement of substitute goods or services, +loss of use, data or profits, or business interruption, however caused and +on any theory of contract, warranty, tort (including negligence), product +liability or otherwise, arising in any way in relation to the Covered Source, +modified Covered Source and/or the Making or Conveyance of a Product, even +if advised of the possibility of such damages, and You shall hold the Licensor(s) +free and harmless from any liability, costs, damages, fees and expenses, including +claims by third parties, in relation to such use. + + 7 Patents + +7.1 Subject to the terms and conditions of this Licence, each Licensor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in subsections 7.2 and 8.4) patent license to +Make, have Made, use, offer to sell, sell, import, and otherwise transfer +the Covered Source and Products, where such licence applies only to those +patent claims licensable by such Licensor that are necessarily infringed by +exercising rights under the Covered Source as Conveyed by that Licensor. + +7.2 If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Covered Source or a Product +constitutes direct or contributory patent infringement, or You seek any declaration +that a patent licensed to You under this Licence is invalid or unenforceable +then any rights granted to You under this Licence shall terminate as of the +date such process is initiated. + + 8 General + +8.1 If any provisions of this Licence are or subsequently become invalid or +unenforceable for any reason, the remaining provisions shall remain effective. + +8.2 You shall not use any of the name (including acronyms and abbreviations), +image, or logo by which the Licensor or CERN is known, except where needed +to comply with section 3, or where the use is otherwise allowed by law. Any +such permitted use shall be factual and shall not be made so as to suggest +any kind of endorsement or implication of involvement by the Licensor or its +personnel. + +8.3 CERN may publish updated versions and variants of this Licence which it +considers to be in the spirit of this version, but may differ in detail to +address new problems or concerns. New versions will be published with a unique +version number and a variant identifier specifying the variant. If the Licensor +has specified that a given variant applies to the Covered Source without specifying +a version, You may treat that Covered Source as being released under any version +of the CERN-OHL with that variant. If no variant is specified, the Covered +Source shall be treated as being released under CERN-OHL-S. The Licensor may +also specify that the Covered Source is subject to a specific version of the +CERN-OHL or any later version in which case You may apply this or any later +version of CERN-OHL with the same variant identifier published by CERN. + +8.4 This Licence shall terminate with immediate effect if You fail to comply +with any of its terms and conditions. + +8.5 However, if You cease all breaches of this Licence, then Your Licence +from any Licensor is reinstated unless such Licensor has terminated this Licence +by giving You, while You remain in breach, a notice specifying the breach +and requiring You to cure it within 30 days, and You have failed to come into +compliance in all material respects by the end of the 30 day period. Should +You repeat the breach after receipt of a cure notice and subsequent reinstatement, +this Licence will terminate immediately and permanently. Section 6 shall continue +to apply after any termination. + +8.6 This Licence shall not be enforceable except by a Licensor acting as such, +and third party beneficiary rights are specifically excluded. diff --git a/options/license/CERN-OHL-W-2.0 b/options/license/CERN-OHL-W-2.0 new file mode 100644 index 000000000000..8563cd280c80 --- /dev/null +++ b/options/license/CERN-OHL-W-2.0 @@ -0,0 +1,261 @@ +CERN Open Hardware Licence Version 2 - Weakly Reciprocal + +Preamble + +CERN has developed this licence to promote collaboration among hardware designers +and to provide a legal tool which supports the freedom to use, study, modify, +share and distribute hardware designs and products based on those designs. +Version 2 of the CERN Open Hardware Licence comes in three variants: CERN-OHL-P +(permissive); and two reciprocal licences: this licence, CERN- OHL-W (weakly +reciprocal) and CERN-OHL-S (strongly reciprocal). + +The CERN-OHL-W is copyright CERN 2020. Anyone is welcome to use it, in unmodified +form only. + +Use of this Licence does not imply any endorsement by CERN of any Licensor +or their designs nor does it imply any involvement by CERN in their development. + + 1 Definitions + + 1.1 'Licence' means this CERN-OHL-W. + + 1.2 'Compatible Licence' means + + a) any earlier version of the CERN Open Hardware licence, or + + b) any version of the CERN-OHL-S or the CERN-OHL-W, or + +c) any licence which permits You to treat the Source to which it applies as +licensed under CERN-OHL-S or CERN-OHL-W provided that on Conveyance of any +such Source, or any associated Product You treat the Source in question as +being licensed under CERN-OHL-S or CERN-OHL-W as appropriate. + +1.3 'Source' means information such as design materials or digital code which +can be applied to Make or test a Product or to prepare a Product for use, +Conveyance or sale, regardless of its medium or how it is expressed. It may +include Notices. + +1.4 'Covered Source' means Source that is explicitly made available under +this Licence. + +1.5 'Product' means any device, component, work or physical object, whether +in finished or intermediate form, arising from the use, application or processing +of Covered Source. + +1.6 'Make' means to create or configure something, whether by manufacture, +assembly, compiling, loading or applying Covered Source or another Product +or otherwise. + +1.7 'Available Component' means any part, sub-assembly, library or code which: + +a) is licensed to You as Complete Source under a Compatible Licence; or + +b) is available, at the time a Product or the Source containing it is first +Conveyed, to You and any other prospective licensees + +i) with sufficient rights and information (including any configuration and +programming files and information about its characteristics and interfaces) +to enable it either to be Made itself, or to be sourced and used to Make the +Product; or + +ii) as part of the normal distribution of a tool used to design or Make the +Product. + + 1.8 'External Material' means anything (including Source) which: + +a) is only combined with Covered Source in such a way that it interfaces with +the Covered Source using a documented interface which is described in the +Covered Source; and + +b) is not a derivative of or contains Covered Source, or, if it is, it is +solely to the extent necessary to facilitate such interfacing. + +1.9 'Complete Source' means the set of all Source necessary to Make a Product, +in the preferred form for making modifications, including necessary installation +and interfacing information both for the Product, and for any included Available +Components. If the format is proprietary, it must also be made available in +a format (if the proprietary tool can create it) which is viewable with a +tool available to potential licensees and licensed under a licence approved +by the Free Software Foundation or the Open Source Initiative. Complete Source +need not include the Source of any Available Component, provided that You +include in the Complete Source sufficient information to enable a recipient +to Make or source and use the Available Component to Make the Product. + +1.10 'Source Location' means a location where a Licensor has placed Covered +Source, and which that Licensor reasonably believes will remain easily accessible +for at least three years for anyone to obtain a digital copy. + +1.11 'Notice' means copyright, acknowledgement and trademark notices, Source +Location references, modification notices (subsection 3.3(b)) and all notices +that refer to this Licence and to the disclaimer of warranties that are included +in the Covered Source. + +1.12 'Licensee' or 'You' means any person exercising rights under this Licence. + +1.13 'Licensor' means a natural or legal person who creates or modifies Covered +Source. A person may be a Licensee and a Licensor at the same time. + + 1.14 'Convey' means to communicate to the public or distribute. + + 2 Applicability + +2.1 This Licence governs the use, copying, modification, Conveying of Covered +Source and Products, and the Making of Products. By exercising any right granted +under this Licence, You irrevocably accept these terms and conditions. + +2.2 This Licence is granted by the Licensor directly to You, and shall apply +worldwide and without limitation in time. + +2.3 You shall not attempt to restrict by contract or otherwise the rights +granted under this Licence to other Licensees. + +2.4 This Licence is not intended to restrict fair use, fair dealing, or any +other similar right. + + 3 Copying, modifying and Conveying Covered Source + +3.1 You may copy and Convey verbatim copies of Covered Source, in any medium, +provided You retain all Notices. + +3.2 You may modify Covered Source, other than Notices, provided that You irrevocably +undertake to make that modified Covered Source available from a Source Location +should You Convey a Product in circumstances where the recipient does not +otherwise receive a copy of the modified Covered Source. In each case subsection +3.3 shall apply. + +You may only delete Notices if they are no longer applicable to the corresponding +Covered Source as modified by You and You may add additional Notices applicable +to Your modifications. + +3.3 You may Convey modified Covered Source (with the effect that You shall +also become a Licensor) provided that You: + + a) retain Notices as required in subsection 3.2; + +b) add a Notice to the modified Covered Source stating that You have modified +it, with the date and brief description of how You have modified it; + +c) add a Source Location Notice for the modified Covered Source if You Convey +in circumstances where the recipient does not otherwise receive a copy of +the modified Covered Source; and + +d) license the modified Covered Source under the terms and conditions of this +Licence (or, as set out in subsection 8.3, a later version, if permitted by +the licence of the original Covered Source). Such modified Covered Source +must be licensed as a whole, but excluding Available Components contained +in it or External Material to which it is interfaced, which remain licensed +under their own applicable licences. + + 4 Making and Conveying Products + +4.1 You may Make Products, and/or Convey them, provided that You either provide +each recipient with a copy of the Complete Source or ensure that each recipient +is notified of the Source Location of the Complete Source. That Complete Source +includes Covered Source and You must accordingly satisfy Your obligations +set out in subsection 3.3. If specified in a Notice, the Product must visibly +and securely display the Source Location on it or its packaging or documentation +in the manner specified in that Notice. + +4.2 Where You Convey a Product which incorporates External Material, the Complete +Source for that Product which You are required to provide under subsection +4.1 need not include any Source for the External Material. + +4.3 You may license Products under terms of Your choice, provided that such +terms do not restrict or attempt to restrict any recipients' rights under +this Licence to the Covered Source. + + 5 Research and Development + +You may Convey Covered Source, modified Covered Source or Products to a legal +entity carrying out development, testing or quality assurance work on Your +behalf provided that the work is performed on terms which prevent the entity +from both using the Source or Products for its own internal purposes and Conveying +the Source or Products or any modifications to them to any person other than +You. Any modifications made by the entity shall be deemed to be made by You +pursuant to subsection 3.2. + + 6 DISCLAIMER AND LIABILITY + +6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products are provided +'as is' and any express or implied warranties, including, but not limited +to, implied warranties of merchantability, of satisfactory quality, non-infringement +of third party rights, and fitness for a particular purpose or use are disclaimed +in respect of any Source or Product to the maximum extent permitted by law. +The Licensor makes no representation that any Source or Product does not or +will not infringe any patent, copyright, trade secret or other proprietary +right. The entire risk as to the use, quality, and performance of any Source +or Product shall be with You and not the Licensor. This disclaimer of warranty +is an essential part of this Licence and a condition for the grant of any +rights granted under this Licence. + +6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to the maximum +extent permitted by law, have no liability for direct, indirect, special, +incidental, consequential, exemplary, punitive or other damages of any character +including, without limitation, procurement of substitute goods or services, +loss of use, data or profits, or business interruption, however caused and +on any theory of contract, warranty, tort (including negligence), product +liability or otherwise, arising in any way in relation to the Covered Source, +modified Covered Source and/or the Making or Conveyance of a Product, even +if advised of the possibility of such damages, and You shall hold the Licensor(s) +free and harmless from any liability, costs, damages, fees and expenses, including +claims by third parties, in relation to such use. + + 7 Patents + +7.1 Subject to the terms and conditions of this Licence, each Licensor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in subsections 7.2 and 8.4) patent license to +Make, have Made, use, offer to sell, sell, import, and otherwise transfer +the Covered Source and Products, where such licence applies only to those +patent claims licensable by such Licensor that are necessarily infringed by +exercising rights under the Covered Source as Conveyed by that Licensor. + +7.2 If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Covered Source or a Product +constitutes direct or contributory patent infringement, or You seek any declaration +that a patent licensed to You under this Licence is invalid or unenforceable +then any rights granted to You under this Licence shall terminate as of the +date such process is initiated. + + 8 General + +8.1 If any provisions of this Licence are or subsequently become invalid or +unenforceable for any reason, the remaining provisions shall remain effective. + +8.2 You shall not use any of the name (including acronyms and abbreviations), +image, or logo by which the Licensor or CERN is known, except where needed +to comply with section 3, or where the use is otherwise allowed by law. Any +such permitted use shall be factual and shall not be made so as to suggest +any kind of endorsement or implication of involvement by the Licensor or its +personnel. + +8.3 CERN may publish updated versions and variants of this Licence which it +considers to be in the spirit of this version, but may differ in detail to +address new problems or concerns. New versions will be published with a unique +version number and a variant identifier specifying the variant. If the Licensor +has specified that a given variant applies to the Covered Source without specifying +a version, You may treat that Covered Source as being released under any version +of the CERN-OHL with that variant. If no variant is specified, the Covered +Source shall be treated as being released under CERN-OHL-S. The Licensor may +also specify that the Covered Source is subject to a specific version of the +CERN-OHL or any later version in which case You may apply this or any later +version of CERN-OHL with the same variant identifier published by CERN. + +You may treat Covered Source licensed under CERN-OHL-W as licensed under CERN-OHL-S +if and only if all Available Components referenced in the Covered Source comply +with the corresponding definition of Available Component for CERN-OHL-S. + +8.4 This Licence shall terminate with immediate effect if You fail to comply +with any of its terms and conditions. + +8.5 However, if You cease all breaches of this Licence, then Your Licence +from any Licensor is reinstated unless such Licensor has terminated this Licence +by giving You, while You remain in breach, a notice specifying the breach +and requiring You to cure it within 30 days, and You have failed to come into +compliance in all material respects by the end of the 30 day period. Should +You repeat the breach after receipt of a cure notice and subsequent reinstatement, +this Licence will terminate immediately and permanently. Section 6 shall continue +to apply after any termination. + +8.6 This Licence shall not be enforceable except by a Licensor acting as such, +and third party beneficiary rights are specifically excluded. diff --git a/options/license/EPICS b/options/license/EPICS new file mode 100644 index 000000000000..4a12fba43c5a --- /dev/null +++ b/options/license/EPICS @@ -0,0 +1,69 @@ +EPICS Open License Terms + +The following is the text of the EPICS Open software license agreement which +now applies to EPICS Base and many of the unbundled EPICS extensions and support +modules. Copyright © . All rights reserved. + + is distributed subject to the following license conditions: + +SOFTWARE LICENSE AGREEMENT + +Software: + +1. The "Software", below, refers to (in either source code, or binary +form and accompanying documentation). Each licensee is addressed as "you" +or "Licensee." + +2. The copyright holders shown above and their third-party licensors hereby +grant Licensee a royalty-free nonexclusive license, subject to the limitations +stated herein and U.S. Government license rights. + +3. You may modify and make a copy or copies of the Software for use within +your organization, if you meet the following conditions: + +a. Copies in source code must include the copyright notice and this Software +License Agreement. + +b. Copies in binary form must include the copyright notice and this Software +License Agreement in the documentation and/or other materials provided with +the copy. + +4. You may modify a copy or copies of the Software or any portion of it, thus +forming a work based on the Software, and distribute copies of such work outside +your organization, if you meet all of the following conditions: + +a. Copies in source code must include the copyright notice and this Software +License Agreement; + +b. Copies in binary form must include the copyright notice and this Software +License Agreement in the documentation and/or other materials provided with +the copy; + +c. Modified copies and works based on the Software must carry prominent notices +stating that you changed specified portions of the Software. + +5. Portions of the Software resulted from work developed under a U.S. Government +contract and are subject to the following license: the Government is granted +for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable +worldwide license in this computer software to reproduce, prepare derivative +works, and perform publicly and display publicly. + +6. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT WARRANTY +OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY LICENSORS, THE UNITED +STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND THEIR EMPLOYEES: (1) DISCLAIM +ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR +NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY OR RESPONSIBILITY +FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF THE SOFTWARE, (3) DO NOT +REPTHAT USE OF THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) +DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION UNINTERRUPTED, THAT IT IS ERROR-FREE +OR THAT ANY ERRORS WILL BE CORRECTED. + +7. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR +THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL, +SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED +TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER SUCH +LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING NEGLIGENCE +OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED +OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. diff --git a/options/license/EUPL-1.0 b/options/license/EUPL-1.0 index 09d08e3f03a4..3fdb93ab427f 100644 --- a/options/license/EUPL-1.0 +++ b/options/license/EUPL-1.0 @@ -44,10 +44,12 @@ the Work under the Licence. the Licence, or otherwise contributes to the creation of a Derivative Work. − The Licensee or "You": any natural or legal person who makes any usage of -the Software under the terms of the Licence. − Distribution and/or Communication: -any act of selling, giving, lending, renting, distributing, communicating, -transmitting, or otherwise making available, on-line or off-line, copies of -the Work at the disposal of any other natural or legal person. +the Software under the terms of the Licence. + +− Distribution and/or Communication: any act of selling, giving, lending, +renting, distributing, communicating, transmitting, or otherwise making available, +on-line or off-line, copies of the Work at the disposal of any other natural +or legal person. 2. Scope of the rights granted by the Licence diff --git a/options/license/GFDL-1.1-invariants-only b/options/license/GFDL-1.1-invariants-only new file mode 100644 index 000000000000..d63c80021ccd --- /dev/null +++ b/options/license/GFDL-1.1-invariants-only @@ -0,0 +1,330 @@ +GNU Free Documentation License + +Version 1.1, March 2000 + +Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other written +document "free" in the sense of freedom: to assure everyone the effective +freedom to copy and redistribute it, with or without modifying it, either +commercially or noncommercially. Secondarily, this License preserves for the +author and publisher a way to get credit for their work, while not being considered +responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a notice placed +by the copyright holder saying it can be distributed under the terms of this +License. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(For example, if the Document is in part a textbook of mathematics, a Secondary +Section may not explain any mathematics.) The relationship could be a matter +of historical connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, whose +contents can be viewed and edited directly and straightforwardly with generic +text editors or (for images composed of pixels) generic paint programs or +(for drawings) some widely available drawing editor, and that is suitable +for input to text formatters or for automatic translation to a variety of +formats suitable for input to text formatters. A copy made in an otherwise +Transparent file format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is not +"Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML designed for human modification. +Opaque formats include PostScript, PDF, proprietary formats that can be read +and edited only by proprietary word processors, SGML or XML for which the +DTD and/or processing tools are not generally available, and the machine-generated +HTML produced by some word processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, and +the Document's license notice requires Cover Texts, you must enclose the copies +in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover +Texts on the front cover, and Back-Cover Texts on the back cover. Both covers +must also clearly and legibly identify you as the publisher of these copies. +The front cover must present the full title with all words of the title equally +prominent and visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve the title +of the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a publicly-accessible +computer-network location containing a complete Transparent copy of the Document, +free of added material, which the general network-using public has access +to download anonymously at no charge using public-standard network protocols. +If you use the latter option, you must take reasonably prudent steps, when +you begin distribution of Opaque copies in quantity, to ensure that this Transparent +copy will remain thus accessible at the stated location until at least one +year after the last time you distribute an Opaque copy (directly or through +your agents or retailers) of that edition to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has less than five). + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section entitled "History", and its title, and add to it an +item stating at least the title, year, new authors, and publisher of the Modified +Version as given on the Title Page. If there is no section entitled "History" +in the Document, create one stating the title, year, authors, and publisher +of the Document as given on its Title Page, then add an item describing the +Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. In any section entitled "Acknowledgements" or "Dedications", preserve the +section's title, and preserve in the section all the substance and tone of +each of the contributor acknowledgements and/or dedications given therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section as "Endorsements" or to conflict in +title with any Invariant Section. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections entitled "History" in the +various original documents, forming one section entitled "History"; likewise +combine any sections entitled "Acknowledgements", and any sections entitled +"Dedications". You must delete all sections entitled "Endorsements." + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +does not as a whole count as a Modified Version of the Document, provided +no compilation copyright is claimed for the compilation. Such a compilation +is called an "aggregate", and this License does not apply to the other self-contained +works thus compiled with the Document, on account of their being thus compiled, +if they are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one quarter of the entire +aggregate, the Document's Cover Texts may be placed on covers that surround +only the Document within the aggregate. Otherwise they must appear on covers +around the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License provided that you also include the original English version +of this License. In case of a disagreement between the translation and the +original English version of this License, the original English version will +prevail. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.1 or any later version published by the Free Software Foundation; +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the +license is included in the section entitled "GNU Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" instead +of saying which ones are invariant. If you have no Front-Cover Texts, write +"no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise +for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.1-invariants-or-later b/options/license/GFDL-1.1-invariants-or-later new file mode 100644 index 000000000000..3a6519b4ca2b --- /dev/null +++ b/options/license/GFDL-1.1-invariants-or-later @@ -0,0 +1,330 @@ +GNU Free Documentation License + +Version 1.1, March 2000 + +Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other written +document "free" in the sense of freedom: to assure everyone the effective +freedom to copy and redistribute it, with or without modifying it, either +commercially or noncommercially. Secondarily, this License preserves for the +author and publisher a way to get credit for their work, while not being considered +responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a notice placed +by the copyright holder saying it can be distributed under the terms of this +License. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(For example, if the Document is in part a textbook of mathematics, a Secondary +Section may not explain any mathematics.) The relationship could be a matter +of historical connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, whose +contents can be viewed and edited directly and straightforwardly with generic +text editors or (for images composed of pixels) generic paint programs or +(for drawings) some widely available drawing editor, and that is suitable +for input to text formatters or for automatic translation to a variety of +formats suitable for input to text formatters. A copy made in an otherwise +Transparent file format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is not +"Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML designed for human modification. +Opaque formats include PostScript, PDF, proprietary formats that can be read +and edited only by proprietary word processors, SGML or XML for which the +DTD and/or processing tools are not generally available, and the machine-generated +HTML produced by some word processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, and +the Document's license notice requires Cover Texts, you must enclose the copies +in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover +Texts on the front cover, and Back-Cover Texts on the back cover. Both covers +must also clearly and legibly identify you as the publisher of these copies. +The front cover must present the full title with all words of the title equally +prominent and visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve the title +of the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a publicly-accessible +computer-network location containing a complete Transparent copy of the Document, +free of added material, which the general network-using public has access +to download anonymously at no charge using public-standard network protocols. +If you use the latter option, you must take reasonably prudent steps, when +you begin distribution of Opaque copies in quantity, to ensure that this Transparent +copy will remain thus accessible at the stated location until at least one +year after the last time you distribute an Opaque copy (directly or through +your agents or retailers) of that edition to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has less than five). + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section entitled "History", and its title, and add to it an +item stating at least the title, year, new authors, and publisher of the Modified +Version as given on the Title Page. If there is no section entitled "History" +in the Document, create one stating the title, year, authors, and publisher +of the Document as given on its Title Page, then add an item describing the +Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. In any section entitled "Acknowledgements" or "Dedications", preserve the +section's title, and preserve in the section all the substance and tone of +each of the contributor acknowledgements and/or dedications given therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section as "Endorsements" or to conflict in +title with any Invariant Section. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections entitled "History" in the +various original documents, forming one section entitled "History"; likewise +combine any sections entitled "Acknowledgements", and any sections entitled +"Dedications". You must delete all sections entitled "Endorsements." + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +does not as a whole count as a Modified Version of the Document, provided +no compilation copyright is claimed for the compilation. Such a compilation +is called an "aggregate", and this License does not apply to the other self-contained +works thus compiled with the Document, on account of their being thus compiled, +if they are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one quarter of the entire +aggregate, the Document's Cover Texts may be placed on covers that surround +only the Document within the aggregate. Otherwise they must appear on covers +around the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License provided that you also include the original English version +of this License. In case of a disagreement between the translation and the +original English version of this License, the original English version will +prevail. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.1 or any later version published by the Free Software Foundation; +with the Invariant Sections being LIST THEIR TITLES , with the Front-Cover +Texts being LIST , and with the Back-Cover Texts being LIST . A copy of the +license is included in the section entitled "GNU Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" instead +of saying which ones are invariant. If you have no Front-Cover Texts, write +"no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise +for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.1-no-invariants-only b/options/license/GFDL-1.1-no-invariants-only new file mode 100644 index 000000000000..d63c80021ccd --- /dev/null +++ b/options/license/GFDL-1.1-no-invariants-only @@ -0,0 +1,330 @@ +GNU Free Documentation License + +Version 1.1, March 2000 + +Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other written +document "free" in the sense of freedom: to assure everyone the effective +freedom to copy and redistribute it, with or without modifying it, either +commercially or noncommercially. Secondarily, this License preserves for the +author and publisher a way to get credit for their work, while not being considered +responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a notice placed +by the copyright holder saying it can be distributed under the terms of this +License. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(For example, if the Document is in part a textbook of mathematics, a Secondary +Section may not explain any mathematics.) The relationship could be a matter +of historical connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, whose +contents can be viewed and edited directly and straightforwardly with generic +text editors or (for images composed of pixels) generic paint programs or +(for drawings) some widely available drawing editor, and that is suitable +for input to text formatters or for automatic translation to a variety of +formats suitable for input to text formatters. A copy made in an otherwise +Transparent file format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is not +"Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML designed for human modification. +Opaque formats include PostScript, PDF, proprietary formats that can be read +and edited only by proprietary word processors, SGML or XML for which the +DTD and/or processing tools are not generally available, and the machine-generated +HTML produced by some word processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, and +the Document's license notice requires Cover Texts, you must enclose the copies +in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover +Texts on the front cover, and Back-Cover Texts on the back cover. Both covers +must also clearly and legibly identify you as the publisher of these copies. +The front cover must present the full title with all words of the title equally +prominent and visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve the title +of the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a publicly-accessible +computer-network location containing a complete Transparent copy of the Document, +free of added material, which the general network-using public has access +to download anonymously at no charge using public-standard network protocols. +If you use the latter option, you must take reasonably prudent steps, when +you begin distribution of Opaque copies in quantity, to ensure that this Transparent +copy will remain thus accessible at the stated location until at least one +year after the last time you distribute an Opaque copy (directly or through +your agents or retailers) of that edition to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has less than five). + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section entitled "History", and its title, and add to it an +item stating at least the title, year, new authors, and publisher of the Modified +Version as given on the Title Page. If there is no section entitled "History" +in the Document, create one stating the title, year, authors, and publisher +of the Document as given on its Title Page, then add an item describing the +Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. In any section entitled "Acknowledgements" or "Dedications", preserve the +section's title, and preserve in the section all the substance and tone of +each of the contributor acknowledgements and/or dedications given therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section as "Endorsements" or to conflict in +title with any Invariant Section. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections entitled "History" in the +various original documents, forming one section entitled "History"; likewise +combine any sections entitled "Acknowledgements", and any sections entitled +"Dedications". You must delete all sections entitled "Endorsements." + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +does not as a whole count as a Modified Version of the Document, provided +no compilation copyright is claimed for the compilation. Such a compilation +is called an "aggregate", and this License does not apply to the other self-contained +works thus compiled with the Document, on account of their being thus compiled, +if they are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one quarter of the entire +aggregate, the Document's Cover Texts may be placed on covers that surround +only the Document within the aggregate. Otherwise they must appear on covers +around the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License provided that you also include the original English version +of this License. In case of a disagreement between the translation and the +original English version of this License, the original English version will +prevail. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.1 or any later version published by the Free Software Foundation; +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the +license is included in the section entitled "GNU Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" instead +of saying which ones are invariant. If you have no Front-Cover Texts, write +"no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise +for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.1-no-invariants-or-later b/options/license/GFDL-1.1-no-invariants-or-later new file mode 100644 index 000000000000..d63c80021ccd --- /dev/null +++ b/options/license/GFDL-1.1-no-invariants-or-later @@ -0,0 +1,330 @@ +GNU Free Documentation License + +Version 1.1, March 2000 + +Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other written +document "free" in the sense of freedom: to assure everyone the effective +freedom to copy and redistribute it, with or without modifying it, either +commercially or noncommercially. Secondarily, this License preserves for the +author and publisher a way to get credit for their work, while not being considered +responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a notice placed +by the copyright holder saying it can be distributed under the terms of this +License. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(For example, if the Document is in part a textbook of mathematics, a Secondary +Section may not explain any mathematics.) The relationship could be a matter +of historical connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, whose +contents can be viewed and edited directly and straightforwardly with generic +text editors or (for images composed of pixels) generic paint programs or +(for drawings) some widely available drawing editor, and that is suitable +for input to text formatters or for automatic translation to a variety of +formats suitable for input to text formatters. A copy made in an otherwise +Transparent file format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is not +"Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML designed for human modification. +Opaque formats include PostScript, PDF, proprietary formats that can be read +and edited only by proprietary word processors, SGML or XML for which the +DTD and/or processing tools are not generally available, and the machine-generated +HTML produced by some word processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, and +the Document's license notice requires Cover Texts, you must enclose the copies +in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover +Texts on the front cover, and Back-Cover Texts on the back cover. Both covers +must also clearly and legibly identify you as the publisher of these copies. +The front cover must present the full title with all words of the title equally +prominent and visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve the title +of the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a publicly-accessible +computer-network location containing a complete Transparent copy of the Document, +free of added material, which the general network-using public has access +to download anonymously at no charge using public-standard network protocols. +If you use the latter option, you must take reasonably prudent steps, when +you begin distribution of Opaque copies in quantity, to ensure that this Transparent +copy will remain thus accessible at the stated location until at least one +year after the last time you distribute an Opaque copy (directly or through +your agents or retailers) of that edition to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has less than five). + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section entitled "History", and its title, and add to it an +item stating at least the title, year, new authors, and publisher of the Modified +Version as given on the Title Page. If there is no section entitled "History" +in the Document, create one stating the title, year, authors, and publisher +of the Document as given on its Title Page, then add an item describing the +Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. In any section entitled "Acknowledgements" or "Dedications", preserve the +section's title, and preserve in the section all the substance and tone of +each of the contributor acknowledgements and/or dedications given therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section as "Endorsements" or to conflict in +title with any Invariant Section. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections entitled "History" in the +various original documents, forming one section entitled "History"; likewise +combine any sections entitled "Acknowledgements", and any sections entitled +"Dedications". You must delete all sections entitled "Endorsements." + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +does not as a whole count as a Modified Version of the Document, provided +no compilation copyright is claimed for the compilation. Such a compilation +is called an "aggregate", and this License does not apply to the other self-contained +works thus compiled with the Document, on account of their being thus compiled, +if they are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one quarter of the entire +aggregate, the Document's Cover Texts may be placed on covers that surround +only the Document within the aggregate. Otherwise they must appear on covers +around the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License provided that you also include the original English version +of this License. In case of a disagreement between the translation and the +original English version of this License, the original English version will +prevail. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.1 or any later version published by the Free Software Foundation; +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the +license is included in the section entitled "GNU Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" instead +of saying which ones are invariant. If you have no Front-Cover Texts, write +"no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise +for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.1-or-later b/options/license/GFDL-1.1-or-later index 3a6519b4ca2b..d63c80021ccd 100644 --- a/options/license/GFDL-1.1-or-later +++ b/options/license/GFDL-1.1-or-later @@ -316,8 +316,8 @@ just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; -with the Invariant Sections being LIST THEIR TITLES , with the Front-Cover -Texts being LIST , and with the Back-Cover Texts being LIST . A copy of the +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have no Invariant Sections, write "with no Invariant Sections" instead diff --git a/options/license/GFDL-1.2-invariants-only b/options/license/GFDL-1.2-invariants-only new file mode 100644 index 000000000000..6bfe9f559332 --- /dev/null +++ b/options/license/GFDL-1.2-invariants-only @@ -0,0 +1,370 @@ +GNU Free Documentation License + +Version 1.2, November 2002 + +Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, +Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.2-invariants-or-later b/options/license/GFDL-1.2-invariants-or-later new file mode 100644 index 000000000000..99499f921738 --- /dev/null +++ b/options/license/GFDL-1.2-invariants-or-later @@ -0,0 +1,370 @@ +GNU Free Documentation License + +Version 1.2, November 2002 + +Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, +Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts,and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.2-no-invariants-only b/options/license/GFDL-1.2-no-invariants-only new file mode 100644 index 000000000000..6bfe9f559332 --- /dev/null +++ b/options/license/GFDL-1.2-no-invariants-only @@ -0,0 +1,370 @@ +GNU Free Documentation License + +Version 1.2, November 2002 + +Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, +Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.2-no-invariants-or-later b/options/license/GFDL-1.2-no-invariants-or-later new file mode 100644 index 000000000000..6bfe9f559332 --- /dev/null +++ b/options/license/GFDL-1.2-no-invariants-or-later @@ -0,0 +1,370 @@ +GNU Free Documentation License + +Version 1.2, November 2002 + +Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, +Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. + + H. Include an unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided for under this License. Any other attempt to copy, modify, +sublicense or distribute the Document is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.2 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.2-or-later b/options/license/GFDL-1.2-or-later index c66aefc2bdc7..6bfe9f559332 100644 --- a/options/license/GFDL-1.2-or-later +++ b/options/license/GFDL-1.2-or-later @@ -349,11 +349,11 @@ To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: -Copyright (c) YEAR YOUR NAME . Permission is granted to copy, distribute and/or +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; -with no Invariant Sections , no Front-Cover Texts , and no Back-Cover Texts -. A copy of the license is included in the section entitled "GNU Free Documentation +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace diff --git a/options/license/GFDL-1.3-invariants-only b/options/license/GFDL-1.3-invariants-only new file mode 100644 index 000000000000..90f814dea736 --- /dev/null +++ b/options/license/GFDL-1.3-invariants-only @@ -0,0 +1,416 @@ +GNU Free Documentation License + +Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free +Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +The "publisher" means any person or entity that distributes copies of the +Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. H. Include an +unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense, or distribute it is void, and will automatically terminate your +rights under this License. + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, receipt +of a copy of some or all of the same material does not give you any rights +to use it. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +If the Document specifies that a proxy can decide which future versions of +this License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Document. + + 11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide +Web server that publishes copyrightable works and also provides prominent +facilities for anybody to edit those works. A public wiki that anybody can +edit is an example of such a server. A "Massive Multiauthor Collaboration" +(or "MMC") contained in the site means any set of copyrightable works thus +published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license +published by Creative Commons Corporation, a not-for-profit corporation with +a principal place of business in San Francisco, California, as well as future +copyleft versions of that license published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in part, +as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this License, +and if all works that were first published under this License somewhere other +than this MMC, and subsequently incorporated in whole or in part into the +MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated +prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site under +CC-BY-SA on the same site at any time before August 1, 2009, provided the +MMC is eligible for relicensing. ADDENDUM: How to use this License for your +documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.3-invariants-or-later b/options/license/GFDL-1.3-invariants-or-later new file mode 100644 index 000000000000..90f814dea736 --- /dev/null +++ b/options/license/GFDL-1.3-invariants-or-later @@ -0,0 +1,416 @@ +GNU Free Documentation License + +Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free +Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +The "publisher" means any person or entity that distributes copies of the +Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. H. Include an +unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense, or distribute it is void, and will automatically terminate your +rights under this License. + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, receipt +of a copy of some or all of the same material does not give you any rights +to use it. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +If the Document specifies that a proxy can decide which future versions of +this License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Document. + + 11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide +Web server that publishes copyrightable works and also provides prominent +facilities for anybody to edit those works. A public wiki that anybody can +edit is an example of such a server. A "Massive Multiauthor Collaboration" +(or "MMC") contained in the site means any set of copyrightable works thus +published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license +published by Creative Commons Corporation, a not-for-profit corporation with +a principal place of business in San Francisco, California, as well as future +copyleft versions of that license published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in part, +as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this License, +and if all works that were first published under this License somewhere other +than this MMC, and subsequently incorporated in whole or in part into the +MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated +prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site under +CC-BY-SA on the same site at any time before August 1, 2009, provided the +MMC is eligible for relicensing. ADDENDUM: How to use this License for your +documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.3-no-invariants-only b/options/license/GFDL-1.3-no-invariants-only new file mode 100644 index 000000000000..90f814dea736 --- /dev/null +++ b/options/license/GFDL-1.3-no-invariants-only @@ -0,0 +1,416 @@ +GNU Free Documentation License + +Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free +Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +The "publisher" means any person or entity that distributes copies of the +Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. H. Include an +unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense, or distribute it is void, and will automatically terminate your +rights under this License. + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, receipt +of a copy of some or all of the same material does not give you any rights +to use it. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +If the Document specifies that a proxy can decide which future versions of +this License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Document. + + 11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide +Web server that publishes copyrightable works and also provides prominent +facilities for anybody to edit those works. A public wiki that anybody can +edit is an example of such a server. A "Massive Multiauthor Collaboration" +(or "MMC") contained in the site means any set of copyrightable works thus +published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license +published by Creative Commons Corporation, a not-for-profit corporation with +a principal place of business in San Francisco, California, as well as future +copyleft versions of that license published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in part, +as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this License, +and if all works that were first published under this License somewhere other +than this MMC, and subsequently incorporated in whole or in part into the +MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated +prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site under +CC-BY-SA on the same site at any time before August 1, 2009, provided the +MMC is eligible for relicensing. ADDENDUM: How to use this License for your +documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.3-no-invariants-or-later b/options/license/GFDL-1.3-no-invariants-or-later new file mode 100644 index 000000000000..90f814dea736 --- /dev/null +++ b/options/license/GFDL-1.3-no-invariants-or-later @@ -0,0 +1,416 @@ +GNU Free Documentation License + +Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free +Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + 0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other functional +and useful document "free" in the sense of freedom: to assure everyone the +effective freedom to copy and redistribute it, with or without modifying it, +either commercially or noncommercially. Secondarily, this License preserves +for the author and publisher a way to get credit for their work, while not +being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of +the document must themselves be free in the same sense. It complements the +GNU General Public License, which is a copyleft license designed for free +software. + +We have designed this License in order to use it for manuals for free software, +because free software needs free documentation: a free program should come +with manuals providing the same freedoms that the software does. But this +License is not limited to software manuals; it can be used for any textual +work, regardless of subject matter or whether it is published as a printed +book. We recommend this License principally for works whose purpose is instruction +or reference. + + 1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that contains +a notice placed by the copyright holder saying it can be distributed under +the terms of this License. Such a notice grants a world-wide, royalty-free +license, unlimited in duration, to use that work under the conditions stated +herein. The "Document", below, refers to any such manual or work. Any member +of the public is a licensee, and is addressed as "you". You accept the license +if you copy, modify or distribute the work in a way requiring permission under +copyright law. + +A "Modified Version" of the Document means any work containing the Document +or a portion of it, either copied verbatim, or with modifications and/or translated +into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the +Document that deals exclusively with the relationship of the publishers or +authors of the Document to the Document's overall subject (or to related matters) +and contains nothing that could fall directly within that overall subject. +(Thus, if the Document is in part a textbook of mathematics, a Secondary Section +may not explain any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, commercial, +philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, +as being those of Invariant Sections, in the notice that says that the Document +is released under this License. If a section does not fit the above definition +of Secondary then it is not allowed to be designated as Invariant. The Document +may contain zero Invariant Sections. If the Document does not identify any +Invariant Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover +Texts or Back-Cover Texts, in the notice that says that the Document is released +under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover +Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, represented +in a format whose specification is available to the general public, that is +suitable for revising the document straightforwardly with generic text editors +or (for images composed of pixels) generic paint programs or (for drawings) +some widely available drawing editor, and that is suitable for input to text +formatters or for automatic translation to a variety of formats suitable for +input to text formatters. A copy made in an otherwise Transparent file format +whose markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format is +not Transparent if used for any substantial amount of text. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without +markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly +available DTD, and standard-conforming simple HTML, PostScript or PDF designed +for human modification. Examples of transparent image formats include PNG, +XCF and JPG. Opaque formats include proprietary formats that can be read and +edited only by proprietary word processors, SGML or XML for which the DTD +and/or processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output purposes +only. + +The "Title Page" means, for a printed book, the title page itself, plus such +following pages as are needed to hold, legibly, the material this License +requires to appear in the title page. For works in formats which do not have +any title page as such, "Title Page" means the text near the most prominent +appearance of the work's title, preceding the beginning of the body of the +text. + +The "publisher" means any person or entity that distributes copies of the +Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose title +either is precisely XYZ or contains XYZ in parentheses following text that +translates XYZ in another language. (Here XYZ stands for a specific section +name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", +or "History".) To "Preserve the Title" of such a section when you modify the +Document means that it remains a section "Entitled XYZ" according to this +definition. + +The Document may include Warranty Disclaimers next to the notice which states +that this License applies to the Document. These Warranty Disclaimers are +considered to be included by reference in this License, but only as regards +disclaiming warranties: any other implication that these Warranty Disclaimers +may have is void and has no effect on the meaning of this License. + + 2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially +or noncommercially, provided that this License, the copyright notices, and +the license notice saying this License applies to the Document are reproduced +in all copies, and that you add no other conditions whatsoever to those of +this License. You may not use technical measures to obstruct or control the +reading or further copying of the copies you make or distribute. However, +you may accept compensation in exchange for copies. If you distribute a large +enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you +may publicly display copies. + + 3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have printed +covers) of the Document, numbering more than 100, and the Document's license +notice requires Cover Texts, you must enclose the copies in covers that carry, +clearly and legibly, all these Cover Texts: Front-Cover Texts on the front +cover, and Back-Cover Texts on the back cover. Both covers must also clearly +and legibly identify you as the publisher of these copies. The front cover +must present the full title with all words of the title equally prominent +and visible. You may add other material on the covers in addition. Copying +with changes limited to the covers, as long as they preserve the title of +the Document and satisfy these conditions, can be treated as verbatim copying +in other respects. + +If the required texts for either cover are too voluminous to fit legibly, +you should put the first ones listed (as many as fit reasonably) on the actual +cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more +than 100, you must either include a machine-readable Transparent copy along +with each Opaque copy, or state in or with each Opaque copy a computer-network +location from which the general network-using public has access to download +using public-standard network protocols a complete Transparent copy of the +Document, free of added material. If you use the latter option, you must take +reasonably prudent steps, when you begin distribution of Opaque copies in +quantity, to ensure that this Transparent copy will remain thus accessible +at the stated location until at least one year after the last time you distribute +an Opaque copy (directly or through your agents or retailers) of that edition +to the public. + +It is requested, but not required, that you contact the authors of the Document +well before redistributing any large number of copies, to give them a chance +to provide you with an updated version of the Document. + + 4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions +of sections 2 and 3 above, provided that you release the Modified Version +under precisely this License, with the Modified Version filling the role of +the Document, thus licensing distribution and modification of the Modified +Version to whoever possesses a copy of it. In addition, you must do these +things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct from +that of the Document, and from those of previous versions (which should, if +there were any, be listed in the History section of the Document). You may +use the same title as a previous version if the original publisher of that +version gives permission. + +B. List on the Title Page, as authors, one or more persons or entities responsible +for authorship of the modifications in the Modified Version, together with +at least five of the principal authors of the Document (all of its principal +authors, if it has fewer than five), unless they release you from this requirement. + +C. State on the Title page the name of the publisher of the Modified Version, +as the publisher. + + D. Preserve all the copyright notices of the Document. + +E. Add an appropriate copyright notice for your modifications adjacent to +the other copyright notices. + +F. Include, immediately after the copyright notices, a license notice giving +the public permission to use the Modified Version under the terms of this +License, in the form shown in the Addendum below. + +G. Preserve in that license notice the full lists of Invariant Sections and +required Cover Texts given in the Document's license notice. H. Include an +unaltered copy of this License. + +I. Preserve the section Entitled "History", Preserve its Title, and add to +it an item stating at least the title, year, new authors, and publisher of +the Modified Version as given on the Title Page. If there is no section Entitled +"History" in the Document, create one stating the title, year, authors, and +publisher of the Document as given on its Title Page, then add an item describing +the Modified Version as stated in the previous sentence. + +J. Preserve the network location, if any, given in the Document for public +access to a Transparent copy of the Document, and likewise the network locations +given in the Document for previous versions it was based on. These may be +placed in the "History" section. You may omit a network location for a work +that was published at least four years before the Document itself, or if the +original publisher of the version it refers to gives permission. + +K. For any section Entitled "Acknowledgements" or "Dedications", Preserve +the Title of the section, and preserve in the section all the substance and +tone of each of the contributor acknowledgements and/or dedications given +therein. + +L. Preserve all the Invariant Sections of the Document, unaltered in their +text and in their titles. Section numbers or the equivalent are not considered +part of the section titles. + +M. Delete any section Entitled "Endorsements". Such a section may not be included +in the Modified Version. + +N. Do not retitle any existing section to be Entitled "Endorsements" or to +conflict in title with any Invariant Section. + + O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or appendices that +qualify as Secondary Sections and contain no material copied from the Document, +you may at your option designate some or all of these sections as invariant. +To do this, add their titles to the list of Invariant Sections in the Modified +Version's license notice. These titles must be distinct from any other section +titles. + +You may add a section Entitled "Endorsements", provided it contains nothing +but endorsements of your Modified Version by various parties--for example, +statements of peer review or that the text has been approved by an organization +as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage +of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts +in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover +Text may be added by (or through arrangements made by) any one entity. If +the Document already includes a cover text for the same cover, previously +added by you or by arrangement made by the same entity you are acting on behalf +of, you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give +permission to use their names for publicity for or to assert or imply endorsement +of any Modified Version. + + 5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, +under the terms defined in section 4 above for modified versions, provided +that you include in the combination all of the Invariant Sections of all of +the original documents, unmodified, and list them all as Invariant Sections +of your combined work in its license notice, and that you preserve all their +Warranty Disclaimers. + +The combined work need only contain one copy of this License, and multiple +identical Invariant Sections may be replaced with a single copy. If there +are multiple Invariant Sections with the same name but different contents, +make the title of each such section unique by adding at the end of it, in +parentheses, the name of the original author or publisher of that section +if known, or else a unique number. Make the same adjustment to the section +titles in the list of Invariant Sections in the license notice of the combined +work. + +In the combination, you must combine any sections Entitled "History" in the +various original documents, forming one section Entitled "History"; likewise +combine any sections Entitled "Acknowledgements", and any sections Entitled +"Dedications". You must delete all sections Entitled "Endorsements". + + 6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released +under this License, and replace the individual copies of this License in the +various documents with a single copy that is included in the collection, provided +that you follow the rules of this License for verbatim copying of each of +the documents in all other respects. + +You may extract a single document from such a collection, and distribute it +individually under this License, provided you insert a copy of this License +into the extracted document, and follow this License in all other respects +regarding verbatim copying of that document. + + 7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent +documents or works, in or on a volume of a storage or distribution medium, +is called an "aggregate" if the copyright resulting from the compilation is +not used to limit the legal rights of the compilation's users beyond what +the individual works permit. When the Document is included in an aggregate, +this License does not apply to the other works in the aggregate which are +not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of +the Document, then if the Document is less than one half of the entire aggregate, +the Document's Cover Texts may be placed on covers that bracket the Document +within the aggregate, or the electronic equivalent of covers if the Document +is in electronic form. Otherwise they must appear on printed covers that bracket +the whole aggregate. + + 8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations +of the Document under the terms of section 4. Replacing Invariant Sections +with translations requires special permission from their copyright holders, +but you may include translations of some or all Invariant Sections in addition +to the original versions of these Invariant Sections. You may include a translation +of this License, and all the license notices in the Document, and any Warranty +Disclaimers, provided that you also include the original English version of +this License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version of +this License or a notice or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", "Dedications", +or "History", the requirement (section 4) to Preserve its Title (section 1) +will typically require changing the actual title. + + 9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense, or distribute it is void, and will automatically terminate your +rights under this License. + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, receipt +of a copy of some or all of the same material does not give you any rights +to use it. + + 10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU +Free Documentation License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the +Document specifies that a particular numbered version of this License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that specified version or of any later version that +has been published (not as a draft) by the Free Software Foundation. If the +Document does not specify a version number of this License, you may choose +any version ever published (not as a draft) by the Free Software Foundation. +If the Document specifies that a proxy can decide which future versions of +this License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Document. + + 11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide +Web server that publishes copyrightable works and also provides prominent +facilities for anybody to edit those works. A public wiki that anybody can +edit is an example of such a server. A "Massive Multiauthor Collaboration" +(or "MMC") contained in the site means any set of copyrightable works thus +published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license +published by Creative Commons Corporation, a not-for-profit corporation with +a principal place of business in San Francisco, California, as well as future +copyleft versions of that license published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in part, +as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this License, +and if all works that were first published under this License somewhere other +than this MMC, and subsequently incorporated in whole or in part into the +MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated +prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site under +CC-BY-SA on the same site at any time before August 1, 2009, provided the +MMC is eligible for relicensing. ADDENDUM: How to use this License for your +documents + +To use this License in a document you have written, include a copy of the +License in the document and put the following copyright and license notices +just after the title page: + +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or +modify this document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation +License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace +the "with...Texts." line with this: + +with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover +Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other combination +of the three, merge those two alternatives to suit the situation. + +If your document contains nontrivial examples of program code, we recommend +releasing these examples in parallel under your choice of free software license, +such as the GNU General Public License, to permit their use in free software. diff --git a/options/license/GFDL-1.3-or-later b/options/license/GFDL-1.3-or-later index 1edf769ac05a..90f814dea736 100644 --- a/options/license/GFDL-1.3-or-later +++ b/options/license/GFDL-1.3-or-later @@ -395,11 +395,11 @@ To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: -Copyright (c) YEAR YOUR NAME . Permission is granted to copy, distribute and/or +Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; -with no Invariant Sections , no Front-Cover Texts , and no Back-Cover Texts -. A copy of the license is included in the section entitled "GNU Free Documentation +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. +A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace diff --git a/options/license/GLWTPL b/options/license/GLWTPL new file mode 100644 index 000000000000..e919dca7bed5 --- /dev/null +++ b/options/license/GLWTPL @@ -0,0 +1,22 @@ +GLWT(Good Luck With That) Public License Copyright (c) Everyone, except Author + +Everyone is permitted to copy, distribute, modify, merge, sell, publish, sublicense +or whatever they want with this software but at their OWN RISK. + +Preamble + +The author has absolutely no clue what the code in this project does. It might +just work or not, there is no third option. + +GOOD LUCK WITH THAT PUBLIC LICENSE + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION + +0. You just DO WHATEVER YOU WANT TO as long as you NEVER LEAVE A TRACE TO +TRACK THE AUTHOR of the original product to blame for or hold responsible. + +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Good luck and Godspeed. diff --git a/options/license/GPL-3.0-linking-exception b/options/license/GPL-3.0-linking-exception new file mode 100644 index 000000000000..76e4173d5ee4 --- /dev/null +++ b/options/license/GPL-3.0-linking-exception @@ -0,0 +1,3 @@ +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license] , the licensors of this Program grant you additional permission to convey the resulting work. diff --git a/options/license/GPL-3.0-linking-source-exception b/options/license/GPL-3.0-linking-source-exception new file mode 100644 index 000000000000..83d5c0453799 --- /dev/null +++ b/options/license/GPL-3.0-linking-source-exception @@ -0,0 +1,3 @@ +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license] , the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work. diff --git a/options/license/Hippocratic-2.1 b/options/license/Hippocratic-2.1 new file mode 100644 index 000000000000..302178eb7e41 --- /dev/null +++ b/options/license/Hippocratic-2.1 @@ -0,0 +1,129 @@ +[SOFTWARE NAME] Copyright (C) (YEAR) (COPYRIGHT HOLDER(S)/AUTHOR(S) ("Licensor") +Hippocratic License Version Number: 2.1. + +Purpose. The purpose of this License is for the Licensor named above to permit +the Licensee (as defined below) broad permission, if consistent with Human +Rights Laws and Human Rights Principles (as each is defined below), to use +and work with the Software (as defined below) within the full scope of Licensor's +copyright and patent rights, if any, in the Software, while ensuring attribution +and protecting the Licensor from liability. + +Permission and Conditions. The Licensor grants permission by this license +("License"), free of charge, to the extent of Licensor's rights under applicable +copyright and patent law, to any person or entity (the "Licensee") obtaining +a copy of this software and associated documentation files (the "Software"), +to do everything with the Software that would otherwise infringe (i) the Licensor's +copyright in the Software or (ii) any patent claims to the Software that the +Licensor can license or becomes able to license, subject to all of the following +terms and conditions: + +* Acceptance. This License is automatically offered to every person and entity +subject to its terms and conditions. Licensee accepts this License and agrees +to its terms and conditions by taking any action with the Software that, absent +this License, would infringe any intellectual property right held by Licensor. + +* Notice. Licensee must ensure that everyone who gets a copy of any part of +this Software from Licensee, with or without changes, also receives the License +and the above copyright notice (and if included by the Licensor, patent, trademark +and attribution notice). Licensee must cause any modified versions of the +Software to carry prominent notices stating that Licensee changed the Software. +For clarity, although Licensee is free to create modifications of the Software +and distribute only the modified portion created by Licensee with additional +or different terms, the portion of the Software not modified must be distributed +pursuant to this License. If anyone notifies Licensee in writing that Licensee +has not complied with this Notice section, Licensee can keep this License +by taking all practical steps to comply within 30 days after the notice. If +Licensee does not do so, Licensee's License (and all rights licensed hereunder) +shall end immediately. + + * Compliance with Human Rights Principles and Human Rights Laws. + + 1. Human Rights Principles. + +(a) Licensee is advised to consult the articles of the United Nations Universal +Declaration of Human Rights and the United Nations Global Compact that define +recognized principles of international human rights (the "Human Rights Principles"). +Licensee shall use the Software in a manner consistent with Human Rights Principles. + +(b) Unless the Licensor and Licensee agree otherwise, any dispute, controversy, +or claim arising out of or relating to (i) Section 1(a) regarding Human Rights +Principles, including the breach of Section 1(a), termination of this License +for breach of the Human Rights Principles, or invalidity of Section 1(a) or +(ii) a determination of whether any Law is consistent or in conflict with +Human Rights Principles pursuant to Section 2, below, shall be settled by +arbitration in accordance with the Hague Rules on Business and Human Rights +Arbitration (the "Rules"); provided, however, that Licensee may elect not +to participate in such arbitration, in which event this License (and all rights +licensed hereunder) shall end immediately. The number of arbitrators shall +be one unless the Rules require otherwise. + +Unless both the Licensor and Licensee agree to the contrary: (1) All documents +and information concerning the arbitration shall be public and may be disclosed +by any party; (2) The repository referred to under Article 43 of the Rules +shall make available to the public in a timely manner all documents concerning +the arbitration which are communicated to it, including all submissions of +the parties, all evidence admitted into the record of the proceedings, all +transcripts or other recordings of hearings and all orders, decisions and +awards of the arbitral tribunal, subject only to the arbitral tribunal's powers +to take such measures as may be necessary to safeguard the integrity of the +arbitral process pursuant to Articles 18, 33, 41 and 42 of the Rules; and +(3) Article 26(6) of the Rules shall not apply. + +2. Human Rights Laws. The Software shall not be used by any person or entity +for any systems, activities, or other uses that violate any Human Rights Laws. +"Human Rights Laws" means any applicable laws, regulations, or rules (collectively, +"Laws") that protect human, civil, labor, privacy, political, environmental, +security, economic, due process, or similar rights; provided, however, that +such Laws are consistent and not in conflict with Human Rights Principles +(a dispute over the consistency or a conflict between Laws and Human Rights +Principles shall be determined by arbitration as stated above). Where the +Human Rights Laws of more than one jurisdiction are applicable or in conflict +with respect to the use of the Software, the Human Rights Laws that are most +protective of the individuals or groups harmed shall apply. + +3. Indemnity. Licensee shall hold harmless and indemnify Licensor (and any +other contributor) against all losses, damages, liabilities, deficiencies, +claims, actions, judgments, settlements, interest, awards, penalties, fines, +costs, or expenses of whatever kind, including Licensor's reasonable attorneys' +fees, arising out of or relating to Licensee's use of the Software in violation +of Human Rights Laws or Human Rights Principles. + +* Failure to Comply. Any failure of Licensee to act according to the terms +and conditions of this License is both a breach of the License and an infringement +of the intellectual property rights of the Licensor (subject to exceptions +under Laws, e.g., fair use). In the event of a breach or infringement, the +terms and conditions of this License may be enforced by Licensor under the +Laws of any jurisdiction to which Licensee is subject. Licensee also agrees +that the Licensor may enforce the terms and conditions of this License against +Licensee through specific performance (or similar remedy under Laws) to the +extent permitted by Laws. For clarity, except in the event of a breach of +this License, infringement, or as otherwise stated in this License, Licensor +may not terminate this License with Licensee. + +* Enforceability and Interpretation. If any term or provision of this License +is determined to be invalid, illegal, or unenforceable by a court of competent +jurisdiction, then such invalidity, illegality, or unenforceability shall +not affect any other term or provision of this License or invalidate or render +unenforceable such term or provision in any other jurisdiction; provided, +however, subject to a court modification pursuant to the immediately following +sentence, if any term or provision of this License pertaining to Human Rights +Laws or Human Rights Principles is deemed invalid, illegal, or unenforceable +against Licensee by a court of competent jurisdiction, all rights in the Software +granted to Licensee shall be deemed null and void as between Licensor and +Licensee. Upon a determination that any term or provision is invalid, illegal, +or unenforceable, to the extent permitted by Laws, the court may modify this +License to affect the original purpose that the Software be used in compliance +with Human Rights Principles and Human Rights Laws as closely as possible. +The language in this License shall be interpreted as to its fair meaning and +not strictly for or against any party. + +* Disclaimer. TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES "AS IS," +WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR AND ANY OTHER CONTRIBUTOR +SHALL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, +OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY KIND +OF LEGAL CLAIM. + +This Hippocratic License is an Ethical Source license (https://ethicalsource.dev) +and is offered for use by licensors and licensees at their own risk, on an +"AS IS" basis, and with no warranties express or implied, to the maximum extent +permitted by Laws. diff --git a/options/license/LGPL-3.0-linking-exception b/options/license/LGPL-3.0-linking-exception new file mode 100644 index 000000000000..28149b0b5c2f --- /dev/null +++ b/options/license/LGPL-3.0-linking-exception @@ -0,0 +1,3 @@ +As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. diff --git a/options/license/MulanPSL-2.0 b/options/license/MulanPSL-2.0 new file mode 100644 index 000000000000..df0d9ac15149 --- /dev/null +++ b/options/license/MulanPSL-2.0 @@ -0,0 +1,187 @@ +木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 + +2020年1月 http://license.coscl.org.cn/MulanPSL2 + +您对"软件"的复制、使用、修改及分发受木兰宽松许可证,第2版("本许可证")的如下条款的约束: + + 0. 定义 + + "软件" 是指由"贡献"构成的许可在"本许可证"下的程序和相关文档的集合。 + + "贡献" 是指由任一"贡献者"许可在"本许可证"下的受版权法保护的作品。 + + "贡献者" 是指将受版权法保护的作品许可在"本许可证"下的自然人或"法人实体"。 + + "法人实体" 是指提交贡献的机构及其"关联实体"。 + +"关联实体" 是指,对"本许可证"下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + 1. 授予版权许可 + + 每个"贡献者"根据"本许可证"授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其"贡献",不论修改与否。 + + 2. 授予专利许可 + +每个"贡献者"根据"本许可证"授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其"贡献"或以其他方式转移其"贡献"。前述专利许可仅限于"贡献者"现在或将来拥有或控制的其"贡献"本身或其"贡献"与许可"贡献"时的"软件"结合而将必然会侵犯的专利权利要求,不包括对"贡献"的修改或包含"贡献"的其他结合。如果您或您的"关联实体"直接或间接地,就"软件"或其中的"贡献"对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则"本许可证"授予您对"软件"的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + "本许可证"不提供对"贡献者"的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + +您可以在任何媒介中将"软件"以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供"本许可证"的副本,并保留"软件"中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + +"软件"及其中的"贡献"在提供时不带任何明示或默示的担保。在任何情况下,"贡献者"或版权所有者不对任何人因使用"软件"或其中的"贡献"而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 6. 语言 + + "本许可证"以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 + +条款结束 + +如何将木兰宽松许可证,第2版,应用到您的软件 + +如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以"LICENSE"为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + +Copyright (c) [Year] [name of copyright holder] + +[Software Name] is licensed under Mulan PSL v2. + +You can use this software according to the terms and conditions of the Mulan +PSL v2. + +You may obtain a copy of Mulan PSL v2 at: + +http://license.coscl.org.cn/MulanPSL2 + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + +See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version +2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) + +January 2020 http://license.coscl.org.cn/MulanPSL2 + +Your reproduction, use, modification and distribution of the Software shall +be subject to Mulan PSL v2 (this License) with the following terms and conditions: + + 0. Definition + +Software means the program and related documents which are licensed under +this License and comprise all Contribution(s). + +Contribution means the copyrightable work licensed by a particular Contributor +under this License. + +Contributor means the Individual or Legal Entity who licenses its copyrightable +work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + +Affiliates means entities that control, are controlled by, or are under common +control with the acting entity under this License, 'control' means direct +or indirect ownership of at least fifty percent (50%) of the voting power, +capital or other securities of controlled or commonly controlled entity. + + 1. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +copyright license to reproduce, use, modify, or distribute its Contribution, +with modification or not. + + 2. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(except for revocation under this Section) patent license to make, have made, +use, offer for sale, sell, import or otherwise transfer its Contribution, +where such patent license is only limited to the patent claims owned or controlled +by such Contributor now or in future which will be necessarily infringed by +its Contribution alone, or by combination of the Contribution with the Software +to which the Contribution was contributed. The patent license shall not apply +to any modification of the Contribution, and any other combination which includes +the Contribution. If you or your Affiliates directly or indirectly institute +patent litigation (including a cross claim or counterclaim in a litigation) +or other patent enforcement activities against any individual or entity by +alleging that the Software or any Contribution in it infringes patents, then +any patent license granted to you under this License for the Software shall +terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + +No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements in section 4. + + 4. Distribution Restriction + +You may distribute the Software in any medium with or without modification, +whether in source or executable forms, provided that you provide recipients +with a copy of this License and retain copyright, patent, trademark and disclaimer +statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + +THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY +KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT +HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY +DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE +OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW +IT'S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + + 6. Language + +THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION +AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE +BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. + +END OF THE TERMS AND CONDITIONS + +How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) +to Your Software + +To apply the Mulan PSL v2 to your work, for easy identification by recipients, +you are suggested to complete following three steps: + +i. Fill in the blanks in following statement, including insert your software +name, the year of the first publication of your software, and your name identified +as the copyright owner; + +ii. Create a file named "LICENSE" which contains the whole context of this +License in the first directory of your software package; + +iii. Attach the statement to the appropriate annotated syntax at the beginning +of each source file. + +Copyright (c) [Year] [name of copyright holder] + +[Software Name] is licensed under Mulan PSL v2. + +You can use this software according to the terms and conditions of the Mulan +PSL v2. + +You may obtain a copy of Mulan PSL v2 at: + +http://license.coscl.org.cn/MulanPSL2 + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + +See the Mulan PSL v2 for more details. diff --git a/options/license/NCGL-UK-2.0 b/options/license/NCGL-UK-2.0 new file mode 100644 index 000000000000..44b8c79e3ec4 --- /dev/null +++ b/options/license/NCGL-UK-2.0 @@ -0,0 +1,138 @@ +Non-Commercial Government Licence + +for public sector information + +You are encouraged to use and re-use the Information that is available under +this licence freely and flexibly, with only a few conditions. + +Using information under this licence + +Use of copyright and database right material expressly made available under +this licence (the 'Information') indicates your acceptance of the terms and +conditions below. + +The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive +licence to use the Information for Non-Commercial purposes only subject to +the conditions below. + +This licence does not affect your freedom under fair dealing or fair use or +any other copyright or database right exceptions and limitations. + +You are free to: + + copy, publish, distribute and transmit the Information; + + adapt the Information; + +exploit the Information for Non-Commercial purposes for example, by combining +it with other information in your own product or application. + +You are not permitted to: + +exercise any of the rights granted to you by this licence in any manner that +is primarily intended for or directed toward commercial advantage or private +monetary compensation. + +You must, where you do any of the above: + +acknowledge the source of the Information by including any attribution statement +specified by the Information Provider(s) and, where possible, provide a link +to this licence; + +If the Information Provider does not provide a specific attribution statement, +you must use the following: + +Contains information licensed under the Non-Commercial Government Licence +v2.0. + +If you are using Information from several Information Providers and listing +multiple attributions is not practical in your product or application, you +may include a URI or hyperlink to a resource that contains the required attribution +statements. + +ensure that any onward licensing of the Information – for example when combined +with other information – is for Non-Commercial purposes only. + +These are important conditions of this licence and if you fail to comply with +them or use the Information other than for Non-Commercial purposes the rights +granted to you under this licence, or any similar licence granted by the Licensor, +will end automatically. + +Exemptions + +This licence does not cover the use of: + + • personal data in the Information; + +• Information that has not been accessed by way of publication or disclosure +under information access legislation (including the Freedom of Information +Acts for the UK and Scotland) by or with the consent of the Information Provider; + +• departmental or public sector organisation logos, crests, military insignia +and the Royal Arms except where they form an integral part of a document or +dataset; + + • military insignia + + • third party rights the Information Provider is not authorised to license; + +• other intellectual property rights, including patents, trade marks, and +design rights; and + + • identity documents such as the British Passport. + +Non-endorsement + +This licence does not grant you any right to use the Information in a way +that suggests any official status or that the Information Provider and/or +Licensor endorse you or your use of the Information. + +No warranty + +The Information is licensed 'as is' and the Information Provider excludes +all representations, warranties, obligations and liabilities in relation to +the Information to the maximum extent permitted by law. + +The Information Provider is not liable for any errors or omissions in the +Information and shall not be liable for any loss, injury or damage of any +kind caused by its use. The Information Provider does not guarantee the continued +supply of the Information. + +Governing Law + +This licence is governed by the laws of the jurisdiction in which the Information +Provider has its principal place of business, unless otherwise specified by +the Information Provider. + +Definitions + +In this licence the terms below have the following meanings: + +'Information' means information protected by copyright or by database right +(for example, literary and artistic works, content, data and source code) +offered for use under the terms of this licence. + +'Information Provider' means the person or organisation providing the Information +under this licence. + +'Licensor' means any Information Provider which has the authority to offer +Information under the terms of this licence or the Keeper of the Public Records, +who has the authority to offer Information subject to Crown copyright and +Crown database rights and Information subject to copyright and database right +that has been assigned to or acquired by the Crown, under the terms of this +licence. + +'Non-Commercial purposes' means not intended for or directed toward commercial +advantage or private monetary compensation. For the purposes of this licence, +'private monetary compensation' does not include the exchange of the Information +for other copyrighted works by means of digital file-sharing or otherwise +provided there is no payment of any monetary compensation in connection with +the exchange of the Information. + +'Use' as a verb, means doing any act which is restricted by copyright or database +right, whether in the original medium or in any other medium, and includes +without limitation distributing, copying, adapting, modifying as may be technically +necessary to use it in a different mode or format. + +'You' means the natural or legal person, or body of persons corporate or incorporate, +acquiring rights under this licence. diff --git a/options/license/NIST-PD b/options/license/NIST-PD new file mode 100644 index 000000000000..76938cde50fd --- /dev/null +++ b/options/license/NIST-PD @@ -0,0 +1,15 @@ +Terms Of Use + +This software was developed by employees of the National Institute of Standards +and Technology (NIST), and others. This software has been contributed to the +public domain. Pursuant to title 15 Untied States Code Section 105, works +of NIST employees are not subject to copyright protection in the United States +and are considered to be in the public domain. As a result, a formal license +is not needed to use this software. + +This software is provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, +IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND +DATA ACCURACY. NIST does not warrant or make any representations regarding +the use of the software or the results thereof, including but not limited +to the correctness, accuracy, reliability or usefulness of this software. diff --git a/options/license/NIST-PD-fallback b/options/license/NIST-PD-fallback new file mode 100644 index 000000000000..9eacb5fd78a8 --- /dev/null +++ b/options/license/NIST-PD-fallback @@ -0,0 +1,25 @@ +Conditions of Use + +This software was developed by employees of the National Institute of Standards +and Technology (NIST), an agency of the Federal Government and is being made +available as a public service. Pursuant to title 17 United States Code Section +105, works of NIST employees are not subject to copyright protection in the +United States. This software may be subject to foreign copyright. Permission +in the United States and in foreign countries, to the extent that NIST may +hold copyright, to use, copy, modify, create derivative works, and distribute +this software and its documentation without fee is hereby granted on a non-exclusive +basis, provided that this notice and disclaimer of warranty appears in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER +EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY +THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM INFRINGEMENT, +AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY +WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT SHALL NIST BE LIABLE +FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL +OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED +WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT, OR +OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR PROPERTY OR OTHERWISE, +AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT OF THE RESULTS OF, +OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. diff --git a/options/license/NTP-0 b/options/license/NTP-0 new file mode 100644 index 000000000000..248aa1a1d0a3 --- /dev/null +++ b/options/license/NTP-0 @@ -0,0 +1,8 @@ +NTP No Attribution (NTP-0) Copyright (4-digit-year) by (CopyrightHoldersName) + +Permission to use, copy, modify, and distribute this software and its documentation +for any purpose is hereby granted, provided that the namesof (TrademarkedName) +not be used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. (TrademarkedName) makesno +representations about the suitability of this software for any purpose. It +is provided "as is" without express or implied warranty. diff --git a/options/license/O-UDA-1.0 b/options/license/O-UDA-1.0 new file mode 100644 index 000000000000..5ce017119c06 --- /dev/null +++ b/options/license/O-UDA-1.0 @@ -0,0 +1,77 @@ +Open Use of Data Agreement v1.0 + +This is the Open Use of Data Agreement, Version 1.0 (the "O-UDA"). Capitalized +terms are defined in Section 5. Data Provider and you agree as follows: + + 1. Provision of the Data + +1.1. You may use, modify, and distribute the Data made available to you by +the Data Provider under this O-UDA if you follow the O-UDA's terms. + +1.2. Data Provider will not sue you or any Downstream Recipient for any claim +arising out of the use, modification, or distribution of the Data provided +you meet the terms of the O-UDA. + +1.3. This O-UDA does not restrict your use, modification, or distribution +of any portions of the Data that are in the public domain or that may be used, +modified, or distributed under any other legal exception or limitation. + + 2. No Restrictions on Use or Results + + 2.1. The O-UDA does not impose any restriction with respect to: + + 2.1.1. the use or modification of Data; or + + 2.1.2. the use, modification, or distribution of Results. + + 3. Redistribution of Data + +3.1. You may redistribute the Data under terms of your choice, so long as: + +3.1.1. You include with any Data you redistribute all credit or attribution +information that you received with the Data, and your terms require any Downstream +Recipient to do the same; and + +3.1.2. Your terms include a warranty disclaimer and limitation of liability +for Upstream Data Providers at least as broad as those contained in Section +4.2 and 4.3 of the O-UDA. + + 4. No Warranty, Limitation of Liability + +4.1. Data Provider does not represent or warrant that it has any rights whatsoever +in the Data. + +4.2. THE DATA IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS +FOR A PARTICULAR PURPOSE. + +4.3. NEITHER DATA PROVIDER NOR ANY UPSTREAM DATA PROVIDER SHALL HAVE ANY LIABILITY +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE DATA OR RESULTS, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 5. Definitions + +5.1. "Data" means the material you receive under the O-UDA in modified or +unmodified form, but not including Results. + +5.2. "Data Provider" means the source from which you receive the Data and +with whom you enter into the O-UDA. + +5.3. "Downstream Recipient" means any person or persons who receives the Data +directly or indirectly from you in accordance with the O-UDA. + +5.4. "Result" means anything that you develop or improve from your use of +Data that does not include more than a de minimis portion of the Data on which +the use is based. Results may include de minimis portions of the Data necessary +to report on or explain use that has been conducted with the Data, such as +figures in scientific papers, but do not include more. Artificial intelligence +models trained on Data (and which do not include more than a de minimis portion +of Data) are Results. + +5.5. "Upstream Data Providers" means the source or sources from which the +Data Provider directly or indirectly received, under the terms of the O-UDA, +material that is included in the Data. diff --git a/options/license/OFL-1.0-RFN b/options/license/OFL-1.0-RFN new file mode 100644 index 000000000000..ad436fd2c871 --- /dev/null +++ b/options/license/OFL-1.0-RFN @@ -0,0 +1,92 @@ +SIL OPEN FONT LICENSE + +Version 1.0 - 22 November 2005 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of cooperative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide an open framework in which fonts +may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and sold with any +software provided that the font names of derivative works are changed. The +fonts and derivatives, however, cannot be released under any other type of +license. + +DEFINITIONS + +"Font Software" refers to any and all of the following: + + - font files + + - data files + + - source code + + - build scripts + + - documentation + +"Reserved Font Name" refers to the Font Software name as seen by users and +any other names as specified after the copyright statement. + +"Standard Version" refers to the collection of Font Software components as +distributed by the Copyright Holder. + +"Modified Version" refers to any derivative font software made by adding to, +deleting, or substituting — in part or in whole -- any of the components of +the Standard Version, by changing formats or by porting the Font Software +to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1) Neither the Font Software nor any of its individual components, in Standard +or Modified Versions, may be sold by itself. + +2) Standard or Modified Versions of the Font Software may be bundled, redistributed +and sold with any software, provided that each copy contains the above copyright +notice and this license. These can be included either as stand-alone text +files, human-readable headers or in the appropriate machine-readable metadata +fields within text or binary files as long as those fields can be easily viewed +by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s), +in part or in whole, unless explicit written permission is granted by the +Copyright Holder. This restriction applies to all references stored in the +Font Software, such as the font menu name and other font description fields, +which are used to differentiate the font from others. + +4) The name(s) of the Copyright Holder or the Author(s) of the Font Software +shall not be used to promote, endorse or advertise any Modified Version, except +to acknowledge the contribution(s) of the Copyright Holder and the Author(s) +or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed using this license, and may not be distributed under any other +license. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/options/license/OFL-1.0-no-RFN b/options/license/OFL-1.0-no-RFN new file mode 100644 index 000000000000..ad436fd2c871 --- /dev/null +++ b/options/license/OFL-1.0-no-RFN @@ -0,0 +1,92 @@ +SIL OPEN FONT LICENSE + +Version 1.0 - 22 November 2005 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of cooperative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide an open framework in which fonts +may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and sold with any +software provided that the font names of derivative works are changed. The +fonts and derivatives, however, cannot be released under any other type of +license. + +DEFINITIONS + +"Font Software" refers to any and all of the following: + + - font files + + - data files + + - source code + + - build scripts + + - documentation + +"Reserved Font Name" refers to the Font Software name as seen by users and +any other names as specified after the copyright statement. + +"Standard Version" refers to the collection of Font Software components as +distributed by the Copyright Holder. + +"Modified Version" refers to any derivative font software made by adding to, +deleting, or substituting — in part or in whole -- any of the components of +the Standard Version, by changing formats or by porting the Font Software +to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1) Neither the Font Software nor any of its individual components, in Standard +or Modified Versions, may be sold by itself. + +2) Standard or Modified Versions of the Font Software may be bundled, redistributed +and sold with any software, provided that each copy contains the above copyright +notice and this license. These can be included either as stand-alone text +files, human-readable headers or in the appropriate machine-readable metadata +fields within text or binary files as long as those fields can be easily viewed +by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s), +in part or in whole, unless explicit written permission is granted by the +Copyright Holder. This restriction applies to all references stored in the +Font Software, such as the font menu name and other font description fields, +which are used to differentiate the font from others. + +4) The name(s) of the Copyright Holder or the Author(s) of the Font Software +shall not be used to promote, endorse or advertise any Modified Version, except +to acknowledge the contribution(s) of the Copyright Holder and the Author(s) +or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed using this license, and may not be distributed under any other +license. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/options/license/OFL-1.1-RFN b/options/license/OFL-1.1-RFN new file mode 100644 index 000000000000..084c9628a625 --- /dev/null +++ b/options/license/OFL-1.1-RFN @@ -0,0 +1,90 @@ +Copyright (c) , (), + +with Reserved Font Name . This Font Software is licensed +under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of collaborative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide a free and open framework in which +fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and/or sold with +any software provided that any reserved names are not used by derivative works. +The fonts and derivatives, however, cannot be released under any other type +of license. The requirement for fonts to remain under this license does not +apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) +under this license and clearly marked as such. This may include source files, +build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright +statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting — in part or in whole — any of the components of the Original +Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1) Neither the Font Software nor any of its individual components, in Original +or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed +and/or sold with any software, provided that each copy contains the above +copyright notice and this license. These can be included either as stand-alone +text files, human-readable headers or in the appropriate machine-readable +metadata fields within text or binary files as long as those fields can be +easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) +unless explicit written permission is granted by the corresponding Copyright +Holder. This restriction only applies to the primary font name as presented +to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software +shall not be used to promote, endorse or advertise any Modified Version, except +to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) +or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed entirely under this license, and must not be distributed under +any other license. The requirement for fonts to remain under this license +does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/options/license/OFL-1.1-no-RFN b/options/license/OFL-1.1-no-RFN new file mode 100644 index 000000000000..521f99da262b --- /dev/null +++ b/options/license/OFL-1.1-no-RFN @@ -0,0 +1,88 @@ +Copyright (c) , (). This Font Software +is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of collaborative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide a free and open framework in which +fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and/or sold with +any software provided that any reserved names are not used by derivative works. +The fonts and derivatives, however, cannot be released under any other type +of license. The requirement for fonts to remain under this license does not +apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) +under this license and clearly marked as such. This may include source files, +build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright +statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting — in part or in whole — any of the components of the Original +Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1) Neither the Font Software nor any of its individual components, in Original +or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed +and/or sold with any software, provided that each copy contains the above +copyright notice and this license. These can be included either as stand-alone +text files, human-readable headers or in the appropriate machine-readable +metadata fields within text or binary files as long as those fields can be +easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) +unless explicit written permission is granted by the corresponding Copyright +Holder. This restriction only applies to the primary font name as presented +to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software +shall not be used to promote, endorse or advertise any Modified Version, except +to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) +or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be +distributed entirely under this license, and must not be distributed under +any other license. The requirement for fonts to remain under this license +does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/options/license/OGC-1.0 b/options/license/OGC-1.0 new file mode 100644 index 000000000000..e7227d0b2af5 --- /dev/null +++ b/options/license/OGC-1.0 @@ -0,0 +1,39 @@ +OGC Software License, Version 1.0 + +This OGC work (including software, documents, or other related items) is being +provided by the copyright holders under the following license. By obtaining, +using and/or copying this work, you (the licensee) agree that you have read, +understood, and will comply with the following terms and conditions: + +Permission to use, copy, and modify this software and its documentation, with +or without modification, for any purpose and without fee or royalty is hereby +granted, provided that you include the following on ALL copies of the software +and documentation or portions thereof, including modifications, that you make: + +1. The full text of this NOTICE in a location viewable to users of the redistributed +or derivative work. + +2. Any pre-existing intellectual property disclaimers, notices, or terms and +conditions. If none exist, a short notice of the following form (hypertext +is preferred, text is permitted) should be used within the body of any redistributed +or derivative code: "Copyright © [$date-of-document] Open Geospatial Consortium, +Inc. All Rights Reserved. http://www. ogc .org/ogc/legal (Hypertext is preferred, +but a textual representation is permitted.) + +3. Notice of any changes or modifications to the OGC files, including the +date changes were made. (We recommend you provide URIs to the location from +which the code is derived.) + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS +MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE +OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD +PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. + +The name and trademarks of copyright holders may NOT be used in advertising +or publicity pertaining to the software without specific, written prior permission. +Title to copyright in this software and any associated documentation will +at all times remain with copyright holders. diff --git a/options/license/PSF-2.0 b/options/license/PSF-2.0 new file mode 100644 index 000000000000..ff67bea898e2 --- /dev/null +++ b/options/license/PSF-2.0 @@ -0,0 +1,42 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), +and the Individual or Organization ("Licensee") accessing and otherwise using +this software ("Python") in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, provided, +however, that PSF's License Agreement and PSF's notice of copyright, i.e., +"Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or incorporates +Python or any part thereof, and wants to make the derivative work available +to others as provided herein, then Licensee hereby agrees to include in any +such work a brief summary of the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES +NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT +NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY +OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF +PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY +INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, +DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF +ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach +of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship +of agency, partnership, or joint venture between PSF and Licensee. This License +Agreement does not grant permission to use PSF trademarks or trade name in +a trademark sense to endorse or promote products or services of Licensee, +or any third party. + +8. By copying, installing or otherwise using Python, Licensee agrees to be +bound by the terms and conditions of this License Agreement. diff --git a/options/license/Parity-7.0.0 b/options/license/Parity-7.0.0 new file mode 100644 index 000000000000..96529f933a9d --- /dev/null +++ b/options/license/Parity-7.0.0 @@ -0,0 +1,100 @@ +# The Parity Public License 7.0.0 + +Contributor: name + +Source Code: address + +## Purpose + +This license allows you to use and share this software for free, but you have +to share software that builds on it alike. + +## Agreement + +In order to receive this license, you have to agree to its rules. Those rules +are both obligations under that agreement and conditions to your license. +Don't do anything with this software that triggers a rule you can't or won't +follow. + +## Notices + +Make sure everyone who gets a copy of any part of this software from you, +with or without changes, also gets the text of this license and the contributor +and source code lines above. + +## Copyleft + +[Contribute](#contribute) software you develop, operate, or analyze with this +software, including changes or additions to this software. When in doubt, +[contribute](#contribute) . + +## Prototypes + +You don't have to [contribute](#contribute) any change, addition, or other +software that meets all these criteria: + + 1. You don't use it for more than thirty days. + +2. You don't share it outside the team developing it, other than for non-production +user testing. + +3. You don't develop, operate, or analyze other software with it for anyone +outside the team developing it. + +## Reverse Engineering + +You may use this software to operate and analyze software you can't [contribute](#contribute) +in order to develop alternatives you can and do [contribute](#contribute) +. + +## Contribute + +To [contribute](#contribute) software: + +1. Publish all source code for the software in the preferred form for making +changes through a freely accessible distribution system widely used for similar +source code so the contributor and others can find and copy it. + +2. Make sure every part of the source code is available under this license +or another license that allows everything this license does, such as [the +Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the +Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the +MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html). + + 3. Take these steps within thirty days. + +4. Note that this license does _not_ allow you to change the license terms +for this software. You must follow [Notices](#notices) . + +## Excuse + +You're excused for unknowingly breaking [Copyleft](#copyleft) if you [contribute](#contribute) +as required, or stop doing anything requiring this license, within thirty +days of learning you broke the rule. You're excused for unknowingly breaking +[Notices](#notices) if you take all practical steps to comply within thirty +days of learning you broke the rule. + +## Defense + +Don't make any legal claim against anyone accusing this software, with or +without changes, alone or with other technology, of infringing any patent. + +## Copyright + +The contributor licenses you to do everything with this software that would +otherwise infringe their copyright in it. + +## Patent + +The contributor licenses you to do everything with this software that would +otherwise infringe any patents they can license or become able to license. + +## Reliability + +The contributor can't revoke this license. + +## No Liability + +***As far as the law allows, this software comes as is, without any warranty +or condition, and the contributor won't be liable to anyone for any damages +related to this software or this license, under any kind of legal claim.*** diff --git a/options/license/PolyForm-Noncommercial-1.0.0 b/options/license/PolyForm-Noncommercial-1.0.0 new file mode 100644 index 000000000000..f8837ce05e82 --- /dev/null +++ b/options/license/PolyForm-Noncommercial-1.0.0 @@ -0,0 +1,113 @@ +# PolyForm Noncommercial License 1.0.0 + + + +## Acceptance + +In order to get any license under these terms, you must agree to them as both +strict obligations and conditions to all your licenses. + +## Copyright License + +The licensor grants you a copyright license for the software to do everything +you might do with the software that would otherwise infringe the licensor's +copyright in it for any permitted purpose. However, you may only distribute +the software according to [Distribution License](#distribution-license) and +make changes or new works based on the software according to [Changes and +New Works License](#changes-and-new-works-license). + +## Distribution License + +The licensor grants you an additional copyright license to distribute copies +of the software. Your license to distribute covers distributing the software +with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license). + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from +you also gets a copy of these terms or the URL for them above, as well as +copies of any plain-text lines beginning with `Required Notice:` that the +licensor provided with the software. For example: + +> Required Notice: Copyright Yoyodyne, Inc. (http://example.com) + +## Changes and New Works License + +The licensor grants you an additional copyright license to make changes and +new works based on the software for any permitted purpose. + +## Patent License + +The licensor grants you a patent license for the software that covers patent +claims the licensor can license, or becomes able to license, that you would +infringe by using the software. + +## Noncommercial Purposes + +Any noncommercial purpose is a permitted purpose. + +## Personal Uses + +Personal use for research, experiment, and testing for the benefit of public +knowledge, personal study, private entertainment, hobby projects, amateur +pursuits, or religious observance, without any anticipated commercial application, +is use for a permitted purpose. + +## Noncommercial Organizations + +Use by any charitable organization, educational institution, public research +organization, public safety or health organization, environmental protection +organization, or government institution is use for a permitted purpose regardless +of the source of funding or obligations resulting from the funding. + +## Fair Use + +You may have "fair use" rights for the software under the law. These terms +do not limit them. + +## No Other Rights + +These terms do not allow you to sublicense or transfer any of your licenses +to anyone else, or prevent the licensor from granting licenses to anyone else. +These terms do not imply any other licenses. + +## Patent Defense + +If you make any written claim that the software infringes or contributes to +infringement of any patent, your patent license for the software granted under +these terms ends immediately. If your company makes such a claim, your patent +license ends immediately for work on behalf of your company. + +## Violations + +The first time you are notified in writing that you have violated any of these +terms, or done anything with the software not covered by your licenses, your +licenses can nonetheless continue if you come into full compliance with these +terms, and take practical steps to correct past violations, within 32 days +of receiving notice. Otherwise, all your licenses end immediately. + +## No Liability + +***As far as the law allows, the software comes as is, without any warranty +or condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim.*** + +## Definitions + +The **licensor** is the individual or entity offering these terms, and the +**software** is the software the licensor makes available under these terms. + +**You** refers to the individual or entity agreeing to these terms. + +**Your company** is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are under the control of, or are under common control with that organization. +**Control** means ownership of substantially all the assets of an entity, +or the power to direct its management and policies by vote, contract, or otherwise. +Control can be direct or indirect. + +**Your licenses** are all the licenses granted to you for the software under +these terms. + +**Use** means anything you do with the software requiring one of your licenses. diff --git a/options/license/PolyForm-Small-Business-1.0.0 b/options/license/PolyForm-Small-Business-1.0.0 new file mode 100644 index 000000000000..514d1d0cebcd --- /dev/null +++ b/options/license/PolyForm-Small-Business-1.0.0 @@ -0,0 +1,105 @@ +# PolyForm Small Business License 1.0.0 + + + +## Acceptance + +In order to get any license under these terms, you must agree to them as both +strict obligations and conditions to all your licenses. + +## Copyright License + +The licensor grants you a copyright license for the software to do everything +you might do with the software that would otherwise infringe the licensor's +copyright in it for any permitted purpose. However, you may only distribute +the software according to [Distribution License](#distribution-license) and +make changes or new works based on the software according to [Changes and +New Works License](#changes-and-new-works-license). + +## Distribution License + +The licensor grants you an additional copyright license to distribute copies +of the software. Your license to distribute covers distributing the software +with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license). + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from +you also gets a copy of these terms or the URL for them above, as well as +copies of any plain-text lines beginning with `Required Notice:` that the +licensor provided with the software. For example: + +> Required Notice: Copyright Yoyodyne, Inc. (http://example.com) + +## Changes and New Works License + +The licensor grants you an additional copyright license to make changes and +new works based on the software for any permitted purpose. + +## Patent License + +The licensor grants you a patent license for the software that covers patent +claims the licensor can license, or becomes able to license, that you would +infringe by using the software. + +## Fair Use + +You may have "fair use" rights for the software under the law. These terms +do not limit them. + +## Small Business + +Use of the software for the benefit of your company is use for a permitted +purpose if your company has fewer than 100 total individuals working as employees +and independent contractors, and less than 1,000,000 USD (2019) total revenue +in the prior tax year. Adjust this revenue threshold for inflation according +to the United States Bureau of Labor Statistics' consumer price index for +all urban consumers, U.S. city average, for all items, not seasonally adjusted, +with 1982–1984=100 reference base. + +## No Other Rights + +These terms do not allow you to sublicense or transfer any of your licenses +to anyone else, or prevent the licensor from granting licenses to anyone else. +These terms do not imply any other licenses. + +## Patent Defense + +If you make any written claim that the software infringes or contributes to +infringement of any patent, your patent license for the software granted under +these terms ends immediately. If your company makes such a claim, your patent +license ends immediately for work on behalf of your company. + +## Violations + +The first time you are notified in writing that you have violated any of these +terms, or done anything with the software not covered by your licenses, your +licenses can nonetheless continue if you come into full compliance with these +terms, and take practical steps to correct past violations, within 32 days +of receiving notice. Otherwise, all your licenses end immediately. + +## No Liability + +***As far as the law allows, the software comes as is, without any warranty +or condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim.*** + +## Definitions + +The **licensor** is the individual or entity offering these terms, and the +**software** is the software the licensor makes available under these terms. + +**You** refers to the individual or entity agreeing to these terms. + +**Your company** is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are under the control of, or are under common control with that organization. +**Control** means ownership of substantially all the assets of an entity, +or the power to direct its management and policies by vote, contract, or otherwise. +Control can be direct or indirect. + +**Your licenses** are all the licenses granted to you for the software under +these terms. + +**Use** means anything you do with the software requiring one of your licenses. diff --git a/options/license/SHL-2.0 b/options/license/SHL-2.0 new file mode 100644 index 000000000000..663b636f66f8 --- /dev/null +++ b/options/license/SHL-2.0 @@ -0,0 +1,23 @@ +# Solderpad Hardware Licence Version 2.0 + +This licence (the "Licence") operates as a wraparound licence to the Apache License Version 2.0 (the "Apache License") and grants to You the rights, and imposes the obligations, set out in the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following extensions. It must be read in conjunction with the Apache License. Section 1 below modifies definitions in the Apache License, and section 2 below replaces sections 2 of the Apache License. You may, at your option, choose to treat any Work released under this License as released under the Apache License (thus ignoring all sections written below entirely). Words in italics indicate changes rom the Apache License, but are indicative and not to be taken into account in interpretation. + + 1. The definitions set out in the Apache License are modified as follows: + + Copyright any reference to 'copyright' (whether capitalised or not) includes 'Rights' (as defined below). + + Contribution also includes any design, as well as any work of authorship. + + Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of the Work and Derivative Works thereof. + + Object form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works). + + Rights means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks). + + Source form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files. + + Work also includes a design or work of authorship, whether in Source form or other Object form. + + 2. Grant of Licence + + 2.1 Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist. diff --git a/options/license/SHL-2.1 b/options/license/SHL-2.1 new file mode 100644 index 000000000000..9de03f617b92 --- /dev/null +++ b/options/license/SHL-2.1 @@ -0,0 +1,45 @@ +SOLDERPAD HARDWARE LICENSE VERSION 2.1 + +This license operates as a wraparound license to the Apache License Version 2.0 (the "Apache License") and incorporates the terms and conditions of the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following additions and modifications. It must be read in conjunction with the Apache License. Section 1 below modifies definitions and terminology in the Apache License and Section 2 below replaces Section 2 of the Apache License. The Appendix replaces the Appendix in the Apache License. You may, at your option, choose to treat any Work released under this license as released under the Apache License (thus ignoring all sections written below entirely). + + 1. Terminology in the Apache License is supplemented or modified as follows: + + "Authorship": any reference to 'authorship' shall be taken to read "authorship or design". + + "Copyright owner": any reference to 'copyright owner' shall be taken to read "Rights owner". + + "Copyright statement": the reference to 'copyright statement' shall be taken to read 'copyright or other statement pertaining to Rights' + + The following new definition shall be added to the Definitions section of the Apache License: + + "Rights" means copyright and any similar right including design right (whether registered or unregistered), rights in semiconductor topographies (mask works) and database rights (but excluding Patents and Trademarks). + + The following definitions shall replace the corresponding definitions in the Apache License: + + "License" shall mean this Solderpad Hardware License version 2.1, being the terms and conditions for use, manufacture, instantiation, adaptation, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the Rights owner or entity authorized by the Rights owner that is granting the License. + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship or design. For the purposes of this License, Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the Work and Derivative Works thereof. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object or material and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works). + + "Source" form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files. + + "Work" shall mean the work of authorship or design, whether in Source or Object form, made available under the License, as indicated by a notice relating to Rights that is included in or attached to the work (an example is provided in the Appendix below). + + 2. Grant of License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist. APPENDIX + +Copyright [yyyy] [name of copyright owner] + +SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 + +Licensed under the Solderpad Hardware License v 2.1 (the "License"); you may not use this file except in compliance with the License, or, at your option, the Apache License version 2.0. + +You may obtain a copy of the License at + +https://solderpad.org/licenses/SHL-2.1/ + +Unless required by applicable law or agreed to in writing, any work distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and limitations under the License. diff --git a/options/license/libselinux-1.0 b/options/license/libselinux-1.0 new file mode 100644 index 000000000000..da106afd98c1 --- /dev/null +++ b/options/license/libselinux-1.0 @@ -0,0 +1,19 @@ +This library (libselinux) is public domain software, i.e. not copyrighted. + +Warranty Exclusion ------------------ + +You agree that this software is a non-commercially developed program that +may contain "bugs" (as that term is used in the industry) and that it may +not function as intended. The software is licensed "as is". NSA makes no, +and hereby expressly disclaims all, warranties, express, implied, statutory, +or otherwise with respect to the software, including noninfringement and the +implied warranties of merchantability and fitness for a particular purpose. + +Limitation of Liability ----------------------- + +In no event will NSA be liable for any damages, including loss of data, lost +profits, cost of cover, or other special, incidental, consequential, direct +or indirect damages arising from the software or the use thereof, however +caused and on any theory of liability. This limitation will apply even if +NSA has been advised of the possibility of such damage. You acknowledge that +this is a reasonable allocation of risk. diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index 4734689fce24..1b103e640924 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -324,7 +324,6 @@ enterred_invalid_owner_name=Името на новия собственик не enterred_invalid_password=Въведената парола е неправилна. user_not_exist=Потребителят не съществува. team_not_exist=Този отбор не съществува. -last_org_owner=Не можете да премахнете последния потребител от екипът 'собственици'. Трябва да има поне един собственик във всеки отбор. cannot_add_org_to_team=Организация не може да бъде добавена като член на екип. invalid_ssh_key=Неуспешно потвърждаване на SSH ключ: %s @@ -446,7 +445,6 @@ key_state_desc=Този ключ е използван през последни show_openid=Показване в профила hide_openid=Скриване от профила ssh_disabled=SSH Изключен - manage_social=Управление на свързани профили в социалните мрежи generate_new_token=Генериране на нов API ключ @@ -514,8 +512,6 @@ template.avatar=Аватар -migrate_type=Тип мигриране -migrate_type_helper=Това хранилище ще бъде огледало migrate_items_wiki=Уики migrate_items_labels=Етикети migrate_items_issues=Проблеми @@ -604,7 +600,6 @@ editor.file_editing_no_longer_exists=Редактираният файл '%s' в editor.file_deleting_no_longer_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_file_is_locked=Файлът '%s' е заключен от %s. @@ -786,6 +781,7 @@ pulls.no_merge_desc=Тази заявка за сливане не може да pulls.no_merge_helper=Включете опции за сливане в настройките на хранилището или обединете заявката за сливане ръчно. pulls.no_merge_wip=Тази заявка за сливане не може да бъде обединена, защото е отбелязана като работа в прогрес. pulls.merge_pull_request=Обедини заявка за сливане +; %[2]s
%[3]s
pulls.status_checks_success=Всички проверявания бяха успешни pulls.update_branch=Осъвременяване на клона @@ -1188,7 +1184,6 @@ config.session_config=Конфигурация на сесии config.session_provider=Доставчик на сесии config.provider_config=Конфигурация на доставчик config.cookie_name=Име на бисквитката -config.enable_set_cookie=Включи използване на бисквитки config.gc_interval_time=GC през интервал config.session_life_time=Период на валидност на сесиите config.https_only=HTTPS само diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 97d7d5c4142b..682a01a13fc5 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -21,10 +21,12 @@ signed_in_as=Přihlášen jako enable_javascript=S JavaScriptem funguje tato webová stránka lépe. toc=Obsah licenses=Licence +return_to_gitea=Vrátit se do Gitea username=Uživatelské jméno email=E-mailová adresa password=Heslo +access_token=Přístupový token re_type=Zadejte znovu heslo captcha=CAPTCHA twofa=Dvoufaktorové ověřování @@ -52,6 +54,8 @@ new_migrate=Nová migrace new_mirror=Nové zrcadlo new_fork=Nové rozštěpení repozitáře new_org=Nová organizace +new_project=Nový projekt +new_project_board=Nová projektová nástěnka manage_org=Spravovat organizace admin_panel=Administrace account_settings=Nastavení účtu @@ -277,7 +281,7 @@ oauth_signup_tab=Zaregistrovat nový účet oauth_signup_title=Přidejte e-mail a heslo (pro obnovení účtu) oauth_signup_submit=Dokončit účet oauth_signin_tab=Propojit s existujícím účtem -oauth_signin_title=Přihlaste se k autorizaci propojeného účtu +oauth_signin_title=Přihlaste se pro ověření propojeného účtu oauth_signin_submit=Propojit účet openid_connect_submit=Připojit openid_connect_title=Připojení k existujícímu účtu @@ -295,6 +299,8 @@ authorize_title=Autorizovat „%s“ pro přístup k vašemu účtu? authorization_failed=Autorizace selhala authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný požadavek. Kontaktujte prosím správce aplikace, kterou jste se pokoušeli autorizovat. sspi_auth_failed=SSPI autentizace selhala +password_pwned=Heslo, které jste zvolili, je na seznamu odcizených hesel, která byla dříve odhalena při narušení veřejných dat. Zkuste to prosím znovu s jiným heslem. +password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned [mail] activate_account=Prosíme, aktivujte si váš účet @@ -349,6 +355,10 @@ lang_select_error=Vyberte jazyk ze seznamu. username_been_taken=Uživatelské jméno je již obsazeno. repo_name_been_taken=Název repozitáře je již použit. +repository_files_already_exist=Soubory pro tento repozitář již existují. Obraťte se na správce systému. +repository_files_already_exist.adopt=Soubory pro tento repozitář již existují a mohou být pouze přijaty. +repository_files_already_exist.delete=Soubory pro tento repozitář již existují. Musíte je odstranit. +repository_files_already_exist.adopt_or_delete=Soubory pro tento repozitář již existují. Přijměte je, nebo je odstraňte. visit_rate_limit=Dosaženo limitu rychlosti dotazů při vzdáleném přístupu. 2fa_auth_required=Vzdálený přístup vyžaduje dvoufaktorové ověřování. org_name_been_taken=Název organizace je již použit. @@ -367,7 +377,7 @@ enterred_invalid_owner_name=Nové jméno vlastníka není správné. enterred_invalid_password=Zadané heslo není správné. user_not_exist=Tento uživatel neexistuje. team_not_exist=Tento tým neexistuje. -last_org_owner=Nemůžete odstranit posledního uživatele z týmu 'vlastníci'. Musí existovat alespoň jeden vlastník v každém týmu. +last_org_owner=Nemůžete odstranit posledního uživatele z týmu „vlastníci“. Musí existovat alespoň jeden vlastník pro organizaci. cannot_add_org_to_team=Organizace nemůže být přidána jako člen týmu. invalid_ssh_key=Nelze ověřit váš SSH klíč: %s @@ -388,6 +398,7 @@ repositories=Repozitáře activity=Veřejná aktivita followers=Sledující starred=Oblíbené repozitáře +projects=Projekty following=Sledovaní follow=Sledovat unfollow=Přestat sledovat @@ -418,6 +429,7 @@ uid=UID u2f=Bezpečnostní klíče public_profile=Veřejný profil +biography_placeholder=Řekněte nám něco o sobě profile_desc=Vaše e-mailová adresa bude použita pro oznámení a další operace. password_username_disabled=Externí uživatelé nemohou měnit svoje uživatelské jméno. Kontaktujte prosím svého administrátora pro více detailů. full_name=Celé jméno @@ -496,8 +508,9 @@ ssh_helper=Potřebujete pomoct? Podívejte se do příručky Gi gpg_helper=Potřebujete pomoct? Podívejte se do příručky GitHubu o GPG. add_new_key=Přidat klíč SSH add_new_gpg_key=Přidat GPG klíč +key_content_ssh_placeholder=Začíná s „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, nebo „ecdsa-sha2-nistp521“ +key_content_gpg_placeholder=Začíná s „-----BEGIN PGP PUBLIC KEY BLOCK-----“ ssh_key_been_used=Tento SSH klíč byl na server již přidán. -ssh_key_name_used=SSH klíč se stejným jménem je již přidán k vašemu účtu. gpg_key_id_used=Veřejný GPG klíč se stejným ID již existuje. gpg_no_key_email_found=Tento GPG klíč není použitelný s žádnou e-mailovou adresou propojenou s vaším účtem. subkeys=Podklíče @@ -525,7 +538,6 @@ token_state_desc=Tato poukázka byla použita během posledních 7 dní show_openid=Zobrazit na profilu hide_openid=Odstranit z profilu ssh_disabled=SSH zakázáno - manage_social=Správa propojených účtů sociálních sítí social_desc=Tyto účty sociálních síti jsou propojeny s vaším Gitea účtem. Ujistěte se, že je všechny znáte, protože mohou být použity k přihlášení do vašeho Gitea účtu. unbind=Odpojit @@ -671,6 +683,15 @@ pick_reaction=Vyberte svoji reakci reactions_more=a %d dalších unit_disabled=Správce webu zakázal tuto sekci repozitáře. language_other=Jiný +adopt_search=Zadejte uživatelské jméno pro hledání nepřijatých repozitářů... (ponechte prázdné pro nalezení všech) +adopt_preexisting_label=Přijmout soubory +adopt_preexisting=Přijmout již existující soubory +adopt_preexisting_content=Vytvořit repozitář z %s +adopt_preexisting_success=Přijaty soubory a vytvořen repozitář z %s +delete_preexisting_label=Smazat +delete_preexisting=Odstranit již existující soubory +delete_preexisting_content=Odstranit soubory v %s +delete_preexisting_success=Smazány nepřijaté soubory v %s desc.private=Soukromý desc.public=Veřejný @@ -700,15 +721,17 @@ form.name_reserved=Jméno repozitáře „%s“ je rezervované. form.name_pattern_not_allowed=Vzor „%s“ není povolený v názvu repozitáře. need_auth=Ověření klonování -migrate_type=Typ migrace -migrate_type_helper=Tento repozitář bude zrcadlem -migrate_type_helper_disabled=Administrátor vašeho webu zakázal nová zrcadla. +migrate_options=Možnosti migrace +migrate_service=Migrační služba +migrate_options_mirror_helper=Tento repozitář bude zrcadlem +migrate_options_mirror_disabled=Administrátor vašeho webu zakázal nová zrcadla. migrate_items=Položky pro migrování migrate_items_wiki=Wiki migrate_items_milestones=Milníky migrate_items_labels=Štítky migrate_items_issues=Úkoly migrate_items_pullrequests=Požadavky na natažení +migrate_items_merge_requests=Sloučit požadavky migrate_items_releases=Vydání migrate_repo=Migrovat repozitář migrate.clone_address=Migrovat / klonovat z URL @@ -718,17 +741,23 @@ migrate.permission_denied=Není dovoleno importovat místní repozitáře. migrate.invalid_local_path=Místní cesta je neplatná, buď neexistuje nebo není adresářem. migrate.failed=Přenesení selhalo: %v migrate.lfs_mirror_unsupported=Zrcadlení LFS objektů není podporováno - použijte místo toho „git lfs fetch --all“ a „git lfs push --all“. -migrate.migrate_items_options=Při přechodu z githubu, zadejte uživatelské jméno a zobrazí se možnosti migrace. +migrate.migrate_items_options=Pro migraci dalších položek je vyžadován přístupový token migrated_from=Migrováno z %[2]s migrated_from_fake=Migrováno z %[1]s +migrate.migrate=Migrovat z %s migrate.migrating=Probíhá migrace z %s ... migrate.migrating_failed=Migrace z %s se nezdařila. +migrate.github.description=Migrace dat z Github.com nebo Github Enterprise. +migrate.git.description=Migrace nebo zrcadlení git dat ze služeb Git +migrate.gitlab.description=Migrace dat z GitLab.com nebo vlastního gitlab serveru. mirror_from=zrcadlo forked_from=rozštěpen z generated_from=generováno z fork_from_self=Nemůžete rozštěpit váš vlastní repozitář. -fork_guest_user=Přihlaste se, aby bylo možné rozštěpit tento repozitář. +fork_guest_user=Přihlaste se pro rozštěpení tohoto repozitáře. +watch_guest_user=Pro sledování tohoto repozitáře se přihlaste. +star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste. copy_link=Kopie copy_link_success=Odkaz byl zkopírován copy_link_error=Pro zkopírování použijte ⌘-C nebo Ctrl-C @@ -751,11 +780,13 @@ code=Zdrojový kód code.desc=Přístup ke zdrojovým kódům, souborům, revizím a větvím. branch=Větev tree=Strom +clear_ref=`Vymazat aktuální referenci" filter_branch_and_tag=Filtr pro větev nebo značku branches=Větve tags=Značky issues=Úkoly pulls=Požadavky na natažení +project_board=Projekty labels=Štítky org_labels_desc=Štítky na úrovni organizace, které mohou být použity se všemi repozitáři v rámci této organizace org_labels_desc_manage=spravovat @@ -774,6 +805,8 @@ audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5 stored_lfs=Uloženo pomocí Git LFS symbolic_link=Symbolický odkaz commit_graph=Graf revizí +commit_graph.monochrome=Černobílé +commit_graph.color=Barevné blame=Blame normal_view=Normální zobrazení line=řádek @@ -823,9 +856,7 @@ editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři. editor.commit_empty_file_header=Potvrďte prázdný soubor editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat? editor.no_changes_to_show=Žádné změny k zobrazení. -editor.fail_to_update_file=Vytvoření nebo změna souboru „%s“ skončila chybou: %v editor.push_rejected_no_message=Změna byla serverem zamítnuta bez zprávy. Prosím, zkontrolujte háčky Gitu. -editor.push_rejected=Změna byla serverem odmítnuta s následující zprávou:
%s
Prosím, zkontrolujte háčky Gitu. editor.add_subdir=Přidat adresář… editor.unable_to_upload_files=Nepodařilo se nahrát soubor „%s“. Chyba: %v editor.upload_file_is_locked=Soubor '%s' uzamkl %s. @@ -855,10 +886,40 @@ commits.gpg_key_id=ID GPG klíče ext_issues=Ext. úkoly ext_issues.desc=Odkaz na externí systém úkolů. +projects=Projekty +projects.desc=Spravovat úkoly a požadavky na natažení na projektových nástěnkách. +projects.create=Vytvořit projekt +projects.title=Název +projects.new=Nový projekt +projects.new_subheader=Koordinujte, sledujte a aktualizujte svou práci na jednom místě, aby projekty zůstaly transparentní a v plánu. +projects.create_success=Projekt '%s' byl vytvořen. +projects.deletion=Odstranit projekt +projects.deletion_desc=Odstranění projektu jej odstraní ze všech souvisejících úkolů. Pokračovat? +projects.deletion_success=Projekt byl odstraněn. +projects.edit=Upravit projekty +projects.edit_subheader=Projekty organizují úkoly a sledují pokrok. +projects.modify=Aktualizovat projekt +projects.edit_success=Projekt '%s' byl aktualizován. +projects.type.none=Žádný +projects.type.basic_kanban=Základní Kanban +projects.type.bug_triage=Třídění chyb +projects.template.desc=Šablona projektu +projects.template.desc_helper=Vyberte šablonu projektu pro začátek +projects.type.uncategorized=Nezařazené +projects.board.edit=Upravit nástěnku +projects.board.edit_title=Název nástěnky +projects.board.new_title=Název nové nástěnky +projects.board.new_submit=Odeslat +projects.board.new=Nová nástěnka +projects.board.delete=Smazat nástěnku +projects.board.deletion_desc=Smazáním projektové nástěnky přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat? +projects.open=Otevřít +projects.close=Zavřít issues.desc=Organizování hlášení chyb, úkolů a milníků. issues.filter_assignees=Filtrovat zpracovatele issues.filter_milestones=Filtrovat milník +issues.filter_projects=Filtrovat projekt issues.filter_labels=Filtrovat štítky issues.filter_reviewers=Filtrovat posuzovatele issues.new=Nový úkol @@ -867,6 +928,12 @@ issues.new.labels=Štítky issues.new.add_labels_title=Použít štítky issues.new.no_label=Bez štítku issues.new.clear_labels=Zrušit štítky +issues.new.projects=Projekty +issues.new.add_project_title=Nastavit projekt +issues.new.clear_projects=Vymazat projekty +issues.new.no_projects=Žádný projekt +issues.new.open_projects=Otevřít projekty +issues.new.closed_projects=Uzavřené projekty issues.new.no_items=Žádné položky issues.new.milestone=Milník issues.new.add_milestone_title=Nastavit milník @@ -880,6 +947,9 @@ issues.new.clear_assignees=Smazat zpracovatele issues.new.no_assignees=Bez zpracovatelů issues.new.no_reviewers=Žádní posuzovatelé issues.new.add_reviewer_title=Požádat o posouzení +issues.choose.get_started=Začínáme +issues.choose.blank=Výchozí +issues.choose.blank_about=Vytvořit úkol z výchozí šablony. issues.no_ref=Není určena žádná větev/značka issues.create=Vytvořit úkol issues.new_label=Nový štítek @@ -894,9 +964,13 @@ issues.label_templates.fail_to_load_file=Nepodařilo se nahrát soubor šablony issues.add_label_at=přidal(a) štítek
%s
%s issues.remove_label_at=odstranil(a) štítek
%s
%s issues.add_milestone_at=`přidal(a) toto do milníku %s %s` +issues.add_project_at=`přidal(a) toto do projektu %s %s` issues.change_milestone_at=`upravil(a) milník z %s na %s %s` +issues.change_project_at=`upravil(a) projekt z %s na %s %s` issues.remove_milestone_at=`odstranil(a) toto z milníku %s %s` +issues.remove_project_at=`odstranil(a) toto z projektu %s %s` issues.deleted_milestone=`(odstraněno)` +issues.deleted_project=`(odstraněno)` issues.self_assign_at=`přiřadil(a) sobě toto %s` issues.add_assignee_at=`byl přiřazen %s %s` issues.remove_assignee_at=`byl odstraněn z přiřazení %s %s` @@ -975,6 +1049,7 @@ issues.poster=Autor issues.collaborator=Spolupracovník issues.owner=Vlastník issues.re_request_review=Znovu požádat o posouzení +issues.is_stale=Od tohoto posouzení došlo ke změnám v tomto požadavku na natažení issues.remove_request_review=Odstranit žádost o posouzení issues.remove_request_review_block=Nelze odstranit žádost o posouzení issues.sign_in_require_desc=Přihlaste se pro zapojení do konverzace. @@ -1165,11 +1240,9 @@ pulls.rebase_merge_commit_pull_request=Rebase a sloučit (--no-ff) pulls.squash_merge_pull_request=Squash a sloučit pulls.require_signed_wont_sign=Větev vyžaduje podepsané revize, ale toto sloučení nebude podepsáno pulls.invalid_merge_option=Nemůžete použít tuto možnost sloučení pro tento požadavek na natažení. -pulls.merge_conflict=Sloučení selhalo: Došlo ke konfliktu při sloučení: %[1]s
%[2]s
Tip: Zkuste jinou strategii -pulls.rebase_conflict=Sloučení selhalo: Došlo ke konfliktu při rebase revize: %[1]s
%[2]s
%[3]s
Tip: Zkuste jinou strategii +; %[2]s
%[3]s
pulls.unrelated_histories=Sloučení selhalo: Základní revize nesdílí společnou historii. Tip: Zkuste jinou strategii pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu. -pulls.push_rejected=Sloučení se nezdařilo: Nahrání bylo odmítnuto s následující zprávou:
%s
Zkontrolujte háčky gitu pro tento repozitář pulls.push_rejected_no_message=Sloučení se nezdařilo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva.
Zkontrolujte háčky gitu pro tento repozitář pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající požadavek na natažení (#%d) s identickými vlastnostmi.` pulls.status_checking=Některé kontroly jsou nedořešeny @@ -1177,6 +1250,8 @@ pulls.status_checks_success=Všechny kontroly byly úspěšné pulls.status_checks_warning=Některé kontroly nahlásily varování pulls.status_checks_failure=Některé kontroly se nezdařily pulls.status_checks_error=Některé kontroly nahlásily chyby +pulls.status_checks_requested=Požadováno +pulls.status_checks_details=Podrobnosti pulls.update_branch=Aktualizovat větev pulls.update_branch_success=Aktualizace větve byla úspěšná pulls.update_not_allowed=Nemáte oprávnění aktualizovat větev @@ -1188,6 +1263,7 @@ milestones.new=Nový milník milestones.open_tab=%d otevřených milestones.close_tab=%d zavřených milestones.closed=Zavřen dne %s +milestones.update_ago=Aktualizováno před %s milestones.no_due_date=Bez lhůty dokončení milestones.open=Otevřít milestones.close=Zavřít @@ -1227,6 +1303,7 @@ signing.wont_sign.basesigned=Sloučení nebude podepsáno, protože základní r signing.wont_sign.headsigned=Sloučení nebude podepsáno, protože základní revize není podepsána signing.wont_sign.commitssigned=Sloučení nebude podepsáno, protože všechny přidružené revize nejsou podepsány signing.wont_sign.approved=Sloučení nebude podepsáno, protože požadavek na natažení není schválen +signing.wont_sign.not_signed_in=Nejste přihlášeni ext_wiki=Ext. Wiki ext_wiki.desc=Odkaz do externí Wiki. @@ -1372,6 +1449,7 @@ settings.pulls.allow_merge_commits=Povolit slučování revizí settings.pulls.allow_rebase_merge=Povolit rebase pro slučovací revize settings.pulls.allow_rebase_merge_commit=Povolit rebase s vyžádanou slučovací revizí (--no-ff) settings.pulls.allow_squash_commits=Povolit squash pro slučovací revize +settings.projects_desc=Povolit projekty v repozitáři settings.admin_settings=Nastavení správce settings.admin_enable_health_check=Povolit kontrolu stavu repozitáře (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Zavřít úkol pomocí revize v jiné než výchozí větvi @@ -1382,11 +1460,28 @@ settings.convert_desc=Můžete převést toto zrcadlo na běžný repozitář. T settings.convert_notices_1=Tato operace převede toto zrcadlo na běžný repozitář a tato změna nemůže být vrácena. settings.convert_confirm=Převést repozitář settings.convert_succeed=Zrcadlo bylo převedeno na běžný repozitář. +settings.convert_fork=Převést na běžný repozitář +settings.convert_fork_desc=Můžete převést toto rozštěpení na běžný repozitář. Tuto akci nelze vrátit zpět. +settings.convert_fork_notices_1=Tato operace převede rozštěpení na běžný repozitář a nelze ji vrátit zpět. +settings.convert_fork_confirm=Převést repozitář +settings.convert_fork_succeed=Rozštěpení bylo překonvertován na běžný repozitář. settings.transfer=Předat vlastnictví settings.transfer_desc=Předat tento repozitář uživateli nebo organizaci, ve které máte administrátorská práva. settings.transfer_notices_1=- Ztratíte přístup k repozitáři, pokud jej převedete na uživatele. settings.transfer_notices_2=- Zůstane vám přístup k repozitáři, pokud jej převedete na organizaci kterou (spolu)vlastníte. settings.transfer_form_title=Zadejte jméno repozitáře pro potvrzení: +settings.signing_settings=Nastavení ověřování podpisu +settings.trust_model=Model důvěry podpisu +settings.trust_model.default=Výchozí model důvěry +settings.trust_model.default.desc=Použít výchozí model důvěry pro tuto instalaci. +settings.trust_model.collaborator=Spolupracovník +settings.trust_model.collaborator.long=Spolupracovník: Důvěřovat podpisům spolupracovníků +settings.trust_model.collaborator.desc=Platné podpisy spolupracovníků tohoto repozitáře budou označeny jako „důvěryhodné“ - (ať se shodují s autorem, či nikoli). V opačném případě budou platné podpisy označeny jako „nedůvěryhodné“, pokud se podpis shoduje s autorem a „neodpovídající“, pokud ne. +settings.trust_model.committer=Tvůrce revize +settings.trust_model.committer.long=Tvůrce revize: Důvěřovat podpisům, které odpovídají autorům (což odpovídá GitHub a přinutí Giteu nastavit jako tvůrce pro Giteou podepsané revize) +settings.trust_model.committer.desc=Platné podpisy budou označeny pouze jako „důvěryhodné“, pokud se shodují s autorem, jinak budou označeny jako „neodpovídající“. To přinutí Giteu, aby byla autorem podepsaných revizí se skutečným autorem označeným jako Co-Authored-By: a Co-Committed-By: na konci revize. Výchozí klíč Gitea musí odpovídat uživateli v databázi. +settings.trust_model.collaboratorcommitter=Spolupracovník+Tvůrce revize +settings.trust_model.collaboratorcommitter.long=Spolupracovník+Tvůrce revize: Důvěřovat podpisům od spolupracovníků, které odpovídají tvůrci revize settings.wiki_delete=Odstranit data Wiki settings.wiki_delete_desc=Smazání Wiki dat repozitáře je trvalé a nemůže být vráceno zpět. settings.wiki_delete_notices_1=- Natrvalo odstraní a zakáže wiki repozitáře pro %s. @@ -1766,7 +1861,9 @@ settings.repoadminchangeteam=Správce úložišť může týmům přidávat a od settings.visibility=Viditelnost settings.visibility.public=Veřejná settings.visibility.limited=Omezená (viditelné jen pro přihlášené uživatele) +settings.visibility.limited_shortname=Omezený settings.visibility.private=Soukromá (viditelné jen členům organizace) +settings.visibility.private_shortname=Soukromý settings.update_settings=Upravit nastavení settings.update_setting_success=Nastavení organizace bylo upraveno. @@ -1862,6 +1959,12 @@ dashboard.operation_switch=Přepnout dashboard.operation_run=Spustit dashboard.clean_unbind_oauth=Vyčistit neprovázané OAuth spojení dashboard.clean_unbind_oauth_success=Všechna neprovázaná OAuth spojení byla smazána. +dashboard.task.started=Zahájen úkol: %[1]s +dashboard.task.process=Úkol: %[1]s +dashboard.task.cancelled=Úkol: %[1]s zrušeno: %[3]s +dashboard.task.error=Chyba v úkolu: %[1]s: %[3]s +dashboard.task.finished=Úkol: %[1]s začalo %[2]s skončilo +dashboard.task.unknown=Neznámý úkol: %[1]s dashboard.cron.started=Začala naplánovaná úloha: %[1]s dashboard.cron.process=Naplánovaná úloha: %[1]s dashboard.cron.cancelled=Naplánovaná úloha: %s zrušena: %[3]s @@ -1923,6 +2026,7 @@ users.full_name=Celé jméno users.activated=Aktivován users.admin=Správce users.restricted=Omezený +users.2fa=2FA users.repos=Repozitáře users.created=Vytvořen users.last_login=Poslední přihlášení @@ -1943,7 +2047,6 @@ users.prohibit_login=Zakázat přihlášení users.is_admin=Je správce users.is_restricted=Je omezený users.allow_git_hook=Může vytvářet háčky Gitu -users.allow_git_hook_tooltip=Háčky Gitu se spustí jako uživatel OS provozující Gitea a budou mít stejný přístup k hostiteli users.allow_import_local=Může importovat lokální repozitáře users.allow_create_organization=Může vytvářet organizace users.update_profile=Aktualizovat uživatelský účet @@ -1972,6 +2075,8 @@ orgs.members=Členové orgs.new_orga=Nová organizace repos.repo_manage_panel=Správa repozitáře +repos.unadopted=Nepřijaté repozitáře +repos.unadopted.no_more=Nebyly nalezeny žádné další nepřijaté repositáře repos.owner=Vlastník repos.name=Název repos.private=Soukromý @@ -2021,6 +2126,11 @@ auths.filter=Uživatelský filtr auths.admin_filter=Správcovský filtr auths.restricted_filter=Filtr omezení auths.restricted_filter_helper=Ponechte prázdné, pokud nechcete nastavit žádné uživatele jako omezené. Použijte hvězdičku („*“) pro nastavení všech uživatelů, kteří neodpovídají filtru administrátora, jako omezené. +auths.verify_group_membership=Ověřit členství ve skupině v LDAP +auths.group_search_base=Základní DN pro hledání skupin +auths.valid_groups_filter=Platný filtr skupin +auths.group_attribute_list_users=Skupinový atribut obsahující seznam uživatelů +auths.user_attribute_in_group=Atribut uživatele ve skupině auths.ms_ad_sa=Atributy vyhledávání MS AD auths.smtp_auth=Typ ověření SMTP auths.smtphost=Server SMTP @@ -2161,6 +2271,7 @@ config.mailer_use_sendmail=Použít Sendmail config.mailer_sendmail_path=Cesta k Sendmail config.mailer_sendmail_args=Dodatečné argumenty pro Sendmail config.mailer_sendmail_timeout=Časový limit Sandmail +config.test_email_placeholder=E-mail (např.: test@example.com) config.send_test_mail=Odeslat zkušební e-mail config.test_mail_failed=Odeslání testovacího e-mailu na „%s“ selhalo: %v config.test_mail_sent=Zkušební e-mail byl odeslán na „%s“. @@ -2178,7 +2289,6 @@ config.session_config=Nastavení relace config.session_provider=Poskytovatel relace config.provider_config=Nastavení poskytovatele config.cookie_name=Název souboru cookie -config.enable_set_cookie=Povolit nastavení cookie config.gc_interval_time=Čas intervalu GC config.session_life_time=Doba trvání relace config.https_only=Pouze protokol HTTPS @@ -2320,6 +2430,7 @@ mirror_sync_create=synchronizoval(a) novou referenci %[2]s%[2]s v %[3]s ze zrcadla approve_pull_request=`schválil(a) %s#%[2]s` reject_pull_request=`navrhl(a) změny pro %s#%[2]s` +publish_release=`vydána značka "%[4]s" v %[3]s` [tool] ago=před %s diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 99d1f5731e70..8e39b11991ee 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -21,10 +21,12 @@ signed_in_as=Angemeldet als enable_javascript=Diese Webseite funktioniert besser mit JavaScript. toc=Inhaltsverzeichnis licenses=Lizenzen +return_to_gitea=Zurück zu Gitea username=Benutzername email=E-Mail-Adresse password=Passwort +access_token=Zugangs-Token re_type=Passwort erneut eingeben captcha=CAPTCHA twofa=Zwei-Faktor-Authentifizierung @@ -297,6 +299,8 @@ authorize_title="%s" den Zugriff auf deinen Account gestatten? authorization_failed=Autorisierung fehlgeschlagen authorization_failed_desc=Die Autorisierung ist fehlgeschlagen, da wir eine ungültige Anfrage festgestellt haben. Bitte kontaktiere den Betreiber der Anwendung, die du gerade autorisieren wolltest. sspi_auth_failed=SSPI Authentifizierung fehlgeschlagen +password_pwned=Das von dir gewählte Passwort ist auf einer Liste von gestohlenen Passwörtern die zuvor bei öffentlichen Datenschutzverletzungen aufgedeckt wurden. Bitte versuche es erneut mit einem anderen Passwort. +password_pwned_err=Anfrage an HaveIBeenPwned konnte nicht abgeschlossen werden [mail] activate_account=Bitte aktiviere dein Konto @@ -351,6 +355,10 @@ lang_select_error=Wähle eine Sprache aus der Liste aus. username_been_taken=Der Benutzername ist bereits vergeben. repo_name_been_taken=Der Repository-Name wird schon verwendet. +repository_files_already_exist=Dateien für dieses Repository sind bereits vorhanden. Kontaktiere den Systemadministrator. +repository_files_already_exist.adopt=Dateien für dieses Repository existieren bereits und können nur übernommen werden. +repository_files_already_exist.delete=Dateien für dieses Repository sind bereits vorhanden. Du must sie löschen. +repository_files_already_exist.adopt_or_delete=Dateien für dieses Repository existieren bereits. Du musst sie entweder übernehmen oder löschen. visit_rate_limit=Das Rate-Limit bei der Gegenseite wurde erreicht. 2fa_auth_required=Die Gegenseite benötigt Zweifaktorauthentifikation. org_name_been_taken=Der Organisationsname ist bereits vergeben. @@ -369,7 +377,7 @@ enterred_invalid_owner_name=Der Name des neuen Besitzers ist ungültig. enterred_invalid_password=Das eingegebene Passwort ist falsch. user_not_exist=Dieser Benutzer ist nicht vorhanden. team_not_exist=Dieses Team existiert nicht. -last_org_owner=Du kannst den letzten Benutzer nicht aus dem „Besitzer“-Team entfernen. Es muss mindestens einen Besitzer in einer Organisation geben. +last_org_owner=Du kannst den letzten Benutzer nicht aus dem 'Besitzer'-Team entfernen. Es muss mindestens einen Besitzer in einer Organisation geben. cannot_add_org_to_team=Eine Organisation kann nicht als Teammitglied hinzugefügt werden. invalid_ssh_key=Dein SSH-Key kann nicht überprüft werden: %s @@ -421,6 +429,7 @@ uid=Uid u2f=Hardware-Sicherheitsschlüssel public_profile=Öffentliches Profil +biography_placeholder=Erzähle uns noch ein bisschen was über dich profile_desc=Deine E-Mail-Adresse wird für Benachrichtigungen und anderes verwendet. password_username_disabled=Benutzer, die nicht von Gitea verwaltet werden können ihren Benutzernamen nicht ändern. Bitte kontaktiere deinen Administrator für mehr Details. full_name=Vollständiger Name @@ -530,7 +539,6 @@ token_state_desc=Dieser Token wurde in den letzten 7 Tagen benutzt show_openid=Im Profil anzeigen hide_openid=Nicht im Profil anzeigen ssh_disabled=SSH ist deaktiviert - manage_social=Verknüpfte soziale Konten verwalten social_desc=Diese Accounts sind mit deinem Gitea-Konto verbunden. Schau dir alle Accounts an, um sicherzustellen dass du alle legitimiert hast, da man sich darüber in deinem Gitea-Konto anmelden kann. unbind=Trennen @@ -676,6 +684,15 @@ pick_reaction=Wähle eine Reaktion reactions_more=und %d weitere unit_disabled=Der Administrator hat diesen Repository-Bereich deaktiviert. language_other=Andere +adopt_search=Geben einen Benutzernamen ein, um nach nicht angenommenen Repositories zu suchen... (leer lassen um alle zu finden) +adopt_preexisting_label=Dateien übernehmen +adopt_preexisting=Vorhandene Dateien übernehmen +adopt_preexisting_content=Repository aus %s erstellen +adopt_preexisting_success=Dateien übernommen und Repository erstellt von %s +delete_preexisting_label=Löschen +delete_preexisting=Vorhandene Dateien löschen +delete_preexisting_content=Dateien in %s löschen +delete_preexisting_success=Nicht übernommene Dateien in %s gelöscht desc.private=Privat desc.public=Öffentlich @@ -705,15 +722,17 @@ form.name_reserved=Der Repository-Name „%s“ ist reserviert. form.name_pattern_not_allowed='%s' ist nicht erlaubt für Repository-Namen. need_auth=Authentifizierung zum Klonen benötigt -migrate_type=Migrationstyp -migrate_type_helper=Dieses Repository wird ein Mirror sein -migrate_type_helper_disabled=Der Administrator hat neue Mirrors deaktiviert. +migrate_options=Migrationsoptionen +migrate_service=Migrationsdienst +migrate_options_mirror_helper=Dieses Repository wird ein Mirror sein +migrate_options_mirror_disabled=Dein Administrator hat neue Mirrors deaktiviert. migrate_items=Migrationselemente migrate_items_wiki=Wiki migrate_items_milestones=Meilensteine migrate_items_labels=Labels migrate_items_issues=Issues migrate_items_pullrequests=Pull-Requests +migrate_items_merge_requests=Merge-Requests migrate_items_releases=Releases migrate_repo=Repository migrieren migrate.clone_address=Migrations- / Klon-URL @@ -723,17 +742,24 @@ migrate.permission_denied=Du hast keine Berechtigung zum Importieren lokaler Rep migrate.invalid_local_path=Der lokale Pfad ist ungültig, existiert nicht oder ist kein Ordner. migrate.failed=Fehler bei der Migration: %v migrate.lfs_mirror_unsupported=Spiegeln von LFS-Objekten wird nicht unterstützt - nutze stattdessen 'git lfs fetch --all' und 'git lfs push --all'. -migrate.migrate_items_options=Wenn du von GitHub migrierst und einen Benutzernamen eingegeben hast, werden die Migrationsoptionen angezeigt. +migrate.migrate_items_options=Zugangs-Token wird benötigt, um zusätzliche Elemente zu migrieren migrated_from=Migriert von %[2]s migrated_from_fake=Migriert von %[1]s +migrate.migrate=Migrieren von %s migrate.migrating=Migriere von %s ... migrate.migrating_failed=Migrieren von %s fehlgeschlagen. +migrate.github.description=Migriere Daten von Github.com oder Github Enterprise. +migrate.git.description=Migriere oder spiegele git-Daten von Git-Services +migrate.gitlab.description=Migriere Daten von GitLab.com oder einem selbst gehostetem gitlab Server. +migrate.gitea.description=Migriere Daten von Gitea.com oder einem selbst gehostetem Gitea Server. mirror_from=Mirror von forked_from=geforkt von generated_from=erzeugt von fork_from_self=Du kannst kein Repository forken, das dir bereits gehört. fork_guest_user=Bitte melde dich an, um dieses Repository zu forken. +watch_guest_user=Melde dich an, um dieses Repository zu beobachten. +star_guest_user=Bitte melde dich an, um dieses Repository zu favorisieren. copy_link=Kopieren copy_link_success=Der Link wurde kopiert copy_link_error=Verwende ⌘-C oder Strg-C zum Kopieren @@ -756,6 +782,7 @@ code=Code code.desc=Zugriff auf Quellcode, Dateien, Commits und Branches. branch=Branch tree=Struktur +clear_ref=`Aktuelle Referenz löschen` filter_branch_and_tag=Branch oder Tag filtern branches=Branches tags=Tags @@ -829,11 +856,9 @@ editor.file_deleting_no_longer_exists=Die Datei '%s' existiert in diesem Reposit editor.file_changed_while_editing=Der Inhalt der Datei hat sich seit dem Beginn der Bearbeitung geändert. Hier klicken, um die Änderungen anzusehen, oder Änderungen erneut comitten, um sie zu überschreiben. editor.file_already_exists=Eine Datei mit dem Namen „%s“ ist bereits in diesem Repository vorhanden. editor.commit_empty_file_header=Leere Datei committen -editor.commit_empty_file_text=Die Datei, die du gerade commitest ist leer! Fortfahren? +editor.commit_empty_file_text=Die Datei, die du commiten willst, ist leer. Fortfahren? editor.no_changes_to_show=Keine Änderungen vorhanden. -editor.fail_to_update_file=Fehler beim Ändern/Erstellen der Datei „%s“. Fehler: %v editor.push_rejected_no_message=Die Änderung wurde vom Server ohne Nachricht abgelehnt. Bitte überprüfe die githooks. -editor.push_rejected=Die Änderung wurde vom Server mit der folgenden Nachricht abgelehnt:
%s
Bitte überprüfe die githooks. editor.add_subdir=Verzeichnis erstellen… editor.unable_to_upload_files=Fehler beim Hochladen der Dateien nach „%s“. Fehler: %v editor.upload_file_is_locked=Datei „%s” ist durch %s gesperrt. @@ -924,6 +949,9 @@ issues.new.clear_assignees=Zuständige entfernen issues.new.no_assignees=Niemand zuständig issues.new.no_reviewers=Keine Reviewer issues.new.add_reviewer_title=Überprüfung anfordern +issues.choose.get_started=Los geht's +issues.choose.blank=Standard +issues.choose.blank_about=Erstelle einen Issue aus dem Standardtemplate. issues.no_ref=Keine Branch/Tag angegeben issues.create=Issue erstellen issues.new_label=Neues Label @@ -1014,7 +1042,6 @@ issues.reopened_at=`hat diesen Issue %[2]s wiede issues.commit_ref_at=`hat dieses Issue %[2]s aus einem Commit referenziert` issues.ref_issue_from=`verweist auf dieses Issue %[4]s %[2]s` issues.ref_pull_from=`verweist auf diesen Pull Request %[4]s %[2]s` -issues.ref_closing_from=`hat auf einen Pull Request %[4]s verwiesen, welcher das Issue %[2]s schließen wird` issues.ref_reopening_from=`hat auf einen Pull Request %[4]s verwiesen, welcher das Issue %[2]s erneut öffnen wird` issues.ref_closed_from=`hat dieses Issue %[4]s geschlossen %[2]s` issues.ref_reopened_from=`hat dieses Issue %[4]s %[2]s wieder geöffnet` @@ -1023,6 +1050,7 @@ issues.poster=Ersteller issues.collaborator=Mitarbeiter issues.owner=Besitzer issues.re_request_review=Review erneut anfordern +issues.is_stale=Seit diesem Review gab es Änderungen an diesem PR issues.remove_request_review=Review-Anfrage entfernen issues.remove_request_review_block=Review-Anfrage kann nicht entfernt werden issues.sign_in_require_desc=Anmelden, um an der Diskussion teilzunehmen. @@ -1190,6 +1218,8 @@ pulls.required_status_check_administrator=Als Administrator kannst du diesen Pul 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.blocked_by_outdated_branch=Dieser Pull Request ist blockiert, da er veraltet ist. +pulls.blocked_by_changed_protected_files_1=Diese Pull Request ist blockiert, weil er eine geschützte Datei ändert: +pulls.blocked_by_changed_protected_files_n=Diese Pull Request ist blockiert, weil er geschützte Dateien ändert: 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. @@ -1213,11 +1243,9 @@ pulls.rebase_merge_commit_pull_request=Rebasen und Mergen (--no-ff) pulls.squash_merge_pull_request=Zusammenfassen und Mergen pulls.require_signed_wont_sign=Die Branch erfordert einen signierten Commit, aber dieser Merge wird nicht signiert pulls.invalid_merge_option=Du kannst diese Mergeoption auf diesen Pull-Request nicht anwenden. -pulls.merge_conflict=Merge fehlgeschlagen: Beim Zusammenführen gab es einen Konflikt: %[1]s
%[2]s
Hinweis: Probiere eine andere Strategie. -pulls.rebase_conflict=Zusammenführen fehlgeschlagen: Es gab einen Konflikt beim rebasing des Commit: %[1]s
%[2]s
%[3]s
Hinweis:Versuche eine andere Strategie +; %[2]s
%[3]s
pulls.unrelated_histories=Zusammenführung fehlgeschlagen: Der Head der Zusammenführung und die Basis haben keinen gemeinsamen Verlauf. Hinweis: Versuche eine andere Strategie pulls.merge_out_of_date=Zusammenführung fehlgeschlagen: Während der Zusammenführung wurde die Basis aktualisiert. Hinweis: Versuche es erneut. -pulls.push_rejected=Zusammenführen fehlgeschlagen: Der Push wurde mit der folgenden Nachricht abgelehnt:
%s
Überprüfe die Githooks für dieses Repository pulls.push_rejected_no_message=Merge fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung.
Überprüfe die Githooks für dieses Repository pulls.open_unmerged_pull_exists=`Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist.` pulls.status_checking=Einige Prüfungen sind noch ausstehend @@ -1225,6 +1253,8 @@ pulls.status_checks_success=Alle Prüfungen waren erfolgreich pulls.status_checks_warning=Einige Prüfungen meldeten Warnungen pulls.status_checks_failure=Einige Prüfungen sind fehlgeschlagen pulls.status_checks_error=Einige Checks meldeten Fehler +pulls.status_checks_requested=Erforderlich +pulls.status_checks_details=Details pulls.update_branch=Branch aktualisieren pulls.update_branch_success=Branch-Aktualisierung erfolgreich pulls.update_not_allowed=Du hast keine Berechtigung, die Branch zu Updaten @@ -1236,6 +1266,7 @@ milestones.new=Neuer Meilenstein milestones.open_tab=%d offen milestones.close_tab=%d geschlossen milestones.closed=Geschlossen %s +milestones.update_ago=Vor %s aktualisiert milestones.no_due_date=Kein Fälligkeitsdatum milestones.open=Öffnen milestones.close=Schließen @@ -1275,6 +1306,7 @@ signing.wont_sign.basesigned=Der Merge Commit wird nicht signiert werden, da der signing.wont_sign.headsigned=Der Merge Commit wird nicht signiert werden, da der Head-Commit nicht signiert ist signing.wont_sign.commitssigned=Der Merge Commit wird nicht signiert werden, da alle zugehörigen Commits nicht signiert sind signing.wont_sign.approved=Der Merge Commit wird nicht signiert werden, da der Pull Request nicht genehmigt wurde +signing.wont_sign.not_signed_in=Du bist nicht eingeloggt ext_wiki=Externes Wiki ext_wiki.desc=Verweis auf externes Wiki. @@ -1432,7 +1464,7 @@ settings.convert_notices_1=Dieser Vorgang wandelt das Mirror-Repository in ein n settings.convert_confirm=Repository umwandeln settings.convert_succeed=Das Mirror-Repository wurde erfolgreich in ein normales Repository umgewandelt. settings.convert_fork=In ein normales Repository umwandeln -settings.convert_fork_desc=Sie können diesen Fork in ein normales Repository umwandeln. Dies kann nicht rückgängig gemacht werden. +settings.convert_fork_desc=Du kannst diesen Fork in ein normales Repository umwandeln. Dies kann nicht rückgängig gemacht werden. settings.convert_fork_notices_1=Dieser Vorgang konvertiert den Fork in ein normales Repository und kann nicht rückgängig gemacht werden. settings.convert_fork_confirm=Repository umwandeln settings.convert_fork_succeed=Der Fork wurde in ein normales Repository konvertiert. @@ -1441,6 +1473,19 @@ settings.transfer_desc=Übertrage dieses Repository auf einen anderen Benutzer o settings.transfer_notices_1=– Du wirst keinen Zugriff mehr haben, wenn der neue Besitzer ein individueller Benutzer ist. settings.transfer_notices_2=– Du wirst weiterhin Zugriff haben, wenn der neue Besitzer eine Organisation ist und du einer der Besitzer bist. settings.transfer_form_title=Gib den Repository-Namen zur Bestätigung ein: +settings.signing_settings=Signaturüberprüfungseinstellungen +settings.trust_model=Signaturvertrauensmodell +settings.trust_model.default=Standardvertrauensmodell +settings.trust_model.default.desc=Verwende das Standardvertrauensmodell für diese Installation. +settings.trust_model.collaborator=Mitarbeiter +settings.trust_model.collaborator.long=Mitarbeiter: Vertraue Signaturen von Mitarbeitern +settings.trust_model.collaborator.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert - ( egal ob sie mit dem Committer übereinstimmen oder nicht). Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, unabhängig ob die Signatur mit dem Committer übereinstimmt oder nicht. +settings.trust_model.committer=Committer +settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Committern passen (Dies stimmt mit GitHub überein und zwingt signierte Commits von Gitea dazu, Gitea als Committer zu haben) +settings.trust_model.committer.desc=Gültige Signaturen werden nur dann als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen, andernfalls werden sie als "nicht übereinstimmend" markiert. Dies zwingt Gitea dazu, der Committer bei signierten Commits zu sein, wobei der eigentliche Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit markiert ist. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen. +settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer +settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen +settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, wenn die Signatur mit dem Committer übereinstimmt als "nicht übereinstimmend". Dies zwingt Gitea als Committer bei signierten Commits mit dem tatsächlichen Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen. settings.wiki_delete=Wiki-Daten löschen settings.wiki_delete_desc=Das Löschen von Wiki-Daten kann nicht rückgängig gemacht werden. Bitte sei vorsichtig. settings.wiki_delete_notices_1=– Dies löscht und deaktiviert das Wiki für %s. @@ -1720,6 +1765,7 @@ diff.review.comment=Kommentieren diff.review.approve=Genehmigen diff.review.reject=Änderung anfragen diff.committed_by=committed von +diff.protected=Geschützt releases.desc=Behalte den Überblick über Versionen und Downloads. release.releases=Releases @@ -1820,7 +1866,9 @@ settings.repoadminchangeteam=Der Repository-Administrator kann den Zugriff für settings.visibility=Sichtbarkeit settings.visibility.public=Öffentlich settings.visibility.limited=Eingeschränkt (nur für angemeldete Nutzer sichtbar) +settings.visibility.limited_shortname=Begrenzt settings.visibility.private=Privat (nur für Organisationsmitglieder sichtbar) +settings.visibility.private_shortname=Privat settings.update_settings=Einstellungen speichern settings.update_setting_success=Organisationseinstellungen wurden aktualisiert. @@ -1931,7 +1979,7 @@ dashboard.delete_inactive_accounts=Alle nicht aktivierten Konten löschen dashboard.delete_inactive_accounts.started=Löschen aller nicht aktivierten Account-Aufgabe gestartet. dashboard.delete_repo_archives=Alle Repository-Archive löschen dashboard.delete_repo_archives.started=Löschen aller Repository-Archive gestartet. -dashboard.delete_missing_repos=Alle Repository-Datensätze mit verlorenen gegangenen Git-Dateien löschen +dashboard.delete_missing_repos=Alle Repository-Datensätze mit verloren gegangenen Git-Dateien löschen dashboard.delete_missing_repos.started=Alle Repositories löschen, die die Git-Dateien-Aufgabe nicht gestartet haben. dashboard.delete_generated_repository_avatars=Generierte Repository-Avatare löschen dashboard.update_mirrors=Mirrors aktualisieren @@ -1943,6 +1991,7 @@ dashboard.update_migration_poster_id=Migration Poster-IDs updaten dashboard.git_gc_repos=Garbage-Collection auf Repositories ausführen dashboard.resync_all_sshkeys=Die Datei '.ssh/authorized_keys' mit Gitea SSH-Schlüsseln aktualisieren. dashboard.resync_all_sshkeys.desc=(Nicht benötigt für den eingebauten SSH-Server.) +dashboard.resync_all_sshprincipals.desc=(Nicht benötigt für den eingebauten SSH-Server.) dashboard.resync_all_hooks=Synchronisiere „pre-receive“-, „update“- und „post-receive“-Hooks für alle Repositories erneut. dashboard.reinit_missing_repos=Alle Git-Repositories mit Einträgen neu einlesen dashboard.sync_external_users=Externe Benutzerdaten synchronisieren @@ -2004,7 +2053,6 @@ users.prohibit_login=Anmelden deaktivieren users.is_admin=Ist Administrator users.is_restricted=Ist eingeschränkt users.allow_git_hook=Darf „Git Hooks“ erstellen -users.allow_git_hook_tooltip=Git Hooks werden auf dem System, auf dem Gitea läuft, mit den gleichen Zugangsrechten ausgeführt users.allow_import_local=Darf lokale Repositories importieren users.allow_create_organization=Darf Organisationen erstellen users.update_profile=Benutzerkonto aktualisieren @@ -2033,6 +2081,8 @@ orgs.members=Mitglieder orgs.new_orga=Neue Organisation repos.repo_manage_panel=Repositoryverwaltung +repos.unadopted=Nicht übernommene Repositories +repos.unadopted.no_more=Keine weiteren unübernommenen Repositories gefunden repos.owner=Besitzer repos.name=Name repos.private=Privat @@ -2082,6 +2132,11 @@ auths.filter=Benutzerfilter auths.admin_filter=Admin-Filter auths.restricted_filter=Eingeschränkte Filter auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Stern ('*'), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen. +auths.verify_group_membership=Gruppenmitgliedschaft in LDAP überprüfen +auths.group_search_base=Gruppensuche Basisdomainname +auths.valid_groups_filter=Gültiger Gruppenfilter +auths.group_attribute_list_users=Gruppenattribut, welches die die Benutzerliste enthält +auths.user_attribute_in_group=Benutzerattribut in der Gruppenliste auths.ms_ad_sa=MS-AD-Suchattribute auths.smtp_auth=SMTP-Authentifizierungstyp auths.smtphost=SMTP-Host @@ -2240,7 +2295,6 @@ config.session_config=Session-Konfiguration config.session_provider=Session-Provider config.provider_config=Provider-Einstellungen config.cookie_name=Cookie-Name -config.enable_set_cookie=Cookies verwenden config.gc_interval_time=GC-Intervall config.session_life_time=Session-Lebensdauer config.https_only=Nur HTTPS diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 11b78f7cf20a..c5d5c928c8e0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -21,10 +21,12 @@ signed_in_as = Signed in as enable_javascript = This website works better with JavaScript. toc = Table of Contents licenses = Licenses +return_to_gitea = Return to Gitea username = Username email = Email Address password = Password +access_token = Access Token re_type = Re-Type Password captcha = CAPTCHA twofa = Two-Factor Authentication @@ -298,6 +300,8 @@ authorization_failed = Authorization failed authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you've tried to authorize. disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. sspi_auth_failed = SSPI authentication failed +password_pwned = The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password. +password_pwned_err = Could not complete request to HaveIBeenPwned [mail] activate_account = Please activate your account @@ -352,6 +356,10 @@ lang_select_error = Select a language from the list. username_been_taken = The username is already taken. repo_name_been_taken = The repository name is already used. +repository_files_already_exist = Files already exist for this repository. Contact the system administrator. +repository_files_already_exist.adopt = Files already exist for this repository and can only be Adopted. +repository_files_already_exist.delete = Files already exist for this repository. You must delete them. +repository_files_already_exist.adopt_or_delete = Files already exist for this repository. Either adopt them or delete them. visit_rate_limit = Remote visit addressed rate limitation. 2fa_auth_required = Remote visit required two factors authentication. org_name_been_taken = The organization name is already taken. @@ -370,11 +378,12 @@ enterred_invalid_owner_name = The new owner name is not valid. enterred_invalid_password = The password you entered is incorrect. user_not_exist = The user does not exist. team_not_exist = The team does not exist. -last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. +last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization. cannot_add_org_to_team = An organization cannot be added as a team member. invalid_ssh_key = Can not verify your SSH key: %s invalid_gpg_key = Can not verify your GPG key: %s +invalid_ssh_principal = Invalid principal: %s unable_verify_ssh_key = "Can not verify the SSH key; double-check it for mistakes." auth_failed = Authentication failed: %v @@ -422,6 +431,7 @@ uid = Uid u2f = Security Keys public_profile = Public Profile +biography_placeholder = Tell us a little bit about yourself profile_desc = Your email address will be used for notifications and other operations. password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. full_name = Full Name @@ -492,9 +502,11 @@ keep_email_private_popup = Your email address will be hidden from other users. openid_desc = OpenID lets you delegate authentication to an external provider. manage_ssh_keys = Manage SSH Keys +manage_ssh_principals = Manage SSH Certificate Principals manage_gpg_keys = Manage GPG Keys add_key = Add Key ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. +principal_desc = These SSH certificate principals are associated with your account and allow full access to your repositories. gpg_desc = These public GPG keys are associated with your account. Keep your private keys safe as they allow commits to be verified. ssh_helper = Need help? Have a look at GitHub's guide to create your own SSH keys or solve common problems you may encounter using SSH. gpg_helper = Need help? Have a look at GitHub's guide about GPG. @@ -502,23 +514,30 @@ add_new_key = Add SSH Key add_new_gpg_key = Add GPG Key key_content_ssh_placeholder = Begins with 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521' key_content_gpg_placeholder = Begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal = Add Principal ssh_key_been_used = This SSH key has already been added to the server. -ssh_key_name_used = An SSH key with same name is already added to your account. +ssh_key_name_used = An SSH key with same name already exists on your account. +ssh_principal_been_used = This principal has already been added to the server. gpg_key_id_used = A public GPG key with same ID already exists. gpg_no_key_email_found = This GPG key is not usable with any email address associated with your account. subkeys = Subkeys key_id = Key ID key_name = Key Name key_content = Content +principal_content = Content add_key_success = The SSH key '%s' has been added. add_gpg_key_success = The GPG key '%s' has been added. +add_principal_success = The SSH certificate principal '%s' has been added. delete_key = Remove ssh_key_deletion = Remove SSH Key gpg_key_deletion = Remove GPG Key +ssh_principal_deletion = Remove SSH Certificate Principal ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? +ssh_principal_deletion_desc = Removing a SSH Certificate Principal revokes its access to your account. Continue? ssh_key_deletion_success = The SSH key has been removed. gpg_key_deletion_success = The GPG key has been removed. +ssh_principal_deletion_success = The principal has been removed. add_on = Added on valid_until = Valid until valid_forever = Valid forever @@ -528,10 +547,10 @@ can_read_info = Read can_write_info = Write key_state_desc = This key has been used in the last 7 days token_state_desc = This token has been used in the last 7 days +principal_state_desc = This principal has been used in the last 7 days show_openid = Show on profile hide_openid = Hide from profile ssh_disabled = SSH Disabled - manage_social = Manage Associated Social Accounts social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account. unbind = Unlink @@ -677,6 +696,15 @@ pick_reaction = Pick your reaction reactions_more = and %d more unit_disabled = The site administrator has disabled this repository section. language_other = Other +adopt_search = Enter username to search for unadopted repositories... (leave blank to find all) +adopt_preexisting_label = Adopt Files +adopt_preexisting = Adopt pre-existing files +adopt_preexisting_content = Create repository from %s +adopt_preexisting_success = Adopted files and created repository from %s +delete_preexisting_label = Delete +delete_preexisting = Delete pre-existing files +delete_preexisting_content = Delete files in %s +delete_preexisting_success = Deleted unadopted files in %s desc.private = Private desc.public = Public @@ -706,15 +734,17 @@ form.name_reserved = The repository name '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. need_auth = Clone Authorization -migrate_type = Migration Type -migrate_type_helper = This repository will be a mirror -migrate_type_helper_disabled = Your site administrator has disabled new mirrors. +migrate_options = Migration Options +migrate_service = Migration Service +migrate_options_mirror_helper = This repository will be a mirror +migrate_options_mirror_disabled = Your site administrator has disabled new mirrors. migrate_items = Migration Items migrate_items_wiki = Wiki migrate_items_milestones = Milestones migrate_items_labels = Labels migrate_items_issues = Issues migrate_items_pullrequests = Pull Requests +migrate_items_merge_requests = Merge Requests migrate_items_releases = Releases migrate_repo = Migrate Repository migrate.clone_address = Migrate / Clone From URL @@ -724,17 +754,24 @@ migrate.permission_denied = You are not allowed to import local repositories. migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." migrate.failed = Migration failed: %v migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. -migrate.migrate_items_options = When migrating from github, input a username and migration options will be displayed. +migrate.migrate_items_options = Access Token is required to migrate additional items migrated_from = Migrated from %[2]s migrated_from_fake = Migrated From %[1]s +migrate.migrate = Migrate From %s migrate.migrating = Migrating from %s ... migrate.migrating_failed = Migrating from %s failed. +migrate.github.description = Migrating data from Github.com or Github Enterprise. +migrate.git.description = Migrating or Mirroring git data from Git services +migrate.gitlab.description = Migrating data from GitLab.com or Self-Hosted gitlab server. +migrate.gitea.description = Migrating data from Gitea.com or Self-Hosted Gitea server. mirror_from = mirror of forked_from = forked from generated_from = generated from fork_from_self = You cannot fork a repository you own. fork_guest_user = Sign in to fork this repository. +watch_guest_user = Sign in to watch this repository. +star_guest_user = Sign in to star this repository. copy_link = Copy copy_link_success = Link has been copied copy_link_error = Use ⌘C or Ctrl-C to copy @@ -757,6 +794,7 @@ code = Code code.desc = Access source code, files, commits and branches. branch = Branch tree = Tree +clear_ref = `Clear current reference` filter_branch_and_tag = Filter branch or tag branches = Branches tags = Tags @@ -830,11 +868,13 @@ editor.file_deleting_no_longer_exists = The file being deleted, '%s', no longer editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. editor.file_already_exists = A file named '%s' already exists in this repository. editor.commit_empty_file_header = Commit an empty file -editor.commit_empty_file_text = The file you're about commit is empty. Proceed? +editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? editor.no_changes_to_show = There are no changes to show. -editor.fail_to_update_file = Failed to update/create file '%s' with error: %v +editor.fail_to_update_file = Failed to update/create file '%s'. +editor.fail_to_update_file_summary = Error Message: editor.push_rejected_no_message = The change was rejected by the server without a message. Please check githooks. -editor.push_rejected = The change was rejected by the server with the following message:
%s
Please check githooks. +editor.push_rejected = The change was rejected by the server. Please check githooks. +editor.push_rejected_summary = Full Rejection Message: editor.add_subdir = Add a directory… editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v editor.upload_file_is_locked = File '%s' is locked by %s. @@ -925,6 +965,9 @@ issues.new.clear_assignees = Clear assignees issues.new.no_assignees = No Assignees issues.new.no_reviewers = No reviewers issues.new.add_reviewer_title = Request review +issues.choose.get_started = Get Started +issues.choose.blank = Default +issues.choose.blank_about = Create an issue from default template. issues.no_ref = No Branch/Tag Specified issues.create = Create Issue issues.new_label = New Label @@ -1025,6 +1068,7 @@ issues.poster = Poster issues.collaborator = Collaborator issues.owner = Owner issues.re_request_review=Re-request review +issues.is_stale = There have been changes to this PR since this review issues.remove_request_review=Remove review request issues.remove_request_review_block=Can't remove review request issues.sign_in_require_desc = Sign in to join this conversation. @@ -1149,6 +1193,7 @@ issues.review.remove_review_request_self = "refused to review %s" issues.review.pending = Pending issues.review.review = Review issues.review.reviewers = Reviewers +issues.review.outdated = Outdated issues.review.show_outdated = Show outdated issues.review.hide_outdated = Hide outdated issues.review.show_resolved = Show resolved @@ -1196,6 +1241,8 @@ pulls.required_status_check_administrator = As an administrator, you may still m 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.blocked_by_outdated_branch = "This Pull Request is blocked because it's outdated." +pulls.blocked_by_changed_protected_files_1= "This Pull Request is blocked because it changes a protected file:" +pulls.blocked_by_changed_protected_files_n= "This Pull Request is blocked because it changes protected files:" 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. @@ -1222,11 +1269,15 @@ pulls.merge_manually = Manually merged pulls.merge_commit_id = The merge commit ID pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed pulls.invalid_merge_option = You cannot use this merge option for this pull request. -pulls.merge_conflict = Merge Failed: There was a conflict whilst merging: %[1]s
%[2]s
Hint: Try a different strategy -pulls.rebase_conflict = Merge Failed: There was a conflict whilst rebasing commit: %[1]s
%[2]s
%[3]s
Hint:Try a different strategy +pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy +pulls.merge_conflict_summary = Error Message +pulls.rebase_conflict = Merge Failed: There was a conflict whilst rebasing commit: %[1]s. Hint: Try a different strategy +pulls.rebase_conflict_summary = Error Message +; %[2]s
%[3]s
pulls.unrelated_histories = Merge Failed: The merge head and base do not share a common history. Hint: Try a different strategy pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again. -pulls.push_rejected = Merge Failed: The push was rejected with the following message:
%s
Review the githooks for this repository +pulls.push_rejected = Merge Failed: The push was rejected. Review the githooks for this repository. +pulls.push_rejected_summary = Full Rejection Message pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.
Review the githooks for this repository pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` pulls.status_checking = Some checks are pending @@ -1234,6 +1285,8 @@ pulls.status_checks_success = All checks were successful pulls.status_checks_warning = Some checks reported warnings pulls.status_checks_failure = Some checks failed pulls.status_checks_error = Some checks reported errors +pulls.status_checks_requested = Required +pulls.status_checks_details = Details pulls.update_branch = Update branch pulls.update_branch_success = Branch update was successful pulls.update_not_allowed = You are not allowed to update branch @@ -1245,6 +1298,7 @@ milestones.new = New Milestone milestones.open_tab = %d Open milestones.close_tab = %d Closed milestones.closed = Closed %s +milestones.update_ago = Updated %s ago milestones.no_due_date = No due date milestones.open = Open milestones.close = Close @@ -1453,6 +1507,19 @@ settings.transfer_desc = Transfer this repository to a user or to an organizatio settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. settings.transfer_form_title = Enter the repository name as confirmation: +settings.signing_settings = Signing Verification Settings +settings.trust_model = Signature Trust Model +settings.trust_model.default = Default Trust Model +settings.trust_model.default.desc= Use the default repository trust model for this installation. +settings.trust_model.collaborator = Collaborator +settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators +settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted" - (whether they match the committer or not). Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not. +settings.trust_model.committer = Committer +settings.trust_model.committer.long = Committer: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the committer) +settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This will force Gitea to be the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database. +settings.trust_model.collaboratorcommitter = Collaborator+Committer +settings.trust_model.collaboratorcommitter.long = Collaborator+Committer: Trust signatures by collaborators which match the committer +settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database. settings.wiki_delete = Delete Wiki Data settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. @@ -1732,6 +1799,7 @@ diff.review.comment = Comment diff.review.approve = Approve diff.review.reject = Request changes diff.committed_by = committed by +diff.protected = Protected releases.desc = Track project versions and downloads. release.releases = Releases @@ -1832,7 +1900,9 @@ settings.repoadminchangeteam = Repository admin can add and remove access for te settings.visibility = Visibility settings.visibility.public = Public settings.visibility.limited = Limited (Visible to logged in users only) +settings.visibility.limited_shortname = Limited settings.visibility.private = Private (Visible only to organization members) +settings.visibility.private_shortname = Private settings.update_settings = Update Settings settings.update_setting_success = Organization settings have been updated. @@ -1955,6 +2025,8 @@ dashboard.update_migration_poster_id = Update migration poster IDs dashboard.git_gc_repos = Garbage collect all repositories dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. dashboard.resync_all_sshkeys.desc = (Not needed for the built-in SSH server.) +dashboard.resync_all_sshprincipals = Update the '.ssh/authorized_principals' file with Gitea SSH principals. +dashboard.resync_all_sshprincipals.desc = (Not needed for the built-in SSH server.) dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist dashboard.sync_external_users = Synchronize external user data @@ -2016,7 +2088,7 @@ users.prohibit_login = Disable Sign-In users.is_admin = Is Administrator users.is_restricted = Is Restricted users.allow_git_hook = May Create Git Hooks -users.allow_git_hook_tooltip = Git Hooks are executed as the OS user running Gitea and will have the same level of host access +users.allow_git_hook_tooltip = Git Hooks are executed as the OS user running Gitea and will have the same level of host access. As a result, users with this special Git Hook privilege can access and modify all Gitea repositories as well as the database used by Gitea. Consequently they are also able to gain Gitea administrator privileges. users.allow_import_local = May Import Local Repositories users.allow_create_organization = May Create Organizations users.update_profile = Update User Account @@ -2045,6 +2117,8 @@ orgs.members = Members orgs.new_orga = New Organization repos.repo_manage_panel = Repository Management +repos.unadopted = Unadopted Repositories +repos.unadopted.no_more = No more unadopted repositories found repos.owner = Owner repos.name = Name repos.private = Private @@ -2094,6 +2168,11 @@ auths.filter = User Filter auths.admin_filter = Admin Filter auths.restricted_filter = Restricted Filter auths.restricted_filter_helper = Leave empty to not set any users as restricted. Use an asterisk ('*') to set all users that do not match Admin Filter as restricted. +auths.verify_group_membership = Verify group membership in LDAP +auths.group_search_base = Group Search Base DN +auths.valid_groups_filter = Valid Groups Filter +auths.group_attribute_list_users = Group Attribute Containing List Of Users +auths.user_attribute_in_group = User Attribute Listed In Group auths.ms_ad_sa = MS AD Search Attributes auths.smtp_auth = SMTP Authentication Type auths.smtphost = SMTP Host @@ -2252,7 +2331,6 @@ config.session_config = Session Configuration config.session_provider = Session Provider config.provider_config = Provider Config config.cookie_name = Cookie Name -config.enable_set_cookie = Enable Set Cookie config.gc_interval_time = GC Interval Time config.session_life_time = Session Life Time config.https_only = HTTPS Only diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index a0168e1f346d..35f631c88bc9 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -21,10 +21,12 @@ signed_in_as=Identificado como enable_javascript=Este sitio web funciona mejor con JavaScript. toc=Tabla de contenidos licenses=Licencias +return_to_gitea=Volver a Gitea username=Nombre de usuario email=Correo electrónico password=Contraseña +access_token=Token de acceso re_type=Vuelva a escribir la contraseña captcha=CAPTCHA twofa=Autenticación de doble factor @@ -52,6 +54,8 @@ new_migrate=Nueva migración new_mirror=Nueva réplica new_fork=Nuevo fork de repositorio new_org=Nueva organización +new_project=Nuevo Proyecto +new_project_board=Nuevo tablón de proyecto manage_org=Administrar organizaciones admin_panel=Administración del sitio account_settings=Configuraciones de la cuenta @@ -295,6 +299,8 @@ 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 +password_pwned=La contraseña que eligió está en una lista de contraseñas robadas previamente expuestas en violaciones de datos públicos. Por favor, inténtalo de nuevo con una contraseña diferente. +password_pwned_err=No se pudo completar la solicitud a HaveIBeenPwned [mail] activate_account=Por favor, active su cuenta @@ -349,6 +355,10 @@ 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. +repository_files_already_exist=Ya existen archivos para este repositorio. Póngase en contacto con el administrador del sistema. +repository_files_already_exist.adopt=Los archivos ya existen para este repositorio y sólo pueden ser aprobados. +repository_files_already_exist.delete=Ya existen archivos para este repositorio. Debe eliminarlos. +repository_files_already_exist.adopt_or_delete=Ya existen archivos para este repositorio. Adoptarlos o eliminarlos. visit_rate_limit=Remoto tiene limitación de tasa de acceso. 2fa_auth_required=Requerir autenticación de doble factor a visitas remotas. org_name_been_taken=Ya existe una organización con este nombre. @@ -367,11 +377,12 @@ enterred_invalid_owner_name=El nuevo nombre de usuario no es válido. enterred_invalid_password=La contraseña que ha introducido es incorrecta. user_not_exist=Este usuario no existe. team_not_exist=Este equipo no existe. -last_org_owner=No puedes eliminar al último usuario del equipo de 'propietarios'. Debe haber al menos un propietario en ningún equipo dado. +last_org_owner=No puedes eliminar al último usuario del equipo de 'propietarios'. Todas las organizaciones deben tener al menos un propietario. cannot_add_org_to_team=Una organización no puede ser añadida como miembro de un equipo. invalid_ssh_key=No se puede verificar su clave SSH: %s invalid_gpg_key=No se puede verificar su clave GPG: %s +invalid_ssh_principal=Principal no válido: %s unable_verify_ssh_key=No se puede verificar su clave SSH: compruebe si contiene errores. auth_failed=Autenticación fallo: %v @@ -388,6 +399,7 @@ repositories=Repositorios activity=Actividad pública followers=Seguidores starred=Repositorios Favoritos +projects=Proyectos following=Siguiendo follow=Seguir unfollow=Dejar de seguir @@ -418,6 +430,7 @@ uid=UUID u2f=Claves de seguridad public_profile=Perfil público +biography_placeholder=Cuéntenos un poco más sobre usted profile_desc=Su dirección de correo se utilizará para las notificaciones y otras operaciones. password_username_disabled=Usuarios no locales no tienen permitido cambiar su nombre de usuario. Por favor, contacta con el administrador del sistema para más detalles. full_name=Nombre completo @@ -488,31 +501,42 @@ keep_email_private_popup=Su dirección de correo electrónico será ocultada de openid_desc=OpenID le permite delegar la autenticación a un proveedor externo. manage_ssh_keys=Gestionar Claves SSH +manage_ssh_principals=Administrar Principales de Certificado SSH manage_gpg_keys=Administrar claves GPG add_key=Añadir Clave ssh_desc=Estas claves públicas SSH están asociadas con su cuenta. Las correspondientes claves privadas permite acceso completo a sus repositorios. +principal_desc=Estos principales de certificado SSH están asociados con su cuenta y permiten el acceso completo a sus repositorios. gpg_desc=Estas claves públicas GPG están asociadas con su cuenta. Mantenga sus claves privadas a salvo, ya que permiten verificar commits. ssh_helper=¿Necesitas ayuda? Echa un vistazo en la guía de GitHub para crear tus propias claves SSH o resolver problemas comunes que puede encontrar al usar SSH. gpg_helper=¿Necesitas ayuda? Echa un vistazo en la guía de GitHub sobre GPG. add_new_key=Añadir clave SSH add_new_gpg_key=Añadir clave GPG +key_content_ssh_placeholder=Comienza con 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384' o 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Comienza con '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal=Añadir Principal ssh_key_been_used=Esta clave SSH ya ha sido añadida al servidor. ssh_key_name_used=Una clave SSH con el mismo nombre ya ha sido añadida a su cuenta. +ssh_principal_been_used=Este principal ya ha sido añadido al servidor. gpg_key_id_used=Ya existe una clave GPG pública con el mismo ID. gpg_no_key_email_found=Esta clave GPG no es usable con ninguna de las direcciones de correo electrónico asociadas con su cuenta. subkeys=Subclaves key_id=ID de clave key_name=Nombre de la Clave key_content=Contenido +principal_content=Contenido add_key_success=Se ha añadido la clave SSH '%s'. add_gpg_key_success=Se ha añadido la clave GPG '%s'. +add_principal_success=El principal de certificado SSH '%s' ha sido añadido. delete_key=Eliminar ssh_key_deletion=Eliminar clave SSH gpg_key_deletion=Eliminar clave GPG +ssh_principal_deletion=Eliminar principal de certificado SSH ssh_key_deletion_desc=Eliminando una clave SSH se revoca su acceso a su cuenta. ¿Continuar? gpg_key_deletion_desc=Eliminando una clave GPG se des-verifican los commits firmados con ella. ¿Continuar? +ssh_principal_deletion_desc=Eliminar un principal de certificado SSH revoca su acceso a su cuenta. ¿Continuar? ssh_key_deletion_success=La clave SSH ha sido eliminada. gpg_key_deletion_success=La clave GPG ha sido eliminada. +ssh_principal_deletion_success=El principal ha sido eliminado. add_on=Añadido en valid_until=Válido hasta valid_forever=Válido para siempre @@ -522,10 +546,10 @@ can_read_info=Leer can_write_info=Escribir key_state_desc=Esta clave ha sido usada en los últimos 7 días token_state_desc=Este token ha sido utilizado en los últimos 7 días +principal_state_desc=Este principal ha sido utilizado en los últimos 7 días show_openid=Mostrar mi perfil hide_openid=Esconderse de perfil ssh_disabled=SSH deshabilitado - manage_social=Gestionar Redes Sociales asociadas social_desc=Estas cuentas sociales están vinculadas a su cuenta de Gitea. Asegúrese de que las reconoce todas, ya que pueden ser usadas para iniciar sesión en su cuenta de Gitea. unbind=Desvincular @@ -671,6 +695,15 @@ pick_reaction=Escoge tu reacción reactions_more=y %d más unit_disabled=El administrador del sitio ha deshabilitado esta sección del repositorio. language_other=Otros +adopt_search=Introduzca el nombre de usuario para buscar repositorios no adoptados... (déjelo en blanco para encontrar todos) +adopt_preexisting_label=Adoptar archivos +adopt_preexisting=Adoptar archivos preexistentes +adopt_preexisting_content=Crear repositorio desde %s +adopt_preexisting_success=Archivos aprobados y creados del repositorio desde %s +delete_preexisting_label=Eliminar +delete_preexisting=Eliminar archivos preexistentes +delete_preexisting_content=Eliminar archivos en %s +delete_preexisting_success=Eliminó archivos no adoptados en %s desc.private=Privado desc.public=Público @@ -700,15 +733,17 @@ form.name_reserved=El nombre de repositorio '%s' está reservado. form.name_pattern_not_allowed=El patrón '%s' no está permitido en un nombre de repositorio. need_auth=Autorización de clonación -migrate_type=Tipo de migración -migrate_type_helper=Este repositorio será una réplica -migrate_type_helper_disabled=El administrador de su sitio ha deshabilitado nuevos espejos. +migrate_options=Opciones de migración +migrate_service=Servicio de Migración +migrate_options_mirror_helper=Este repositorio será uno replicado +migrate_options_mirror_disabled=El administrador de tu sitio ha desactivado nuevos repositorios replicados. migrate_items=Objetos de migración migrate_items_wiki=Wiki migrate_items_milestones=Hitos migrate_items_labels=Etiquetas migrate_items_issues=Incidencias migrate_items_pullrequests=Pull Requests +migrate_items_merge_requests=Merge Requests migrate_items_releases=Lanzamientos migrate_repo=Migrar Repositorio migrate.clone_address=Migrar / Clonar desde URL @@ -718,17 +753,24 @@ migrate.permission_denied=No te está permitido importar repositorios locales. migrate.invalid_local_path=La ruta local es inválida. No existe o no es un directorio. migrate.failed=Migración fallida: %v migrate.lfs_mirror_unsupported=La replicación de objetos LFS no está soportada - use 'git lfs fetch --all' y 'git lfs push --all' en su lugar. -migrate.migrate_items_options=Cuando migra desde github, se mostrarán un nombre de usuario y se mostrarán opciones de migración. +migrate.migrate_items_options=Un token de acceso es necesario para migrar elementos adicionales migrated_from=Migrado desde %[2]s migrated_from_fake=Migrado desde %[1]s +migrate.migrate=Migrar desde %s migrate.migrating=Migrando desde %s... migrate.migrating_failed=La migración desde %s ha fallado. +migrate.github.description=Migrar datos de Github.com o Github Enterprise. +migrate.git.description=Migrar o replicar de datos de git desde los servicios de Git +migrate.gitlab.description=Migrar datos de GitLab.com o servidor gitlab autoalojado. +migrate.gitea.description=Migrando datos de Gitea.com o servidor Gitea autoalojado. mirror_from=réplica de forked_from=forkeado de generated_from=generado desde fork_from_self=No puede hacer fork a un repositorio que ya es suyo. fork_guest_user=Regístrate para forkear este repositorio. +watch_guest_user=Iniciar sesión para seguir este repositorio. +star_guest_user=Iniciar sesión para destacar este repositorio. copy_link=Copiar copy_link_success=El enlace ha sido copiado copy_link_error=Use ⌘ + C o Ctrl-C para copiar @@ -751,11 +793,13 @@ code=Código code.desc=Acceder código fuente, archivos, commits, y ramas. branch=Rama tree=Árbol +clear_ref=`Borrar referencia actual` filter_branch_and_tag=Filtrar por rama o etiqueta branches=Ramas tags=Etiquetas issues=Incidencias pulls=Pull Requests +project_board=Proyectos labels=Etiquetas org_labels_desc=Etiquetas de nivel de la organización que pueden ser utilizadas con todos los repositorios bajo esta organización org_labels_desc_manage=gestionar @@ -774,6 +818,8 @@ audio_not_supported_in_browser=Su navegador no soporta el tag audio de HTML5. stored_lfs=Almacenados con Git LFS symbolic_link=Enlace simbólico commit_graph=Gráfico de commits +commit_graph.monochrome=Mono +commit_graph.color=Color blame=Blame normal_view=Vista normal line=línea @@ -823,9 +869,11 @@ editor.file_already_exists=Ya existe un archivo con nombre '%s' en este reposito editor.commit_empty_file_header=Commit un archivo vacío editor.commit_empty_file_text=El archivo que estás tratando de commit está vacío. ¿Proceder? editor.no_changes_to_show=No existen cambios para mostrar. -editor.fail_to_update_file=Error al actualizar/crear el archivo '%s', error: %v +editor.fail_to_update_file=Error al actualizar/crear el archivo '%s'. +editor.fail_to_update_file_summary=Mensaje de error editor.push_rejected_no_message=El cambio fue rechazado por el servidor sin un mensaje. Por favor, compruebe githooks. -editor.push_rejected=El cambio fue rechazado por el servidor con el siguiente mensaje:
%s
Por favor, compruebe los gitooks. +editor.push_rejected=El cambio fue rechazado por el servidor. Por favor, compruebe githooks. +editor.push_rejected_summary=Mensaje completo de rechazo editor.add_subdir=Añadir un directorio… editor.unable_to_upload_files=Error al subir archivos a '%s', error: %v editor.upload_file_is_locked=El archivo '%s' está bloqueado por %s. @@ -855,10 +903,40 @@ commits.gpg_key_id=ID de clave GPG ext_issues=Incidencias externas ext_issues.desc=Enlace a un gestor de incidencias externo. +projects=Proyectos +projects.desc=Gestionar problemas y pulls en los tablones del proyecto. +projects.create=Crear Proyecto +projects.title=Título +projects.new=Nuevo proyecto +projects.new_subheader=Coordine, haga seguimiento y actualice su trabajo en un solo lugar, para que los proyectos se mantengan transparentes y en el calendario previsto. +projects.create_success=El proyecto '%s' ha sido creado. +projects.deletion=Eliminar Proyecto +projects.deletion_desc=Eliminar un proyecto elimina todos las incidencias relacionadas. ¿Continuar? +projects.deletion_success=Se eliminó el proyecto. +projects.edit=Editar Proyectos +projects.edit_subheader=Los proyectos organizan las incidencias y el seguimiento del progreso. +projects.modify=Actualizar Proyecto +projects.edit_success=El proyecto '%s' ha sido actualizado. +projects.type.none=Ninguno +projects.type.basic_kanban=Kanban básico +projects.type.bug_triage=Prueba de error +projects.template.desc=Plantilla del proyecto +projects.template.desc_helper=Seleccione una plantilla de proyecto para empezar +projects.type.uncategorized=Sin categorizar +projects.board.edit=Editar tablón +projects.board.edit_title=Nuevo Nombre del Tablón +projects.board.new_title=Nuevo Nombre del Tablón +projects.board.new_submit=Enviar +projects.board.new=Nuevo tablón +projects.board.delete=Eliminar tablón +projects.board.deletion_desc=Eliminar un tablón de proyecto mueve todas las incidencias relacionadas a 'Sin categorizar'. ¿Continuar? +projects.open=Abrir +projects.close=Cerrar issues.desc=Organizar los informes de fallos, tareas e hitos. issues.filter_assignees=Filtrar asignado issues.filter_milestones=Filtrar hito +issues.filter_projects=Filtrar Proyecto issues.filter_labels=Filtrar etiqueta issues.filter_reviewers=Filtrar revisor issues.new=Nueva incidencia @@ -867,6 +945,12 @@ issues.new.labels=Etiquetas issues.new.add_labels_title=Aplicar etiquetas issues.new.no_label=Sin etiquetas issues.new.clear_labels=Limpiar etiquetas +issues.new.projects=Proyectos +issues.new.add_project_title=Definir Proyecto +issues.new.clear_projects=Limpiar proyectos +issues.new.no_projects=Ningún proyecto +issues.new.open_projects=Proyectos Abiertos +issues.new.closed_projects=Proyectos cerrados issues.new.no_items=No hay elementos issues.new.milestone=Milestone issues.new.add_milestone_title=Fijar hito @@ -880,6 +964,9 @@ issues.new.clear_assignees=Limpiar asignados issues.new.no_assignees=No asignados issues.new.no_reviewers=No hay revisores issues.new.add_reviewer_title=Solicitar revisión +issues.choose.get_started=Comenzar +issues.choose.blank=Predeterminado +issues.choose.blank_about=Crear una incidencia a partir de la plantilla predeterminada. issues.no_ref=Ninguna Rama/Etiqueta especificada issues.create=Crear incidencia issues.new_label=Nueva Etiqueta @@ -894,9 +981,13 @@ issues.label_templates.fail_to_load_file=Error al cargar el archivo de plantilla issues.add_label_at=añadida la etiqueta
%s
%s issues.remove_label_at=eliminada la etiqueta
%s
%s issues.add_milestone_at=`ha añadido esto al hito %s %s` +issues.add_project_at=`añadió esto al proyecto %s %s` issues.change_milestone_at=`modificó el hito de %s a %s %s` +issues.change_project_at=`modificó el proyecto de %s a %s %s` issues.remove_milestone_at=`ha eliminado esto del hito %s %s ` +issues.remove_project_at=`removió esto del proyecto %s %s` issues.deleted_milestone=`(eliminado)` +issues.deleted_project=`(eliminado)` issues.self_assign_at=`auto asignado este %s` issues.add_assignee_at='fue asignado por %s %s' issues.remove_assignee_at=`fue desasignado por %s %s` @@ -975,6 +1066,7 @@ issues.poster=Autor issues.collaborator=Colaborador issues.owner=Propietario issues.re_request_review=Solicitar revisión de nuevo +issues.is_stale=Ha habido cambios en este PR desde esta revisión issues.remove_request_review=Eliminar solicitud de revisión issues.remove_request_review_block=No se puede eliminar la solicitud de revisión issues.sign_in_require_desc=Inicie sesión para unirse a esta conversación. @@ -1099,6 +1191,7 @@ issues.review.remove_review_request_self=rechazó revisar %s issues.review.pending=Pendiente issues.review.review=Revisar issues.review.reviewers=Revisores +issues.review.outdated=Obsoleto issues.review.show_outdated=Mostrar obsoletos issues.review.hide_outdated=Ocultar obsoletos issues.review.show_resolved=Mostrar resueltos @@ -1142,6 +1235,8 @@ pulls.required_status_check_administrator=Como administrador, aún puede fusiona pulls.blocked_by_approvals=Este pull request aún no tiene suficientes aprobaciones. %d de %d autorizaciones concedidas. pulls.blocked_by_rejection=Esta Pull Request tiene cambios solicitados por un revisor oficial. pulls.blocked_by_outdated_branch=Este Pull Request está bloqueada porque está desactualizada. +pulls.blocked_by_changed_protected_files_1=Esta Pull Request está bloqueada porque cambia un archivo protegido: +pulls.blocked_by_changed_protected_files_n=Esta Pull Request está bloqueada porque cambia archivos protegidos: pulls.can_auto_merge_desc=Este Pull Request puede ser fusionado automáticamente. pulls.cannot_auto_merge_desc=Este pull request no se puede combinar automáticamente debido a conflictos. pulls.cannot_auto_merge_helper=Combinar manualmente para resolver los conflictos. @@ -1165,11 +1260,15 @@ pulls.rebase_merge_commit_pull_request=Hacer Rebase y Fusionar (--no-ff) pulls.squash_merge_pull_request=Hacer Squash y Fusionar pulls.require_signed_wont_sign=Esta rama requiere commits firmados pero esta fusión no será firmada 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.merge_conflict=Fusión fallida: Hubo un conflicto mientras se fusionaba. Pista: Pruebe una estrategia diferente +pulls.merge_conflict_summary=Mensaje de error +pulls.rebase_conflict=Fusión fallida: Hubo un conflicto mientras se rebasaba el commit: %[1]s. Pista: Prueba una estrategia diferente +pulls.rebase_conflict_summary=Mensaje de error +; %[2]s
%[3]s
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.push_rejected=Fusión fallida: El push fue rechazado con el siguiente mensaje:
%s
Revisar los githooks para este repositorio +pulls.push_rejected=Fusión fallida: El push fue rechazado. Revise los githooks para este repositorio. +pulls.push_rejected_summary=Mensaje completo de rechazo pulls.push_rejected_no_message=Fusión fallida: El push fue rechazado pero no hubo mensaje remoto.
Revise los githooks para este repositorio 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 @@ -1177,6 +1276,8 @@ pulls.status_checks_success=Todas las comprobaciones han sido exitosas pulls.status_checks_warning=Algunas comprobaciones han reportado advertencias pulls.status_checks_failure=Algunas comprobaciones han fallado pulls.status_checks_error=Algunas comprobaciones reportaron errores +pulls.status_checks_requested=Obligatorio +pulls.status_checks_details=Detalles pulls.update_branch=Actualizar rama pulls.update_branch_success=La actualización de la rama ha finalizado correctamente pulls.update_not_allowed=No tiene permisos para actualizar esta rama @@ -1188,6 +1289,7 @@ milestones.new=Nuevo hito milestones.open_tab=%d abiertas milestones.close_tab=%d cerradas milestones.closed=Cerrada %s +milestones.update_ago=Actualizado hace %s milestones.no_due_date=Sin fecha límite milestones.open=Abrir milestones.close=Cerrar @@ -1227,6 +1329,7 @@ signing.wont_sign.basesigned=Esta fusión no se firmará ya que base commit no e signing.wont_sign.headsigned=Esta fusión no se firmará ya que head commit no está firmado signing.wont_sign.commitssigned=Esta fusión no se firmará ya que todos sus commits asociados no están firmados signing.wont_sign.approved=Esta fusión no se firmará ya que el PR no está aprobado +signing.wont_sign.not_signed_in=No has iniciado sesión ext_wiki=Wiki externa ext_wiki.desc=Enlace a una wiki externa. @@ -1372,6 +1475,7 @@ settings.pulls.allow_merge_commits=Activar Commit Fusionar settings.pulls.allow_rebase_merge=Activar Rebase de los commits fusionados settings.pulls.allow_rebase_merge_commit=Activar Rebase con commits explícitos de fusión (--no-ff) settings.pulls.allow_squash_commits=Activar Squash en los commits fusionados +settings.projects_desc=Activar Proyectos de Repositorio settings.admin_settings=Ajustes de administrador settings.admin_enable_health_check=Activar cheques de estado de salud del repositorio (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Cerrar una incidencia a través de un commit realizado en una rama no principal @@ -1392,6 +1496,19 @@ settings.transfer_desc=Transferir este repositorio a un usuario o una organizaci settings.transfer_notices_1=- Perderá el acceso al repositorio si lo transfiere a un usuario individual. settings.transfer_notices_2=- Mantendrá el acceso al repositorio si lo transfiere a una organización que usted (co-)posee. settings.transfer_form_title=Escriba el nombre del repositorio como confirmación: +settings.signing_settings=Configuración de verificación de firmas +settings.trust_model=Modelo de confianza de firma +settings.trust_model.default=Modelo de confianza por defecto +settings.trust_model.default.desc=Utilice el modelo de confianza de repositorio por defecto para esta instalación. +settings.trust_model.collaborator=Colaborador +settings.trust_model.collaborator.long=Colaborador: Confiar en firmas de colaboradores +settings.trust_model.collaborator.desc=Las firmas válidas de los colaboradores de este repositorio serán marcadas como "confiables" - (coincidan o no con el committer). De lo contrario, las firmas válidas serán marcadas como "no confiables" si la firma coincide con el committer y "no coincidente" si no lo es. +settings.trust_model.committer=Committer +settings.trust_model.committer.long=Committer: Firmas de confianza que coinciden con los committers (Esto coincide con GitHub y obligará a Gitea a firmar los commits a tener a Gitea como el committer) +settings.trust_model.committer.desc=Las firmas válidas solo se marcarán como "confiables" si coinciden con el autor de la confirmación; de lo contrario, se marcarán como "no coincidentes". Esto obligará a Gitea a ser el confirmador en los compromisos firmados con el confirmador real marcado como Co-Authored-By: y Co-Committed-By: tráiler en el commit. La clave Gitea predeterminada debe coincidir con un usuario en la base de datos. +settings.trust_model.collaboratorcommitter=Colaborador+Comitter +settings.trust_model.collaboratorcommitter.long=Colaborador+Comitter: Confiar en firmas de colaboradores que coincidan con el committer +settings.trust_model.collaboratorcommitter.desc=Las firmas válidas de los colaboradores de este repositorio se marcarán como "de confianza" si coinciden con el confirmador. De lo contrario, las firmas válidas se marcarán como "no confiables" si la firma coincide con el autor de la confirmación y como "no coincidentes" en caso contrario. Esto obligará a Gitea a ser marcado como el confirmador en los compromisos firmados con el confirmador real marcado como Coautor por: y Cocommitido por: tráiler en el compromiso. La clave Gitea predeterminada debe coincidir con un usuario en la base de datos. settings.wiki_delete=Eliminar datos de Wiki settings.wiki_delete_desc=Eliminar los datos del wiki del repositorio es permanente y no se puede deshacer. settings.wiki_delete_notices_1=- Esto eliminará y desactivará permanentemente el wiki del repositorio para %s. @@ -1558,6 +1675,7 @@ settings.protect_merge_whitelist_committers_desc=Permitir a los usuarios o equip settings.protect_merge_whitelist_users=Usuarios en la lista blanca para fusionar: settings.protect_merge_whitelist_teams=Equipos en la lista blanca para fusionar: settings.protect_check_status_contexts=Habilitar comprobación de estado +settings.protect_check_status_contexts_desc=Requiere verificaciones de estado para pasar antes de fusionar. Elija qué verificaciones de estado deben pasar antes de que las ramas puedan fusionarse en una rama que coincida con esta regla. Cuando se active, los commits primero deben ser empujados a otra rama, y luego fusionados o empujados directamente a una rama que coincida con esta regla luego de que las verificaciones de estado hayan pasado. Si no se selecciona ningún contexto, el último commit debe ser exitoso sin importar el 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. @@ -1570,6 +1688,7 @@ settings.dismiss_stale_approvals_desc=Cuando los nuevos commits que cambien el c settings.require_signed_commits=Requiere commits firmados settings.require_signed_commits_desc=Rechazar push en esta rama si los commits no están firmados o no son verificables. settings.protect_protected_file_patterns=Patrones de archivos protegidos (separados con punto y coma '\;'): +settings.protect_protected_file_patterns_desc=No se permite cambiar directamente archivos protegidos, incluso si el usuario tiene derechos para añadir, editar o eliminar archivos en esta rama. Se pueden separar múltiples patrones usando punto y coma ('\;'). Véase la documentación de github.com/gobwas/glob sobre sintaxis de patrones. Ejemplos: .drone.yml,/docs/**/*.txt. settings.add_protected_branch=Activar protección settings.delete_protected_branch=Desactivar protección settings.update_protect_branch_success=La protección de la rama '%s' ha sido actualizada. @@ -1669,6 +1788,7 @@ diff.review.comment=Comentario diff.review.approve=Aprobar diff.review.reject=Solicitud de cambios diff.committed_by=cometido por +diff.protected=Protegido releases.desc=Seguir las versiones y descargas del proyecto. release.releases=Lanzamientos @@ -1769,7 +1889,9 @@ settings.repoadminchangeteam=El administrador del repositorio puede añadir y el settings.visibility=Visibilidad settings.visibility.public=Público settings.visibility.limited=Limitado (Visible sólo para los usuarios registrados) +settings.visibility.limited_shortname=Limitado settings.visibility.private=Privado (Visible sólo para miembros de la organización) +settings.visibility.private_shortname=Privado settings.update_settings=Actualizar configuración settings.update_setting_success=Configuración de la organización se han actualizado. @@ -1892,6 +2014,8 @@ dashboard.update_migration_poster_id=Actualizar ID de usuario en migraciones dashboard.git_gc_repos=Ejecutar la recolección de basura en los repositorios dashboard.resync_all_sshkeys=Actualizar el archivo '.ssh/authorized_keys' con claves SSH de Gitea. dashboard.resync_all_sshkeys.desc=(No es necesario para el servidor SSH incorporado.) +dashboard.resync_all_sshprincipals=Actualizar el archivo '.ssh/authorized_principals' con los principales de certificado SSH de Gitea. +dashboard.resync_all_sshprincipals.desc=(No es necesario para el servidor SSH incorporado.) dashboard.resync_all_hooks=Resincronizar los hooks de pre-recepción, actualización y post-recepción de todos los repositorios. dashboard.reinit_missing_repos=Reiniciar todos los repositorios Git faltantes de los que existen registros dashboard.sync_external_users=Sincronizar datos de usuario externo @@ -1932,6 +2056,7 @@ users.full_name=Nombre completo users.activated=Activado users.admin=Administrador users.restricted=Restringido +users.2fa=2FA users.repos=Repositorios users.created=Creado users.last_login=Último registro @@ -1952,7 +2077,7 @@ users.prohibit_login=Desactivar inicio de sesión users.is_admin=Es administrador users.is_restricted=está restringido users.allow_git_hook=Puede crear Git Hooks -users.allow_git_hook_tooltip=Git Hooks son ejecutados como el usuario del sistema operativo ejecutando Gitea y tendrán el mismo nivel de acceso de host +users.allow_git_hook_tooltip=Git Hooks se ejecutan como el usuario del sistema operativo que ejecuta Gitea y tendrá el mismo nivel de acceso de host. Como resultado, los usuarios con este privilegio especial de Git Hook pueden acceder y modificar todos los repositorios de Gitea así como la base de datos utilizada por Gitea. También pueden obtener privilegios de administrador de Gitea. users.allow_import_local=Puede importar repositorios locales users.allow_create_organization=Puede crear organizaciones users.update_profile=Actualizar cuenta de usuario @@ -1981,6 +2106,8 @@ orgs.members=Miembros orgs.new_orga=Nueva organización repos.repo_manage_panel=Gestión de repositorios +repos.unadopted=Repositorios no adoptados +repos.unadopted.no_more=No se encontraron más repositorios no adoptados repos.owner=Propietario repos.name=Nombre repos.private=Privado @@ -2030,6 +2157,11 @@ auths.filter=Filtro de usuario auths.admin_filter=Filtro de aministrador auths.restricted_filter=Filtro restringido auths.restricted_filter_helper=Dejar en blanco para no establecer ningún usuario como restringido. Utilice un asterisco ('*') para establecer todos los usuarios que no coincidan con el filtro de administración como restringido. +auths.verify_group_membership=Verificar pertenencia al grupo en LDAP +auths.group_search_base=Base DN para la búsqueda de grupos +auths.valid_groups_filter=Filtro de grupos válidos +auths.group_attribute_list_users=Atributo del grupo que contiene la lista de usuarios +auths.user_attribute_in_group=Atributo de usuario listado en el grupo auths.ms_ad_sa=Atributos de búsqueda de MS AD auths.smtp_auth=Tipo de autenticación SMTP auths.smtphost=Servidor SMTP @@ -2170,6 +2302,7 @@ config.mailer_use_sendmail=Usar Sendmail config.mailer_sendmail_path=Ruta de Sendmail config.mailer_sendmail_args=Argumentos adicionales por Sendmail config.mailer_sendmail_timeout=Tiempo de espera de Sendmail +config.test_email_placeholder=Correo electrónico (ej. test@ejemplo.com) config.send_test_mail=Enviar prueba de correo config.test_mail_failed=Fallo al enviar correo electrónico de prueba a '%s': %v config.test_mail_sent=Se ha enviado un correo electrónico de prueba a '%s'. @@ -2187,7 +2320,6 @@ config.session_config=Configuración de la Sesión config.session_provider=Proveedor de la Sesión config.provider_config=Configuración del Proveedor config.cookie_name=Nombre de la Cookie -config.enable_set_cookie=Activar Establecimiento de Cookie config.gc_interval_time=Intervalo de tiempo del GC config.session_life_time=Tiempo de Vida de la Sesión config.https_only=Sólo HTTPS @@ -2329,6 +2461,7 @@ mirror_sync_create=sincronizada nueva referencia %[2]s a mirror_sync_delete=sincronizada y eliminada referencia %[2]s en %[3]s desde réplica approve_pull_request=`aprobado %s#%[2]s` reject_pull_request=`sugerido cambios para %s#%[2]s` +publish_release=`se lanzó "%[4]s" en %[3]s` [tool] ago=hace %s diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 6716d1839061..3b21f6cf8a30 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -20,6 +20,7 @@ user_profile_and_more=پروفایل و تنظیمات… signed_in_as=ورود به عنوان enable_javascript=این وب‌سایت با جاوا اسکریپت بهتر کار می‌کند. toc=فهرست محتویات +licenses=گواهینامه ها username=نام کاربری email=آدرس ایمیل @@ -51,6 +52,8 @@ new_migrate=انتقال جدید new_mirror=قرینه ای جدید new_fork=انشعاب مخزن جدید new_org=سازمان جدید +new_project=پروژه جدید +new_project_board=صفحه پروژه جدید manage_org=مدیریت سازمان‌ها admin_panel=مدیریت سایت account_settings=تنظیمات حساب @@ -71,6 +74,7 @@ issues=مسائل milestones=نقاط عطف cancel=انصراف +save=ذخیره add=افزودن add_all=افزودن همه remove=حذف @@ -80,13 +84,16 @@ write=نوشتن preview=پیش نمایش loading=بارگذاری… +error404=صفحه موردنظر شما یا وجود ندارد یا شما دسترسی کافی برای مشاهده آن را ندارید. [error] occurred=خطایی رخ داده است +report_message=اگر شما مطمئن هستیند این مشکل مربوط به یک باگ در Gitea است، لطفا در GitHub مشکل را جستجو کنید و در صورت نیاز، یک موضوع جدید باز کنید. [startpage] app_desc=یک سرویس گیت بی‌درد سر و راحت install=راه‌اندازی ساده +install_desc=به سادگی فایل اجرایی را برای پلتفرم موردنظر خود اجرا کنید یا آن را در قالب یک کانتینر Docker آماده کنید و یا بصورت یک بسته دریافت کنید. platform=مستقل از سکو platform_desc=گیت همه جا اجرا می‌شود بریم! می‌توانید Windows, macOS, Linux, ARM و ... هر کدام را دوست داشتید انتخاب کنید! lightweight=ابزارک سبک @@ -107,6 +114,7 @@ password=رمز عبور db_name=نام پایگاه داده db_helper=نکته برای کاربران MySQL: لطفا از موتور InnoDB استفاده کنید و اگر از نوع کدینگ "utf8mb4" استفاده می کنید، ورژن InnoDB خود را به بالای 5.6 به روز رسانی کنید. db_schema=قالب +db_schema_helper=برای مقدار پیش فرض پایگاه داده خالی بگذارید ("public"). ssl_mode=SSL charset=نوع کدگذاری path=مسیر @@ -162,6 +170,7 @@ 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=کاربران وارد شده دسترسی به صفحات را دارند. مهمان‌ها فقط قادر به دیدن صفحه 'ثبت نام' و 'ورود' هستند. @@ -201,8 +210,17 @@ my_orgs=سازمان های من my_mirrors=قرینه‌های من view_home=نمایش %s search_repos=یافتن مخزن… +filter=فیلترهای دیگر +show_archived=بایگانی شده +show_both_archived_unarchived=نمایش دادن موارد بایگانی شده و غیر بایگانی نشده +show_only_archived=نمایش دادن موارد بایگانی شده +show_only_unarchived=نمایش دادن موارد بایگانی نشده +show_private=خصوصی +show_both_private_public=نماش دادن موارد عمومی و خصوصی +show_only_private=نماش دادن موارد خصوصی +show_only_public=نمایش دادن موارد عمومی issues.in_your_repos=در مخازن شما @@ -351,7 +369,6 @@ enterred_invalid_owner_name=نام مالک جدید معتبر نیست. enterred_invalid_password=گذرواژه وارد شده صحیح نیست. user_not_exist=کاربر وجود ندارد. team_not_exist=تیم وجود ندارد. -last_org_owner=قادر به حذف کاربر آخر از تیم "صاحبان" نیست. باید حداقل یک مالک در هر تیم باشد. cannot_add_org_to_team=یک سازمان را نمی توان به عنوان عضو تیم اضافه کرد. invalid_ssh_key=کلید SSH شما تأیید نشد: %s @@ -372,11 +389,13 @@ repositories=مخازن activity=فعالیت های عمومی followers=دنبال کنندگان starred=مخان ستاره دار +projects=پروژه‌ها following=دنبال میکنید follow=دنبال کردن unfollow=عدم دنبال کردن heatmap.loading=بارگذاری Heatmap… user_bio=زندگی‌نامه +disabled_public_activity=این کاربر نمایش عمومی فعالیت های خود را غیرفعال کرده است. form.name_reserved=نام کاربری "%s" استفاده شده است. form.name_pattern_not_allowed=الگوی %s در نام کاربری مجاز نیست. @@ -415,6 +434,9 @@ continue=ادامه cancel=انصراف language=زبان ui=پوسته +privacy=حریم خصوصی +keep_activity_private=مخفی ساختن فعالیت ها از صفحه پروفایل +keep_activity_private_popup=نمایان ساختن فعالیت ها برای شما و مدیران lookup_avatar_by_mail=جست و جو آواتار توسط نشانی ایمیل federated_avatar_lookup=جستجو برای آواتار مشترک @@ -461,6 +483,7 @@ add_email=اضافه کردن نشانی ایمیل add_openid=اضافه کردن نشانی OpenID add_email_confirmation_sent=یک ایمیل تایید به نشانی %s ارسال شد, لطفا صندوق خود را حداکثر تا %s آینده برای تکمیل فرایند تایید بررسی کنید. add_email_success=نشانی ایمیل جدید اضافه شده است. +email_preference_set_success=تنظیمات ایمیل با موفقیت اعمال شد. add_openid_success=نشانی OpenID اضافه شد. keep_email_private=مخفی کردن نشانی ایمیل keep_email_private_popup=نشانی ایمیل شما به کاربران دیگر نمایش داده نمی‌شود. @@ -478,7 +501,6 @@ 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 با این ID پیش از این وجود داشته است. gpg_no_key_email_found=این کلید GPG با هیچ ایمیلی که به حساب شما مرتبط است، قابل استفاده نیست. subkeys=کلید های زیر مجموعه @@ -506,7 +528,6 @@ token_state_desc=این توکن در ۷ روز گذشته استفاده شده show_openid=نمایش بر روی نمایه hide_openid=مخفی کردن از نمایه ssh_disabled=SSH غیر فعل شد - manage_social=مدیریت حساب های اجتماعی مرتبط social_desc=تمامی شبکه های اجتماعی که به حساب کاربری شما متصل است. مطمئن شوید که با تمامی آنها می‎توانید به صورت ایمن وارد شوید. unbind=لغو ارتباط @@ -519,6 +540,7 @@ new_token_desc=برنامه های از token شما می‎تواندد دست token_name=نام توکن generate_token=ساخت توکن generate_token_success=اکنون token جدید ساخته شد. همینک آنها را کپی کنید دوباره آن را نخواهید دید. +generate_token_name_duplicate=%s قبلا بعنوان نام یک برنامه استفاده شده است. لطفا از یک نام دیگر استفاده کنید. delete_token=حذف access_token_deletion=حذف توکن access_token_deletion_desc=حذف token باعث از کار افتادن تمامی برنامه‎هایی که در آنها به کار رفته می‎شود. آیا ادامه می‎دهید؟ @@ -653,6 +675,13 @@ reactions_more=و %d بیشتر unit_disabled=مدیر سایت این قسمت مخزن را غیرفعال کرده است. language_other=دیگر +desc.private=خصوصی +desc.public=عمومی +desc.private_template=قالب خصوصی +desc.public_template=قالب +desc.internal=داخلی +desc.internal_template=قالب داخلی +desc.archived=بایگانی شده template.items=موارد الگو template.git_content=محتوای گیت (شاخه پیش فرض) @@ -674,8 +703,6 @@ 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=نقاط عطف @@ -691,7 +718,6 @@ 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 ... @@ -729,7 +755,9 @@ branches=شاخه‎ها tags=برچسب‎ها issues=مسائل pulls=تقاضاهای واکشی +project_board=پروژه‌ها labels=برچسب‌ها +org_labels_desc=برچسب های سطح سازمان که می توانند برای تمامی مخازن ذیل این سازمان استفاده شوند org_labels_desc_manage=مدیریت milestones=نقاط عطف @@ -744,7 +772,10 @@ file_too_large=حجم این پرونده بیشتر از آن است که قا video_not_supported_in_browser=مرورگر شما از تگ video که در HTML5 تعریف شده است، پشتیبانی نمی کند. audio_not_supported_in_browser=مرورگر شما از تگ audio که در HTML5 تعریف شده است، پشتیبانی نمی کند. stored_lfs=ذخیره شده با GIT LFS +symbolic_link=پیوند نمادین commit_graph=نمودار کامیت +commit_graph.monochrome=مونو +commit_graph.color=رنگ blame=سرزنش normal_view=نمایش عادی line=خط @@ -767,6 +798,7 @@ editor.name_your_file=نام پرونده شما… editor.filename_help=برای اضافه کردن پوشه از slash ('/') استفاده کنید. برای حذف آن ها از backspace در ابتدای فیلد ورودی است استفاده کنید. editor.or=یا editor.cancel_lower=انصراف +editor.commit_signed_changes=اعمال تغییرات امضا شده editor.commit_changes=تغییرات کامیت editor.add_tmpl=افزودن '' editor.add=افزودن '%s' @@ -791,9 +823,8 @@ editor.file_deleting_no_longer_exists=فایل آماده حذف می‌شود ' 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.push_rejected_no_message=درخواست تغییر توسط سرور بدون هیچ پیامی رد شد. لطفا githooks را بررسی کنید. editor.add_subdir=افزودن پوشه… editor.unable_to_upload_files=عدم موفقیت در آپلود پرونده به '%s' با خطا: %v editor.upload_file_is_locked=پرونده '%s' توسط %s قفل شده است. @@ -816,27 +847,47 @@ commits.date=تاریخ commits.older=قدیمی تر commits.newer=جدیدتر commits.signed_by=امضا شده توسط +commits.signed_by_untrusted_user=امضا شده توسط یک کاربر غیرقابل اعتماد +commits.signed_by_untrusted_user_unmatched=امضا شده توسط یک کاربر غیرقابل اعتماد که با اعمال کننده تغییرات مطابقت ندارد commits.gpg_key_id=شناسه کلید GPG ext_issues=مسائل اضافی ext_issues.desc=پیوند به ردیاب خارجی برای موضوع. +projects=پروژه‌ها +projects.create=ایجاد پروژه جدید +projects.title=عنوان +projects.new=پروژه جدید +projects.deletion=حذف پروژه +projects.open=باز‌کردن +projects.close=بستن issues.desc=سازمان دهی گزارش باگ ها و وظایف و.... +issues.filter_assignees=عامل فیلتر +issues.filter_milestones=نقطه عطف فیلتر +issues.filter_labels=برچسب فیلتر +issues.filter_reviewers=بازبین گر فیلتر issues.new=مسئله‌ی جدید issues.new.title_empty=عنوان نمی تواند خالی باشد issues.new.labels=برچسب‌ها +issues.new.add_labels_title=بکار بردن برچسب ها issues.new.no_label=بدون برچسب issues.new.clear_labels=پاک‌کردن برچسب‌ها +issues.new.projects=پروژه‌ها +issues.new.clear_projects=پاک کردن پروژه‌ها issues.new.no_items=موردی وجود ندارد issues.new.milestone=نقطه عطف +issues.new.add_milestone_title=تنظیم نقطه عطف issues.new.no_milestone=بدون نقطه عطف issues.new.clear_milestone=پاک‌کردن نقطه عطف issues.new.open_milestone=نقاط عطف باز issues.new.closed_milestone=نقاط عطف بسته issues.new.assignees=تخصیص شده +issues.new.add_assignees_title=مشخص کردن کاربران issues.new.clear_assignees=پاک کردن تخصیص issues.new.no_assignees=بدون تخصیص +issues.new.no_reviewers=بدون بازبین گر +issues.new.add_reviewer_title=درخواست بازبینی issues.no_ref=بدون شاخه/برچسب مشخص issues.create=ایجاد مسئله issues.new_label=برچسب جدید @@ -848,6 +899,8 @@ 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 حذف شد` @@ -895,7 +948,9 @@ 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=باز @@ -909,10 +964,13 @@ issues.context.edit=ویرایش issues.context.delete=حذف issues.no_content=هنوز محتوایی ایجاد نشده. issues.close_issue=ببند +issues.pull_merged_at=`انجام ادغام تغییرات %[2]s داخل %[3]s %[4]s` issues.close_comment_issue=ثبت دیدگاه و بستن issues.reopen_issue=بازگشایی issues.reopen_comment_issue=ثبت دیدگاه و بازگشایی issues.create_comment=دیدگاه +issues.closed_at=`%[2]s این موضوع را بست` +issues.reopened_at=`%[2]s این موضوع را دوباره باز کرد` issues.commit_ref_at=`ارجاع این مسئله به کامیت %[2]s` issues.ref_issue_from=` ارجاعات این مسائله %[4] %[2]s` issues.ref_pull_from=` ارجاعات این تقاضای ادغام %[4] %[2]s` @@ -924,6 +982,9 @@ issues.ref_from=`از %[1]` issues.poster=نویسنده issues.collaborator=همكار issues.owner=مالک +issues.re_request_review=درخواست دوباره برای بازبینی +issues.remove_request_review=حذف درخواست بازبینی +issues.remove_request_review_block=امکان حذف درخواست بازبینی وجود ندارد issues.sign_in_require_desc=برای پیوستن به گفتگو، وارد شودید. issues.edit=ویرایش issues.cancel=انصراف @@ -941,6 +1002,8 @@ 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" کلیک کنید` @@ -989,6 +1052,9 @@ issues.due_date=موعد مقرر issues.invalid_due_date_format=موعد مقرر، باید به سبک 'yyyy-mm-dd' باشد. issues.error_modifying_due_date=تغییر موعد مقرر با شکست مواجه شد. issues.error_removing_due_date=حذف موعد مقرر با شکست مواجه شد. +issues.push_commit_1=%d اعمال تغییر اضافه شده است %s +issues.push_commits_n=%d اعمال تغییرات اضافه شده است %s +issues.force_push_codes=`ارسال به سرور اجباری %[1] از %[2]s به%[4]s %[6]s` issues.due_date_form=yyyy-mm-dd issues.due_date_form_add=افزودن موعد مقرر issues.due_date_form_edit=ویرایش @@ -1007,6 +1073,8 @@ issues.dependency.add=اضافه کردن وابستگی… issues.dependency.cancel=انصراف issues.dependency.remove=حذف/ساقط کردن issues.dependency.remove_info=حذف این وابستگی +issues.dependency.added_dependency=`%s یک مخزن جدید اضافه کرد` +issues.dependency.removed_dependency=`%s یک وابستگی را حذف کرد` issues.dependency.issue_closing_blockedby=بستن این تقاضای واکشی وسط موضوعات زیر رد/ مسدود شده است issues.dependency.pr_closing_blockedby=بستن این موضوع وسط موضوعات زیر رد/ مسدود شده است issues.dependency.issue_close_blocks=این مسئله با توجه به موضوعات مطرح شده مسدود شده است @@ -1029,13 +1097,23 @@ issues.review.self.approval=شما نمی‌توانید تقاضای واکشی issues.review.self.rejection=شما نمی‌توانید تقاضا تغییرات تقاضای واکشی خود را تغییر دهید. issues.review.approve=این تغییرات را تایید شدند %s issues.review.comment=بازبینی شدند %s +issues.review.left_comment=یک نظر ثبت کرد issues.review.content.empty=شما می‌بایستی در مورد تقاضای تغییرات اظهار نظر کنید. issues.review.reject=تقاضا شد برای تغییر %s +issues.review.wait=%s درخواست بازبینی کرده است +issues.review.add_review_request=از %s %s درخواست بازبینی کرد +issues.review.remove_review_request=برای %s %s درخواست بازبینی را حذف کرد +issues.review.remove_review_request_self=%s از بازبینی خودداری کرد issues.review.pending=در انتظار issues.review.review=بازبینی issues.review.reviewers=بازبینی‌کنندگان issues.review.show_outdated=نمایش از رده خارج‌ها issues.review.hide_outdated=مخفی کرده از رده خارج ها +issues.review.show_resolved=نمایش حل شده ها +issues.review.hide_resolved=مخفی کردن حل شده ها +issues.review.resolve_conversation=مکالمه را بعنوان حل شده علامت گذاری کردن +issues.review.un_resolve_conversation=مکالمه را بعنوان حل نشده علامت گذاری کردن +issues.review.resolved_by=علامت گذاری این مکالمه بعنوان حل شده issues.assignee.error=به دلیل خطای غیرمنتظره همه تکالیف اضافه نشد. pulls.desc=نمایش تقاضای واکشی ها و بازبینی های کد. @@ -1067,23 +1145,35 @@ pulls.data_broken=این تقاضای واکشی به دلیل از دست رف pulls.files_conflicted=این تقاضای واکشی دارای تغییراتی است که با شاخه هدف تداخل دارد. pulls.is_checking=در حال پردازش تداخل در ادغام می‌باشد. لطفاً لحظاتی بعد امتحان کنید. pulls.required_status_check_failed=برخی بررسی های ضروری موفقیت آمیز نبود. +pulls.required_status_check_missing=برخی بررسی های موردنیاز از قلم افتاده است. pulls.required_status_check_administrator=مثل یک مدیر، ممکن است شما این تقاضای واکشی را مسکوت بگذارید. pulls.blocked_by_approvals=این تقاضای واکشی هنوز به اندازه کافی مورد مورد تایید نیست. %d از %d مورد آن قابل تایید می‌باشد. pulls.blocked_by_rejection=این درخواست واکشی به درخواست یکی از ناظران رسمی دارای تغییرات است. +pulls.blocked_by_outdated_branch=این تقاضای واکشی مسدود شد به این علت که قدیمی است. pulls.can_auto_merge_desc=این تقاضا واکشی می تواند به صورت خودکار ادغام شود. pulls.cannot_auto_merge_desc=این تقاضای واکشی به علت تداخل نمی تواند به صورت خودکار ادغام شود. pulls.cannot_auto_merge_helper=به صورتی دستی ادغام کنید تا مشکل تداخل را حل نمایید. +pulls.num_conflicting_files_1=%d فایل متناقض +pulls.num_conflicting_files_n=%d فایل متناقض +pulls.approve_count_1=%d تایید +pulls.approve_count_n=%d تایید +pulls.reject_count_1=%d درخواست تغییر +pulls.reject_count_n=%d درخواست تغییر +pulls.waiting_count_1=%d منتظر بازبینی +pulls.waiting_count_n=%d منتظر بازبینی pulls.no_merge_desc=این تقاضای واکشی قابل ادغام نیست لذا تمامی گزینه های ادغام مخزن غیر فعال هستند. pulls.no_merge_helper=گزینه های ادغام را در تنظیمات مخزن فعال کنید یا از تقاضای واکشی به صورت دستی ادغام نمایید. pulls.no_merge_wip=این تقاضای واکشی قابل ادغام نیست لذا اکنون به این مخزن درحال پردازش علامت گذاری شده است. +pulls.no_merge_not_ready=این درخواست واکشی آماده ادغام نیست، وضعیت بازبینی را چک کنید. +pulls.no_merge_access=شما مجاز به ادغام این درخواست واکشی نیستید. pulls.merge_pull_request=ادغام تقاضای واکشی pulls.rebase_merge_pull_request=بازگردانی و ادغام pulls.rebase_merge_commit_pull_request=بازگردانی و ادغام (--no-ff) pulls.squash_merge_pull_request=له کردن و ادغام +pulls.require_signed_wont_sign=انشعاب نیازمند تغییرات امضا شده است اما این ادغام امضا نخواهد شد pulls.invalid_merge_option=شما نمی‌توانید از این گزینه برای تقاضای واکشی استفاده کنید. -pulls.merge_conflict=ادغام ناموفق: آنجا یک تداخل در لیست سفید بود: %[1]s
%[2]s
نکته: یک استراتژی دیگر را امتحان کنید -pulls.rebase_conflict=ادغام ناموفق: آنجا یک تداخل در لیست سفید بنیان کامیت بود: %[1]s
%[2]s
%[3]s
نکته: یک استراتژی دیگر را امتحان کنید +; %[2]s
%[3]s
pulls.unrelated_histories=ادغام ناموفق: سر و پایه ادغام یک تاریخ مشترک ندارند. نکته: یک استراتژی متفاوت را امتحان کنید pulls.merge_out_of_date=ادغام ناموفق: در حالی که ادغام را ایجاد می کند ، پایگاه به روز شد. نکته: دوباره امتحان کنید. pulls.open_unmerged_pull_exists=`شما نمی‌توانید یک عملیات را انجام داده یا بازگشایی نمایید لذا (#%d) مورد تقاضای واکشی با ویژگی منحصر به فرد هنوز رسیدگی نشده (معلق) است. ` @@ -1123,6 +1213,9 @@ milestones.filter_sort.most_complete=بیشترین کامل شده milestones.filter_sort.most_issues=بیشترین مسائل milestones.filter_sort.least_issues=کمترین مسائل +signing.wont_sign.headsigned=زمانیکه آخرین تغییرات (head) امضا نشده باشد، ادغام نیز امضا نخواهد شد +signing.wont_sign.commitssigned=زمانیکه تمامی تغییرات مرتبط امضا نشده باشند، ادغام نیز امضا نخواهد شد +signing.wont_sign.approved=تا زمانیکه درخواست دریافت تایید نشده است ادغام امضا نخواهد شد ext_wiki=دانشنامه خارجی ext_wiki.desc=پیوند به یک دانشنامه خارجی. @@ -1278,6 +1371,11 @@ settings.convert_desc=شما می توانید این مخزن قرینه شده settings.convert_notices_1=این عملیات می تواند این مخزن قرینه شده را به یک مخزن معمولی تبدیل نمایید. این عمل بازگشت ناپذیر خواهد بود. settings.convert_confirm=تبدیل مخزن settings.convert_succeed=قرینه به یک مخزن معمولی تبدیل شد. +settings.convert_fork=تبدیل به مخزن عادی +settings.convert_fork_desc=شما می توانید این انشعاب را به یک مخزن عادی تبدیل کنید. این عمل غیر قابل بازگشت می باشد. +settings.convert_fork_notices_1=این عملیات، انشعاب را تبدیل به مخزن عادی خواهد کرد و بازگشت ناپذیر است. +settings.convert_fork_confirm=تبدیل مخزن +settings.convert_fork_succeed=انشعاب به یک مخزن عادی تبدیل شده است. settings.transfer=انتقال مالکیت settings.transfer_desc=انتقال مالکیت این مخزن به کاربر بانی یا سازمانی که شما حق مدیریت در آن دارید. settings.transfer_notices_1=- شما دسترسی خود را نسبت مخزن را از دست میدهید اگر مالکیت آن را به یک کاربری مفرد انتقال دهید. @@ -1311,8 +1409,13 @@ settings.search_user_placeholder=جستجوی کاربر… settings.org_not_allowed_to_be_collaborator=سازمان ها را نمیتوان به عنوان همکار افزود. settings.change_team_access_not_allowed=تغییر دسترسی های تیم برای این مخزن توسط مالک ارگان محدود شده است settings.team_not_in_organization=تیم همانند ارگان برای این مخزن نیست +settings.teams=تیم ها +settings.add_team=افزودن تیم settings.add_team_duplicate=تیم پیش از این مخزن داشته settings.add_team_success=تیم هم‌اکنون به مخزن دسترسی دارد. +settings.search_team=جستجوی تیم… +settings.change_team_permission_tip=دسترسی تیم در صفحه تنظیمات تیم انجام شده و برای هر مخزن نمی تواند تغییر یابد +settings.delete_team_tip=این تیم به تمامی مخازن دسترسی دارد و نمی تواند حذف شود settings.remove_team_success=دسترسی تیم به مخزن حذف شد. settings.add_webhook=اضافه‌کردن Webhook settings.add_webhook.invalid_channel_name=کانال هوک تحت وب نمی‌تواند خالی باشد و نمی‌توانید تنها حاوی این حرف # باشد. @@ -1975,7 +2078,6 @@ config.session_config=پیکربندی نشست ها config.session_provider=تامین کننده نشست config.provider_config=پیکربندی تامین کننده config.cookie_name=نام کوکی -config.enable_set_cookie=فعال سازی تنظیم کردن کوکی config.gc_interval_time=فاصله زمانی GC config.session_life_time=طول عمر نشست config.https_only=فقط HTTPS diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index ae5e5f57823f..767c05f6db84 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -318,7 +318,6 @@ password_special_one=Ainakin yksi erikoismerkki (välimerkki, sulut, lainausmerk enterred_invalid_password=Syöttämäsi salasana oli väärä. user_not_exist=Käyttäjää ei ole olemassa. team_not_exist=Tiimiä ei ole olemassa. -last_org_owner=Et voi poistaa viimeistä käyttäjää organisaation omistajien tiimistä. Jokaisella organisaatiolla on oltava vähintään yksi omistaja. cannot_add_org_to_team=Organisaatiota ei voida lisätä tiimin jäseneksi. invalid_ssh_key=SSH-avaintasi ei voi vahvistaa: %s @@ -441,7 +440,6 @@ add_on=Lisätty valid_until=Vanhenee last_used=Käytetty viimeksi no_activity=Ei viimeaikaista toimintaa - manage_social=Hallitse liitettyjä sosiaalisia tilejä generate_new_token=Luo uusi pääsymerkki @@ -523,8 +521,6 @@ template.issue_labels=Ongelmien tunnisteet need_auth=Kloonauksen valtuutus -migrate_type=Siirtotyyppi -migrate_type_helper=Tämä repo tulee olemaan peili migrate_items=Siirrettävät asiat migrate_items_wiki=Wiki migrate_items_milestones=Merkkipaalut @@ -536,7 +532,6 @@ migrate_repo=Siirrä repo migrate.clone_address=Migraation / Kloonaa URL osoitteesta migrate.permission_denied=Sinun ei sallita tuovan paikallisia repoja. migrate.failed=Siirto epäonnistui: %v -migrate.migrate_items_options=Kun siirryt githubista, syötä käyttäjänimi niin siirtymisasetukset tulevat näkyviin. mirror_from=peilaus alkaen forked_from=forkattu lähteestä @@ -771,6 +766,7 @@ pulls.has_merged=Vetopyyntö on yhdistetty. pulls.can_auto_merge_desc=Tämä pull-pyyntö voidaan yhdistää automaattisesti. pulls.merge_pull_request=Yhdistä Pull-pyyntö +; %[2]s
%[3]s
milestones.new=Uusi merkkipaalu milestones.open_tab=%d avoinna @@ -1249,7 +1245,6 @@ config.session_config=Istunnon asetukset config.session_provider=Istunnon toimittaja config.provider_config=Toimittajan asetukset config.cookie_name=Evästenimi -config.enable_set_cookie=Ota käyttöön aseta eväste config.gc_interval_time=GC aikaväli aika config.session_life_time=Istunnon elinikä config.https_only=Vain HTTPS diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index a2607c928223..167e72c5478d 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -358,7 +358,6 @@ enterred_invalid_owner_name=Le nom du nouveau propriétaire est invalide. enterred_invalid_password=Le mot de passe saisi est incorrect. user_not_exist=Cet utilisateur n'existe pas. team_not_exist=L'équipe n'existe pas. -last_org_owner=Vous ne pouvez pas supprimer le dernier utilisateur de l’équipe « propriétaires ». Il doit y avoir au moins un propriétaire dans chaque équipe. cannot_add_org_to_team=Une organisation ne peut être ajoutée comme membre d'une équipe. invalid_ssh_key=Impossible de vérifier votre clé SSH : %s @@ -484,7 +483,6 @@ gpg_helper=Besoin d'aide ? Consultez le guide Github à propos add_new_key=Ajouter une clé SSH add_new_gpg_key=Ajouter une clé GPG ssh_key_been_used=Cette clef SSH a déjà été ajoutée au serveur. -ssh_key_name_used=Une clef SSH du même nom est déjà associée à votre compte. gpg_key_id_used=Une clef GPG publique avec le même identifiant existe déjà. gpg_no_key_email_found=Cette clef GPG n'est utilisable avec aucune adresse e-mail associée à ce compte. subkeys=Sous-clés @@ -512,7 +510,6 @@ token_state_desc=Ce jeton a été utilisé durant les 7 derniers jours show_openid=Afficher sur mon profil hide_openid=Masquer du profil ssh_disabled=SSH désactivé - manage_social=Gérer les réseaux sociaux associés social_desc=Ces réseaux sociaux sont liés à votre compte Gitea. Veuillez vous assurer que vous les reconnaissez tous car ils peuvent être utilisés pour se connecter à votre compte Gitea. unbind=Dissocier @@ -680,8 +677,6 @@ form.name_reserved=Le dépôt "%s" a un nom réservé. form.name_pattern_not_allowed="%s" n'est pas autorisé dans un nom de dépôt. need_auth=Autorisations de clonage -migrate_type=Type de migration -migrate_type_helper=Ce dépôt sera un miroir migrate_items=Éléments à migrer migrate_items_wiki=Wiki migrate_items_milestones=Jalons @@ -697,7 +692,6 @@ migrate.permission_denied=Vous n'êtes pas autorisé à importer des dépôts lo migrate.invalid_local_path=Chemin local non valide, non existant ou n'étant pas un dossier. migrate.failed=Echec de migration: %v migrate.lfs_mirror_unsupported=La synchronisation des objets LFS n'est pas supportée - veuillez utiliser 'git lfs fetch --all' et 'git lfs push --all' à la place. -migrate.migrate_items_options=Quand vous migrez depuis github, saisissez un nom d'utilisateur et des options de migration seront affichées. migrated_from=Migré de %[2]s migrated_from_fake=Migré de %[1]s migrate.migrating=Migration de %s ... @@ -800,11 +794,8 @@ editor.file_deleting_no_longer_exists=Le fichier en cours de suppression, '%s', editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. Cliquez ici pour voir les changements ou soumettez de nouveau pour les écraser. editor.file_already_exists=Un fichier nommé '%s' existe déjà dans ce dépôt. editor.commit_empty_file_header=Commiter un fichier vide -editor.commit_empty_file_text=Le fichier que vous souhaitez commiter est vide. Continuer ? editor.no_changes_to_show=Il n’y a aucun changement à afficher. -editor.fail_to_update_file=Échec lors de la mise à jour/création du fichier '%s' avec l’erreur : %v editor.push_rejected_no_message=La modification a été rejetée par le serveur sans message. Veuillez vérifier les githooks. -editor.push_rejected=Le changement a été rejeté par le serveur avec le message suivant :
%s
Veuillez vérifier les githooks. editor.add_subdir=Ajouter un dossier… editor.unable_to_upload_files=Échec lors de l'envoie du fichier '%s' avec l’erreur : %v editor.upload_file_is_locked=Le fichier '%s' est verrouillé par %s. @@ -1141,11 +1132,9 @@ pulls.rebase_merge_commit_pull_request=Rebase et Fusion (--no-ff) pulls.squash_merge_pull_request=Squash et fusionner pulls.require_signed_wont_sign=La branche nécessite des révisions signées mais cette fusion ne sera pas signée pulls.invalid_merge_option=Vous ne pouvez pas utiliser cette option de fusion pour cette demande. -pulls.merge_conflict=Échec de la fusion: Il y a eu un conflit de fusion : %[1]s
%[2]s
Indice : Essayez une stratégie différente -pulls.rebase_conflict=Échec de la fusion: Il y a eu un conflit tout en rebasant la révision : %[1]s
%[2]s
%[3]s
Indice : Essayez une autre stratégie +; %[2]s
%[3]s
pulls.unrelated_histories=Échec de la fusion: La tête de fusion et la base ne partagent pas d'historique commun. Indice : Essayez une stratégie différente pulls.merge_out_of_date=Échec de la fusion: La base a été mise à jour en cours de fusion. Indice : Réessayez. -pulls.push_rejected=Fusion échouée : l'envoi a été rejetée avec le message suivant :
%s
Revoir les githooks pour ce dépôt pulls.push_rejected_no_message=Fusion échouée : l'envoi a été rejeté mais il n'y avait pas de message distant.
Revoir les githooks pour ce dépôt pulls.open_unmerged_pull_exists=`Vous ne pouvez pas ré-ouvrir cette demande de fusion car il y a une demande de fusion (#%d) en attente avec des propriétés identiques.` pulls.status_checking=Certains contrôles sont en attente @@ -1363,6 +1352,7 @@ settings.transfer_desc=Transférer ce dépôt à un autre utilisateur ou une org settings.transfer_notices_1=- Vous perdrez l'accès à ce dépôt si vous le transférez à un autre utilisateur. settings.transfer_notices_2=- Vous conserverez l'accès à ce dépôt si vous le transférez à une organisation dont vous êtes (co-)propriétaire. settings.transfer_form_title=Entrez le nom du dépôt pour confirmer : +settings.trust_model.collaboratorcommitter.desc=Les signatures valides des des collaborateurs de ce dépôt seront marquées "de confiance" si elles correspondent à l'expéditeur. Dans le cas contraire, les signatures valides seront marquées "non fiables" si la signature correspond au validateur et "sans correspondance" pour les autres cas. Cela forcera Gitea à être marqué comme le committer sur les commits signés avec le committer réel marqué comme Co-Authored-By: et Co-Committed-By: inclus dans la livraison. La clé par défaut de Gitea doit correspondre à un utilisateur dans la base de données. settings.wiki_delete=Supprimer les données du Wiki settings.wiki_delete_desc=Supprimer les données du wiki d'un dépôt est permanent et ne peut être annulé. settings.wiki_delete_notices_1=- Ceci supprimera de manière permanente et désactivera le wiki de dépôt pour %s. @@ -1897,7 +1887,6 @@ users.prohibit_login=Désactiver la connexion users.is_admin=Est Administrateur users.is_restricted=Est restreint users.allow_git_hook=Autoriser la création de Git Hooks -users.allow_git_hook_tooltip=Les Hooks Git sont exécutés en tant qu'utilisateur système de Gitea et auront le même niveau d'accès que l' hôte users.allow_import_local=Autoriser l'importation de dépôts locaux users.allow_create_organization=Autoriser la création d'organisations users.update_profile=Modifier un compte @@ -2132,7 +2121,6 @@ config.session_config=Configuration de session config.session_provider=Fournisseur de session config.provider_config=Configuration du fournisseur config.cookie_name=Nom du cookie -config.enable_set_cookie=Activer les cookies config.gc_interval_time=Intervals GC config.session_life_time=Durée des sessions config.https_only=HTTPS uniquement diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 9ff69a3a68cf..46b8aa469410 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -344,7 +344,6 @@ enterred_invalid_owner_name=Az új tulajdonos neve érvénytelen. enterred_invalid_password=A megadott jelszó érvénytelen. user_not_exist=A fiók nem létezik. team_not_exist=Ez a csapat nem létezik. -last_org_owner=Nem távolítható el az utolsó felhasználó a "tulajdonosok" csapatból. Minden csapatba legalább egy tulajdonos kell. cannot_add_org_to_team=Egy szervezet nem adható hozzá egy csoporthoz tagként. invalid_ssh_key=Nem tudtuk ellenőrizni az SSH kulcsodat: %s @@ -465,7 +464,6 @@ gpg_helper=Segítség kell? Nézd meg a GitHub GPG add_new_key=SSH kulcs hozzáadása add_new_gpg_key=GPG kulcs hozzáadása ssh_key_been_used=Ezt az SSH kulcsot már hozzáadták a ehhez a szerverhez. -ssh_key_name_used=Egy SSH kulcs már létezik ezzel a névvel a fiókodban. gpg_key_id_used=Ilyen azonosítóval már létezik nyilvános GPG kulcs. gpg_no_key_email_found=Ez a GPG kulcs nem használható egyik email címeddel sem. subkeys=Alkulcsok @@ -493,7 +491,6 @@ token_state_desc=Ez a token volt használva az elmúlt 7 napban show_openid=Megjelenítés a profilon hide_openid=Elrejtés a profilról ssh_disabled=SSH kikapcsolva - manage_social=Kapcsolódó fiókok kezelése unbind=Szétválasztás @@ -633,8 +630,6 @@ form.name_reserved=A tárolónév ('%s') a rendszernek van fenntartva. form.name_pattern_not_allowed='%s' minta nem engedélyezett tárolónévben. need_auth=Hitelesítés másoláshoz -migrate_type=Migráció típusa -migrate_type_helper=Ez a tároló tükörként fog működni migrate_items_wiki=Wiki migrate_items_milestones=Mérföldkövek migrate_items_labels=Címkék @@ -737,7 +732,6 @@ editor.filename_is_a_directory='%s' fájlnév már használva van könyvtárnév editor.file_already_exists=A(z) '%s' nevű fájl már létezik a tárolóban. editor.commit_empty_file_header=Egy üres fájl commitolása editor.no_changes_to_show=Nincsen megjeleníthető változás. -editor.fail_to_update_file=Nem sikerült létrehozni/módosítani a következő fájlt: '%s' A hiba oka: %v editor.add_subdir=Mappa hozzáadása… editor.unable_to_upload_files=Nem sikerült feltölteni a fájlokat a "%s" hiba: %v editor.upload_file_is_locked='%s' fájlt %s zárolta. @@ -966,6 +960,7 @@ pulls.can_auto_merge_desc=Ez az egyesítési kérés automatikusan végrehajthat pulls.merge_pull_request=Egyesítési kérés végrehajtása pulls.rebase_merge_pull_request=Rebase és egyesítés pulls.squash_merge_pull_request=Squash és egyesítés +; %[2]s
%[3]s
pulls.status_checking=Néhány ellenőrzés függőben van pulls.status_checks_success=Minden ellenőrzés sikeres volt @@ -1654,7 +1649,6 @@ config.session_config=Munkamenet Beállítások config.session_provider=Munkamenet Szolgáltató config.provider_config=Szolgáltató Beállítás config.cookie_name=Süti Név -config.enable_set_cookie=Süti Használatának Engedélyezése config.gc_interval_time=GC Intervallum Idő config.session_life_time=Munkamenet Élettartama config.https_only=Csak HTTPS diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index b242568eabf5..1ef3911baf94 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -349,7 +349,6 @@ enterred_invalid_owner_name=Nama pemilik baru salah. enterred_invalid_password=Kata sandi yang Anda masukkan salah. user_not_exist=Pengguna tidak ada. team_not_exist=Tim tidak ada. -last_org_owner=Anda tidak dapat menghapus pengguna terakhir dari tim pemilik. Harus ada setidaknya satu pemilik dalam tim yang diberikan. cannot_add_org_to_team=Sebuah organisasi tidak dapat ditambahkan sebagai anggota tim. invalid_ssh_key=Tidak dapat memverifikasi kunci SSH Anda: %s @@ -469,7 +468,6 @@ gpg_helper=Butuh bantuan? Lihatlah pada petunjuk GitHub
duplikat migrate_items=Ihwal Migrasi migrate_items_wiki=Wiki migrate_repo=Migrasi Repositori @@ -650,7 +645,6 @@ migrate.permission_denied=Anda tidak diizinkan untuk mengimpor repositori lokal. migrate.invalid_local_path=Jalur lokal tidak valid; tidak ada atau bukan direktori. migrate.failed=Migrasi gagal: %v migrate.lfs_mirror_unsupported=Mencerminkan object LFS tidak didukung - gunakan 'git lfs fetch --all' dan 'git lfs push --all'. -migrate.migrate_items_options=Ketika migrasi dari GitHub, masukkan nama pengguna dan opsi migrasi akan ditampilkan. migrated_from=Termigrasi dari %[2]s migrated_from_fake=Termigrasi Dari %[1]s migrate.migrating=Memigrasi dari %s ... @@ -736,7 +730,6 @@ editor.cancel=Membatalkan editor.branch_already_exists=Cabang '%s' sudah ada di repositori ini. editor.file_is_a_symlink='%s' adalah tautan simbolik, yang tidak dapat disunting dalam penyunting web editor.no_changes_to_show=Tidak ada perubahan untuk ditampilkan. -editor.fail_to_update_file=Gagal untuk memperbarui/buat berkas '%s' dengan kesalahan: %v editor.unable_to_upload_files=Gagal untuk mengunggah berkas ke '%s' dengan kesalahan: %v editor.upload_files_to_dir=Unggah berkas ke '%s' @@ -867,6 +860,7 @@ pulls.can_auto_merge_desc=Permintaan tarik ini dapat digabung secara otomatis. pulls.merge_pull_request=Gabungkan Permintaan Tarik pulls.rebase_merge_pull_request=Membuat Ulang Dasar dan Menggabungkan pulls.squash_merge_pull_request=Tindih dan Gabungkan +; %[2]s
%[3]s
milestones.new=Milestone Baru milestones.open_tab=%d Terbuka @@ -1292,7 +1286,6 @@ config.session_config=Sesi konfigurasi config.session_provider=Sesi penyedia config.provider_config=Konfigurasi penyedia config.cookie_name=Nama cookie -config.enable_set_cookie=Pengatur cookie diaktifkan config.gc_interval_time=Waktu interval GC config.session_life_time=Sesi jangka waktu config.https_only=Hanya HTTPS diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 49f3a48b218a..3785f4dac256 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -20,10 +20,13 @@ user_profile_and_more=Profilo ed Impostazioni… signed_in_as=Accesso effettuato come enable_javascript=Il sito funziona meglio con JavaScript. toc=Indice dei contenuti +licenses=Licenze +return_to_gitea=Ritorna a Gitea username=Nome utente email=Indirizzo Email password=Password +access_token=Token di accesso re_type=Reinserisci la password captcha=CAPTCHA twofa=Verifica in due passaggi @@ -51,6 +54,8 @@ new_migrate=Nuova Migrazione new_mirror=Nuovo Mirror new_fork=Nuovo Fork new_org=Nuova organizzazione +new_project=Nuovo progetto +new_project_board=Nuova scheda del progetto manage_org=Gestisci le organizzazioni admin_panel=Amministrazione Sito account_settings=Impostazioni dell'account @@ -71,6 +76,7 @@ issues=Problemi milestones=Milestones cancel=Annulla +save=Salva add=Aggiungi add_all=Aggiungi tutti remove=Rimuovi @@ -83,10 +89,13 @@ loading=Caricamento… error404=La pagina che stai cercando di raggiungere non esiste oppure non sei autorizzato a visualizzarla. [error] +occurred=Si è verificato un errore +report_message=Se sei sicuro che sia un bug di Gitea, cerca il problema su GitHub e apri una nuova segnalazione se necessario. [startpage] app_desc=Un servizio auto-ospitato per Git pronto all'uso install=Facile da installare +install_desc=Semplicemente avvia l'eseguibile per la tua piattaforma. Oppure avvia Gitea con Docker, oppure ottienilo pacchettizzato. platform=Multipiattaforma platform_desc=Gitea funziona ovunque Go possa essere compilato: Windows, macOS, Linux, ARM, etc. Scegli ciò che ami! lightweight=Leggero @@ -163,6 +172,7 @@ openid_signin=Attiva l'accesso OpenID openid_signin_popup=Attiva registrazione utente via OpenID. openid_signup=Attiva OpenID Self-Registration openid_signup_popup=Attiva OpenID-based user self-registration. +enable_captcha=Abilita CAPTCHA per registrazione enable_captcha_popup=Richiedi convalida captcha per i nuovi utenti. require_sign_in_view=Richiedi l'accesso per visualizzare le pagine require_sign_in_view_popup=Limita l'accesso alle pagine agli utenti che hanno eseguito l'accesso. I visitatori visualizzeranno solamente le pagine di accesso e registrazione. @@ -202,8 +212,17 @@ my_orgs=Le mie Organizzazioni my_mirrors=I miei Mirror view_home=Vedi %s search_repos=Trova un repository… +filter=Altro filtri +show_archived=Archiviato +show_both_archived_unarchived=Mostra sia gli archiviati che i non archiviati +show_only_archived=Visualizzazione solo archiviati +show_only_unarchived=Visualizzazione solo non archiviati +show_private=Privato +show_both_private_public=Mostrando sia pubblico che privato +show_only_private=Visualizzazione solo privati +show_only_public=Mostrando solo pubblici issues.in_your_repos=Nei tuoi repository @@ -280,6 +299,8 @@ authorize_title=Vuoi autorizzare "%s" ad accedere al tuo account? authorization_failed=Autorizzazione fallita authorization_failed_desc=L'autorizzazione non è riuscita perché abbiamo rilevato una richiesta non valida. Contatta l'amministratore dell'app che hai provato ad autorizzare. sspi_auth_failed=Autenticazione SSPI fallita +password_pwned=La password che hai scelto è in una lista di password rubate precedentemente esposte in violazioni di dati pubblici. Per favore riprova con una password diversa. +password_pwned_err=Impossibile completare la richiesta a HaveIBeenPwned [mail] activate_account=Per favore attiva il tuo account @@ -334,6 +355,10 @@ lang_select_error=Selezionare una lingua dall'elenco. username_been_taken=Il Nome utente esiste già. repo_name_been_taken=Il nome del repository esiste già. +repository_files_already_exist=File già esistenti per questo repository. Contatta l'amministratore di sistema. +repository_files_already_exist.adopt=I file esistono già per questo repository e possono essere solo Adottati. +repository_files_already_exist.delete=I file esistono già per questo repository. È necessario eliminarli. +repository_files_already_exist.adopt_or_delete=I file esistono già per questo repository. O li Adotti o li Elimini. visit_rate_limit=La visita remota ha segnalato un limite raggiunto. 2fa_auth_required=La visita remota ha richiesto l'autenticazione a due fattori. org_name_been_taken=Il nome della organizzazione esiste già. @@ -352,7 +377,7 @@ enterred_invalid_owner_name=Il nuovo nome del proprietario non è valido. enterred_invalid_password=La password inserita non è corretta. user_not_exist=L'utente non esiste. team_not_exist=Questo team non esiste. -last_org_owner=Non è possibile rimuovere l'ultimo utente del team 'proprietari'. Deve esserci almeno un proprietario in qualsiasi team specificato. +last_org_owner=Non è possibile rimuovere l'ultimo utente dal team 'proprietari'. Ci deve essere almeno un proprietario per un'organizzazione. cannot_add_org_to_team=Un'organizzazione non può essere aggiunto come membro del team. invalid_ssh_key=Impossibile verificare la tua chiave SSH: %s @@ -373,11 +398,13 @@ repositories=Repository activity=Attività pubblica followers=Seguaci starred=Repositories votate +projects=Progetti following=Seguiti follow=Segui unfollow=Non seguire più heatmap.loading=Caricamento della Heatmap… user_bio=Biografia +disabled_public_activity=L'utente ha disabilitato la vista pubblica dell'attività. form.name_reserved=L'username '%s' è riservato. form.name_pattern_not_allowed=Il modello '%s' non è consentito come nome di un utente. @@ -402,6 +429,7 @@ uid=Uid u2f=Chiavi di sicurezza public_profile=Profilo pubblico +biography_placeholder=Raccontaci un po' di te profile_desc=Il tuo indirizzo email sarà utilizzato per le notifiche e altre operazioni. password_username_disabled=Gli utenti non locali non hanno il permesso di cambiare il proprio nome utente. per maggiori dettagli si prega di contattare l'amministratore del sito. full_name=Nome Completo @@ -416,6 +444,9 @@ continue=Continua cancel=Annulla language=Lingua ui=Tema +privacy=Privacy +keep_activity_private=Nascondi l'attività dal profilo +keep_activity_private_popup=Rendi l'attività visibile solo da te e dagli amministratori lookup_avatar_by_mail=Cerca Avatar per indirizzo Email federated_avatar_lookup=Ricerca per avatar federata @@ -477,8 +508,9 @@ ssh_helper= Hai bisogno di aiuto? Dai un'occhiata alla guida di gpg_helper=Hai bisogno di aiuto? Dai un'occhiata alla guida di GitHub riguardo il GPG. add_new_key=Aggiungi Chiave SSH add_new_gpg_key=Aggiungi Chiave GPG +key_content_ssh_placeholder=Comincia con 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', o 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Comincia con '-----BEGIN PGP PUBLIC KEY BLOCK-----' ssh_key_been_used=Questa chiave SSH è già stata aggiunta al server. -ssh_key_name_used=Una chiave SSH con lo stesso nome è stata già aggiunta al tuo account. gpg_key_id_used=Esiste già una chiave GPG pubblica con lo stesso ID. gpg_no_key_email_found=Questa chiave GPG non è utilizzabile con nessun indirizzo email associato al tuo account. subkeys=Sottochiavi @@ -506,7 +538,6 @@ token_state_desc=Questo token è stato utilizzato negli ultimi 7 giorni show_openid=Mostra nel profilo hide_openid=Nascondi dal profilo ssh_disabled=SSH disabilitato - manage_social=Gestisci gli Account Sociali Associati social_desc=Questi account sociali sono collegati al tuo account Gitea. Assicurati di riconoscerli tutti in quanto possono essere usati per effettuare il login con il tuo account Gitea. unbind=Rimuovi il collegamento @@ -519,6 +550,7 @@ new_token_desc=Le applicazioni che utilizzano un token hanno accesso completo al token_name=Nome Token generate_token=Genera Token generate_token_success=Il nuovo token è stato generato. Copia ora in quanto non verrà mostrato nuovamente. +generate_token_name_duplicate=%s è già stato utilizzato come nome dell'applicazione. Si prega di usarne uno nuovo. delete_token=Elimina access_token_deletion=Elimina token di accesso access_token_deletion_desc=Eliminare un token revocherà l'accesso al tuo account alle applicazioni che lo utilizzano. Continuare? @@ -651,7 +683,23 @@ pick_reaction=Scegli la tua reazione reactions_more=e %d più unit_disabled=L'amministratore ha disabilitato questa sezione del repository. language_other=Altro - +adopt_search=Inserisci il nome utente per cercare i repository non adottati... (lascia vuoto per trovare tutti) +adopt_preexisting_label=Adotta File +adopt_preexisting=Adottare file preesistenti +adopt_preexisting_content=Crea repository da %s +adopt_preexisting_success=File adottati e repository creati da %s +delete_preexisting_label=Elimina +delete_preexisting=Elimina file preesistenti +delete_preexisting_content=Elimina file in %s +delete_preexisting_success=Eliminato file non adottati in %s + +desc.private=Privato +desc.public=Pubblico +desc.private_template=Modello privato +desc.public_template=Modello +desc.internal=Interno +desc.internal_template=Template interno +desc.archived=Archiviato template.items=Elementi del modello template.git_content=Contenuto di Git (Ramo predefinito) @@ -673,14 +721,17 @@ form.name_reserved=Il nome repository '%s' è riservato. form.name_pattern_not_allowed=Il modello '%s' non è consentito come nome di un repository. need_auth=Autorizzazione clone -migrate_type=Tipo di migrazione -migrate_type_helper=Questo repository sarà un mirror +migrate_options=Opzioni di migrazione +migrate_service=Servizio migrazione +migrate_options_mirror_helper=Questo repository sarà un mirror +migrate_options_mirror_disabled=L'amministratore del sito ha disabilitato i nuovi mirror. migrate_items=Elementi di migrazione migrate_items_wiki=Wiki migrate_items_milestones=Milestone migrate_items_labels=Etichette migrate_items_issues=Issues migrate_items_pullrequests=Pull request +migrate_items_merge_requests=Richieste di Merge migrate_items_releases=Rilasci migrate_repo=Migra Repository migrate.clone_address=Migra / Clona da URL @@ -690,17 +741,23 @@ migrate.permission_denied=Non è consentito importare repository locali. migrate.invalid_local_path=Percorso locale non valido, non esiste o non è una cartella. migrate.failed=Migrazione non riuscita: %v migrate.lfs_mirror_unsupported=La duplicazione di oggetti LFS non è supportata - usa ' git lfs fetch --all e 'git lfs push --all' invece. -migrate.migrate_items_options=Quando si migra da github, inserisci un nome utente e verranno mostrate opzioni di migrazione. +migrate.migrate_items_options=Il Token di accesso è richiesto per migrare elementi aggiuntivi migrated_from=Migrato da %[2]s migrated_from_fake=Migrato da %[1]s +migrate.migrate=Migra da %s migrate.migrating=Migrazione da %s... migrate.migrating_failed=Migrazione da %s fallita. +migrate.github.description=Migrazione dati da Github.com o Github Enterprise. +migrate.git.description=Migrazione o Mirroring dei dati git dai servizi Git +migrate.gitlab.description=Migrazione dei dati dal server gitlab di GitLab.com o Self-Hosted. mirror_from=mirror da forked_from=forkato da generated_from=generato da fork_from_self=Non puoi effettuare il fork del tuo stesso repository. fork_guest_user=Accedi per effettuare il fork di questo repository. +watch_guest_user=Accedi per seguire questo repository. +star_guest_user=Accedi per marcare in questo repository. copy_link=Copia copy_link_success=Il link è stato copiato copy_link_error=Premere ⌘-C o Ctrl-C per copiare @@ -723,12 +780,15 @@ code=Codice code.desc=Accedi al codice sorgente, file, commits e branches. branch=Ramo (Branch) tree=Albero (Tree) +clear_ref=`Cancella il riferimento corrente` filter_branch_and_tag=Filtra per branch o tag branches=Rami (Branch) tags=Tag issues=Problemi pulls=Pull Requests +project_board=Progetti labels=Etichette +org_labels_desc=Etichette a livello di organizzazione che possono essere utilizzate con tutti i repository sotto questa organizzazione org_labels_desc_manage=gestisci milestones=Traguardi @@ -745,6 +805,8 @@ audio_not_supported_in_browser=Il tuo browser non supporta il tag "video" di HTM stored_lfs=Memorizzati con Git LFS symbolic_link=Link Simbolico commit_graph=Grafico dei commit +commit_graph.monochrome=Mono +commit_graph.color=Colore blame=Blame normal_view=Vista normale line=riga @@ -792,11 +854,9 @@ editor.file_deleting_no_longer_exists=Il file che si sta per eliminare, '%s', no editor.file_changed_while_editing=I contenuti di questo file hanno subito dei cambiamenti da quando hai iniziato la modifica. Clicca qui per visualizzarli o Committa nuovamente i Cambiamenti per sovrascriverli. editor.file_already_exists=Un file di nome '%s' esiste già in questo repository. editor.commit_empty_file_header=Commit di un file vuoto -editor.commit_empty_file_text=Il file di cui stai facendo un commit è vuoto. Procedere? +editor.commit_empty_file_text=Il file che stai per effettuare il commit è vuoto. Procedere? editor.no_changes_to_show=Non ci sono cambiamenti da mostrare. -editor.fail_to_update_file=Errore durante l'aggiornamento/ creazione del file '%s' con errore: %v editor.push_rejected_no_message=La modifica è stata rifiutata dal server senza un messaggio. Controlla githooks. -editor.push_rejected=La modifica è stata rifiutata dal server con il seguente messaggio:
%s
. Perfavore controlla githooks. editor.add_subdir=Aggiungi una directory… editor.unable_to_upload_files=Impossibile caricare i file su '%s' con errore:%v editor.upload_file_is_locked=Il file '%s' è bloccato da %s. @@ -826,10 +886,40 @@ commits.gpg_key_id=ID Chiave GPG ext_issues=Issue esterne ext_issues.desc=Collegamento al puntatore di una issue esterna. +projects=Progetti +projects.desc=Gestisci problemi e fai il pull nelle schede di progetto. +projects.create=Crea un progetto +projects.title=Titolo +projects.new=Nuovo progetto +projects.new_subheader=Coordina, traccia e aggiorna il tuo lavoro in un unico posto, quindi i progetti rimangono trasparenti e in programma. +projects.create_success=La milestone '%s' è stata creata. +projects.deletion=Elimina progetto +projects.deletion_desc=Eliminare un progetto lo rimuove fra tutte le issue relative. Continuare? +projects.deletion_success=Il progetto è stato cancellato. +projects.edit=Modifica progetto +projects.edit_subheader=I progetti organizzano i problemi e monitorano i progressi. +projects.modify=Aggiorna progetto +projects.edit_success=Il progetto '%s' è stato aggiornato. +projects.type.none=Nessuno +projects.type.basic_kanban=Basic Kanban +projects.type.bug_triage=Bug Triage +projects.template.desc=Template di progetto +projects.template.desc_helper=Seleziona un modello di progetto per iniziare +projects.type.uncategorized=Senza categoria +projects.board.edit=Modifica scheda +projects.board.edit_title=Nuovo Nome Della Scheda +projects.board.new_title=Nuovo Nome Della Scheda +projects.board.new_submit=Invia +projects.board.new=Nuova Scheda +projects.board.delete=Elimina Scheda +projects.board.deletion_desc=L'eliminazione di una scheda di progetto sposta tutti i problemi correlati a 'Uncategorized'. Continuare? +projects.open=Apri +projects.close=Chiudi issues.desc=Organizza le segnalazioni di bug, attività e pietre miliari. issues.filter_assignees=Filtra assegnatario issues.filter_milestones=Filtra traguardo +issues.filter_projects=Filtra Progetti issues.filter_labels=Filtra etichetta issues.filter_reviewers=Filtra revisore issues.new=Nuovo Problema @@ -838,6 +928,12 @@ issues.new.labels=Etichette issues.new.add_labels_title=Applica etichette issues.new.no_label=Nessuna etichetta issues.new.clear_labels=Pulisci le etichette +issues.new.projects=Progetti +issues.new.add_project_title=Imposta Progetto +issues.new.clear_projects=Cancella progetti +issues.new.no_projects=Nessun progetto +issues.new.open_projects=Apri Progetti +issues.new.closed_projects=Progetti chiusi issues.new.no_items=Nessun elemento issues.new.milestone=Traguardo issues.new.add_milestone_title=Imposta traguardo @@ -851,6 +947,9 @@ issues.new.clear_assignees=Cancella assegnatari issues.new.no_assignees=Nessuna assegnatario issues.new.no_reviewers=Nessun revisore issues.new.add_reviewer_title=Richiedi revisione +issues.choose.get_started=Inizia +issues.choose.blank=Default +issues.choose.blank_about=Crea un problema dal modello predefinito. issues.no_ref=Nessun Branch/Tag specificato issues.create=Crea Problema issues.new_label=Nuova etichetta @@ -862,10 +961,16 @@ issues.label_templates.info=Non esistono etichette. Crea una etichetta con 'Nuov issues.label_templates.helper=Scegli un set di etichette issues.label_templates.use=Usa Set Etichette issues.label_templates.fail_to_load_file=Impossibile caricare il file template di etichetta '%s': %v +issues.add_label_at=aggiunta l'etichetta
%s
%s +issues.remove_label_at=rimossa l'etichetta
%s
%s issues.add_milestone_at=`aggiunta alle pietre miliari %s %s` +issues.add_project_at=`aggiunto questo al progetto %s %s` issues.change_milestone_at=`pietra miliare modificata da %s a %s %s` +issues.change_project_at=`ha modificato il progetto da %s a %s %s` issues.remove_milestone_at=`rimossa dalle pietre miliari %s %s` +issues.remove_project_at=`rimosso questo dal progetto %s %s` issues.deleted_milestone='(rimosso)' +issues.deleted_project=`(eliminato)` issues.self_assign_at=`%s auto-assegnato` issues.add_assignee_at=`è stato assegnato da %s %s` issues.remove_assignee_at=`è stato rimosso da %s %s` @@ -909,7 +1014,9 @@ issues.action_assignee_no_select=Nessun assegnatario issues.opened_by=aperto %[1]s da %[3]s pulls.merged_by=mergiato %[1]s da %[3]s pulls.merged_by_fake=mergiato %[1]s da %[2]s +issues.closed_by=del %[3]s chiuso %[1]s issues.opened_by_fake=aperto %[1]s da %[2]s +issues.closed_by_fake=della %[2]s chiusa %[1]s issues.previous=Pagina precedente issues.next=Pagina successiva issues.open_title=Aperto @@ -923,10 +1030,13 @@ issues.context.edit=Modifica issues.context.delete=Elimina issues.no_content=Non ci sono ancora contenuti. issues.close_issue=Chiudi +issues.pull_merged_at=`merged commit %[2]s in %[3]s %[4]s` issues.close_comment_issue=Commenta e Chiudi issues.reopen_issue=Riapri issues.reopen_comment_issue=Commenta e Riapri issues.create_comment=Commento +issues.closed_at=`chiuso questo probleam %[2]s` +issues.reopened_at=`riaperto questo problema %[2]s` issues.commit_ref_at=`ha fatto riferimento a questa issue dal commit %[2]s` issues.ref_issue_from=`ha fatto riferimento a questo problema %[4]s %[2]s` issues.ref_pull_from=`ha fatto riferimento a questa pull request %[4]s %[2]s` @@ -938,7 +1048,10 @@ issues.ref_from=`da %[1]s` issues.poster=Autore issues.collaborator=Collaboratori issues.owner=Proprietario +issues.re_request_review=Revisione ri-richiesta +issues.is_stale=Ci sono stati cambiamenti a questa PR da questa revisione issues.remove_request_review=Elimina richiesta revisione +issues.remove_request_review_block=Impossibile rimuovere la richiesta di revisione issues.sign_in_require_desc=Effettua l'accesso per partecipare alla conversazione. issues.edit=Modifica issues.cancel=Annulla @@ -1006,6 +1119,9 @@ issues.due_date=Data di scadenza issues.invalid_due_date_format=Il formato della data di scadenza deve essere 'yyyy-mm-dd'. issues.error_modifying_due_date=Impossibile modificare la data di scadenza. issues.error_removing_due_date=Impossibile rimuovere la data di scadenza. +issues.push_commit_1=aggiunto %d commit %s +issues.push_commits_n=aggiunto %d commit %s +issues.force_push_codes=`force-pushed %[1]s da %[2]s a %[4]s %[6]s` issues.due_date_form=yyyy-mm-dd issues.due_date_form_add=Aggiungi data di scadenza issues.due_date_form_edit=Modifica @@ -1024,6 +1140,8 @@ issues.dependency.add=Aggiungi dipendenza… issues.dependency.cancel=Annulla issues.dependency.remove=Rimuovi issues.dependency.remove_info=Rimuovi questa dipendenza +issues.dependency.added_dependency=`ha aggiunto una nuova dipendenza %s` +issues.dependency.removed_dependency=`ha rimosso una dipendenza %s` issues.dependency.issue_closing_blockedby=La chiusura di questa richiesta pull è bloccata per i seguenti problemi issues.dependency.pr_closing_blockedby=La chiusura di questo problema è bloccata per i seguenti problemi issues.dependency.issue_close_blocks=Questo problema impedisce la chiusura dei seguenti problemi @@ -1049,6 +1167,10 @@ issues.review.comment=revisionato %s issues.review.left_comment=lascia un commento issues.review.content.empty=Devi lasciare un commento che indichi la modifica richiesta. issues.review.reject=richieste modifiche %s +issues.review.wait=è stato richiesto per la revisione %s +issues.review.add_review_request=recensione richiesta da %s %s +issues.review.remove_review_request=ha rimosso la richiesta di revisione per %s %s +issues.review.remove_review_request_self=ha rifiutato di rivedere %s issues.review.pending=In sospeso issues.review.review=Revisiona issues.review.reviewers=Revisori @@ -1057,6 +1179,8 @@ issues.review.hide_outdated=Nascondere obsoleti issues.review.show_resolved=Mostra risolti issues.review.hide_resolved=Nascondi risolte issues.review.resolve_conversation=Risolvi la conversazione +issues.review.un_resolve_conversation=Segnala la conversazione come non risolta +issues.review.resolved_by=ha contrassegnato questa conversazione come risolta issues.assignee.error=Non tutte le assegnazioni sono state aggiunte a causa di un errore imprevisto. pulls.desc=Attiva pull request e revisioni di codice. @@ -1092,6 +1216,7 @@ pulls.required_status_check_missing=Mancano alcuni controlli richiesti. pulls.required_status_check_administrator=Come amministratore, puoi ancora unire questa pull request. pulls.blocked_by_approvals=La richiesta Pull non ha abbastanza approvazioni. %d di %d approvazioni concesse. pulls.blocked_by_rejection=Questa Pull Request ha delle modifiche richieste da un revisore. +pulls.blocked_by_outdated_branch=Questa Pull Request è bloccata perché obsoleta. pulls.can_auto_merge_desc=La pull request può essere unita automaticamente. pulls.cannot_auto_merge_desc=Questa pull request non può essere unita automaticamente a causa di conflitti. pulls.cannot_auto_merge_helper=Unire manualmente per risolvere i conflitti. @@ -1115,16 +1240,18 @@ pulls.rebase_merge_commit_pull_request=Rebase e Merge (--no-ff) pulls.squash_merge_pull_request=Fai squash e unisci pulls.require_signed_wont_sign=Il branch richiede commit firmati ma questo merge non verrà firmato pulls.invalid_merge_option=Non puoi utilizzare questa opzione di merge per questa pull request. -pulls.merge_conflict=Unione fallita: C'è stato un conflitto durante l'unione: %[1]s
%[2]s
Suggerimento: prova una strategia diversa -pulls.rebase_conflict=Unione fallita: Si é verificato un conflitto durante il rebase: %[1]s
%[2]s
%[3]s
Suggerimento: prova una strategia diversa +; %[2]s
%[3]s
pulls.unrelated_histories=Unione fallita: gli Head del ramo da unire e la base non condividono una storia cronologica in comune. Suggerimento: prova una strategia diversa pulls.merge_out_of_date=Unione fallita: Durante la generazione del merge, la base è stata aggiornata. Suggerimento: Riprova. -pulls.push_rejected=Merge Fallito: Il push è stato rifiutato con il seguente messaggio:
%s
. Revisiona i githooks per questo repository pulls.push_rejected_no_message=Merge Fallito: Il push è stato rifiutato ma non ci sono messaggi remoti.
Revisiona i githooks per questo repository pulls.open_unmerged_pull_exists=`Non è possibile riaprire questa pull request perché ne esiste un'altra (#%d) con proprietà identiche.` pulls.status_checking=Alcuni controlli sono in sospeso pulls.status_checks_success=Tutti i controlli sono stati effettuati con successo +pulls.status_checks_warning=Alcuni controlli hanno segnalato avvertimenti pulls.status_checks_failure=Alcuni controlli sono falliti +pulls.status_checks_error=Alcuni controlli hanno segnalato errori +pulls.status_checks_requested=Richiesto +pulls.status_checks_details=Dettagli pulls.update_branch=Aggiorna branch pulls.update_branch_success=Brench aggiornato con successo pulls.update_not_allowed=Non sei abilitato ad aggiornare il branch @@ -1134,6 +1261,7 @@ milestones.new=Nuova Milestone milestones.open_tab=%d Aperti milestones.close_tab=%d Chiusi milestones.closed=Chiuso %s +milestones.update_ago=Aggiornato %s fa milestones.no_due_date=Nessuna data di scadenza milestones.open=Apri milestones.close=Chiudi @@ -1173,6 +1301,7 @@ signing.wont_sign.basesigned=Il merge non verrà firmato poiché il commit di ba signing.wont_sign.headsigned=Questo merge non sarà firmato poiché il commit head non è firmato signing.wont_sign.commitssigned=Questo merge non sarà firmato poiché i commit associati non non sono firmati signing.wont_sign.approved=Il merge non sarà firmato poiché il PR non è approvato +signing.wont_sign.not_signed_in=Non hai effettuato l'accesso ext_wiki=Wiki esterna ext_wiki.desc=Collegamento a una wiki esterna. @@ -1328,11 +1457,24 @@ settings.convert_desc=È possibile convertire questo mirror in un repository reg settings.convert_notices_1=- Questa operazione convertirà questo mirror in una repository regolare e non potrà essere annullata. settings.convert_confirm=Converti Repository settings.convert_succeed=Il mirror è stato convertito in un repository regolare. +settings.convert_fork=Converti in un repository regolare +settings.convert_fork_desc=Puoi convertire questo fork in un normale repository. Questo non può essere annullato. +settings.convert_fork_notices_1=Questa operazione convertirà il fork in un normale repository e non può essere annullata. +settings.convert_fork_confirm=Converti Repository +settings.convert_fork_succeed=Il fork è stato convertito in un repository regolare. settings.transfer=Trasferisci proprietà settings.transfer_desc=Trasferisci questo repository a un altro utente o a un'organizzazione nella quale hai diritti d'amministratore. settings.transfer_notices_1=-Si perderà l'accesso al repository se lo si trasferisce ad un utente singolo. settings.transfer_notices_2=-Si manterrà l'accesso al repository se si trasferisce in un'organizzazione che possiedi (o condividi con qualcun'altro). settings.transfer_form_title=Inserisci il nome del repository come conferma: +settings.signing_settings=Impostazioni Verifica Firma +settings.trust_model=Modello di Fiducia per la Firma +settings.trust_model.default=Modello Di Fiducia Predefinito +settings.trust_model.default.desc=Usa il modello di trust del repository predefinito per questa installazione. +settings.trust_model.collaborator=Collaboratore +settings.trust_model.collaborator.long=Collaboratore: Firme di fiducia da parte dei collaboratori +settings.trust_model.collaborator.desc=Le firme valide da parte dei collaboratori di questo repository saranno contrassegnate con "trusted" (sia che corrispondano al committer o meno). Altrimenti, le firme valide saranno contrassegnate con "untrusted" se la firma corrisponde al committer e "unmatched" se non. +settings.trust_model.committer=Committer settings.wiki_delete=Elimina dati Wiki settings.wiki_delete_desc=L'eliminazione dei dati della wiki del repository è permanente e non può essere annullata. settings.wiki_delete_notices_1=-Questa operazione eliminerà permanentemente e disabiliterà la wiki repository per %s. @@ -1361,8 +1503,11 @@ settings.search_user_placeholder=Ricerca utente… settings.org_not_allowed_to_be_collaborator=Le organizzazioni non possono essere aggiunte come un collaboratore. settings.change_team_access_not_allowed=La modifica dell'accesso al team per il repository è stato limitato al solo proprietario dell'organizzazione settings.team_not_in_organization=Il team non è nella stessa organizzazione del repository +settings.teams=Gruppi +settings.add_team=Aggiungi Squadra settings.add_team_duplicate=Il team ha già il repository settings.add_team_success=Il team ha ora accesso al repository. +settings.search_team=Cerca Squadra… settings.remove_team_success=L'accesso del team al repository è stato rimosso. settings.add_webhook=Aggiungi Webhook settings.add_webhook.invalid_channel_name=Il canale Webhook non può essere vuoto e contenere solo un # carattere. @@ -1472,6 +1617,7 @@ settings.protect_enable_push=Abilita push settings.protect_enable_push_desc=Chiunque con accesso in scrittura sarà autorizzato a pushare su questo ramo (ma non forzare il push). settings.protect_whitelist_committers=Lista bianch push ristretti settings.protect_whitelist_committers_desc=Solo gli utenti o i team nella whitelist potranno pushare su questo ramo (ma non forzare il push). +settings.protect_whitelist_deploy_keys=Chiavi di deploy in whitelist con permessi di scrittura per il push. settings.protect_whitelist_users=Utenti nella whitelist per pushare: settings.protect_whitelist_search_users=Cerca utenti… settings.protect_whitelist_teams=Team nella whitelist per pushare: @@ -1506,6 +1652,7 @@ settings.edit_protected_branch=Modifica settings.protected_branch_required_approvals_min=Le autorizzazioni richieste non possono essere negative. settings.bot_token=Token del Bot settings.chat_id=ID chat +settings.matrix.homeserver_url=URL Homeserver settings.matrix.room_id=ID della stanza settings.matrix.access_token=Token di accesso settings.matrix.message_type=Tipo di messaggio @@ -1586,6 +1733,7 @@ diff.review.placeholder=Revisione commento diff.review.comment=Commentare diff.review.approve=Approva diff.review.reject=Richiedi cambiamenti +diff.committed_by=committato da releases.desc=Tenere traccia di versioni e download del progetto. release.releases=Rilasci @@ -1683,7 +1831,9 @@ settings.repoadminchangeteam=L'amministratore del repository può aggiungere e r settings.visibility=Visibilità settings.visibility.public=Pubblico settings.visibility.limited=Limitato (Visibile solo agli utenti registrati) +settings.visibility.limited_shortname=Limitato settings.visibility.private=Privato (Visibile solo ai membri dell'organizzazione) +settings.visibility.private_shortname=Privato settings.update_settings=Aggiorna Impostazioni settings.update_setting_success=Le impostazioni dell'organizzazione sono state aggiornate. @@ -1778,9 +1928,27 @@ dashboard.operation_switch=Cambia dashboard.operation_run=Esegui dashboard.clean_unbind_oauth=Elimina connessione OAuth slegate dashboard.clean_unbind_oauth_success=Tutte le connessione OAuth slegate sono state eliminate. +dashboard.task.started=Compito iniziato: %[1]s +dashboard.task.process=Compito: %[1]s +dashboard.task.cancelled=Compito: %[1]s annullato: %[3]s +dashboard.task.error=Errore in Attività: %[1]s: %[3]s +dashboard.task.finished=Compito: %[1]s iniziato da %[2]s ha finito +dashboard.task.unknown=Attività sconosciuta: %[1]s +dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %s cancellato: %[3]s +dashboard.cron.error=Errore in Cron: %s: %[3]s +dashboard.cron.finished=Cron: %[1]s ha finito +dashboard.delete_inactive_accounts=Elimina tutti gli account non attivati +dashboard.delete_inactive_accounts.started=Attività di eliminazione degli account non attivati iniziata. dashboard.delete_repo_archives=Elimina tutti gli archivi dei repository +dashboard.delete_repo_archives.started=Attività di eliminazione degli archivi del repository iniziata. dashboard.delete_missing_repos=Elimina tutti i repository mancanti dei loro file Git +dashboard.delete_missing_repos.started=Elimina tutti i repository mancanti dei loro file Git. dashboard.delete_generated_repository_avatars=Elimina gli avatar generati nelle repository +dashboard.update_mirrors=Aggiorna Mirror +dashboard.repo_health_check=Controlla integrità di tutti i repository +dashboard.archive_cleanup=Elimina vecchi archivi del repository +dashboard.deleted_branches_cleanup=Pulisci branch eliminati dashboard.git_gc_repos=Esegui la garbage collection su tutti i repository dashboard.resync_all_hooks=Sincronizza nuovamente gli hook di pre-ricezione, di aggiornamento e di post-ricezione di tutti i repository. dashboard.reinit_missing_repos=Reinizializza tutti i repository Git mancanti per i quali esistono cambiamenti registrati esistenti @@ -1822,6 +1990,7 @@ users.full_name=Nome Completo users.activated=Attivato users.admin=Amministratore users.restricted=Limitato +users.2fa=2FA users.repos=Repo users.created=Creato users.last_login=Ultimo accesso @@ -1842,6 +2011,7 @@ users.prohibit_login=Disattiva login users.is_admin=È amministratore users.is_restricted=È limitato users.allow_git_hook=Può creare Git Hook +users.allow_git_hook_tooltip=Git Hooks sono eseguiti come l'utente OS che esegue Gitea e avrà lo stesso livello di accesso host. Di conseguenza, gli utenti con questo speciale privilegio Git Hook possono accedere e modificare tutti i repository Gitea e il database utilizzato da Gitea. Di conseguenza sono anche in grado di ottenere privilegi di amministratore Gitea. users.allow_import_local=Può importare repository locali users.allow_create_organization=Può creare organizzazioni users.update_profile=Aggiorna account utente @@ -1917,6 +2087,12 @@ auths.search_page_size=Dimensioni pagina auths.filter=Fitro utente auths.admin_filter=Filtro Amministratore auths.restricted_filter=Filtro riservato +auths.restricted_filter_helper=Lasciare vuoto per non impostare alcun utente come limitato. Utilizzare un asterisco ('*') per impostare tutti gli utenti che non corrispondono al filtro amministratore. +auths.verify_group_membership=Verifica l'appartenenza al gruppo in LDAP +auths.group_search_base=Ricerca Gruppo Base DN +auths.valid_groups_filter=Filtro Gruppi Valido +auths.group_attribute_list_users=Gruppo Attributo Contenente Elenco Utenti +auths.user_attribute_in_group=Attributo Utente Elencato nel Gruppo auths.ms_ad_sa=Attributi di ricerca AD MS auths.smtp_auth=Tipo di autenticazione SMTP auths.smtphost=Host SMTP @@ -2054,6 +2230,7 @@ config.mailer_user=Utente config.mailer_use_sendmail=Utilizza Sendmail config.mailer_sendmail_path=Percorso Sendmail config.mailer_sendmail_args=Argomenti aggiuntivi per Sendmail +config.test_email_placeholder=Email (es. test@example.com) config.send_test_mail=Invia email di prova config.test_mail_failed=Impossibile inviare mail di prova a '%s': %v config.test_mail_sent=Una mail di prova è stata inviata a '%s'. @@ -2071,7 +2248,6 @@ config.session_config=Configurazione Sessione config.session_provider=Fornitore Sessione config.provider_config=Impostazioni Provider config.cookie_name=Nome del Cookie -config.enable_set_cookie=Abilita Uso dei Cookie config.gc_interval_time=Intervallo di tempo della GC config.session_life_time=Durata Sessione config.https_only=Solo HTTPS @@ -2179,6 +2355,7 @@ notices.delete_selected=Elimina selezionati notices.delete_all=Elimina tutti gli avvisi notices.type=Tipo notices.type_1=Repository +notices.type_2=Attività notices.desc=Descrizione notices.op=Op. notices.delete_success=Gli avvisi di sistema sono stati eliminati. @@ -2200,6 +2377,7 @@ transfer_repo=repository %s trasferito in %s push_tag=pushato il tag %[2]s to %[3]s delete_tag=tag eliminato %[2]s da %[3]s delete_branch=branch eliminato %[2]s da %[3]s +compare_branch=Confronta compare_commits=Confronta %d commits compare_commits_general=Confronta commit mirror_sync_push=commit sincronizzati a %[3]s a %[4]s dal mirror @@ -2207,6 +2385,7 @@ mirror_sync_create=sincronizzato il nuovo riferimento %[2]s< mirror_sync_delete=riferimento sincronizzato ed eliminato %[2]s a %[3]s dal mirror approve_pull_request=`approvato %s#%[2]s` reject_pull_request=`modifiche suggerite per %s#%[2]s` +publish_release=`rilasciata tag %[4]s a %[3]s` [tool] ago=%s fa diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 79b238e92a07..15c2521e8564 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -21,10 +21,12 @@ signed_in_as=サインイン済み enable_javascript=このサイトはJavaScriptを使用しています toc=目次 licenses=ライセンス +return_to_gitea=Giteaに戻る username=ユーザー名 email=メールアドレス password=パスワード +access_token=アクセストークン re_type=パスワードを再入力 captcha=CAPTCHA twofa=2要素認証 @@ -297,6 +299,8 @@ authorize_title="%s"にあなたのアカウントへのアクセスを許可し authorization_failed=認可失敗 authorization_failed_desc=不正なリクエストであったため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。 sspi_auth_failed=SSPI認証に失敗しました +password_pwned=あなたが選択したパスワードは、過去の情報漏洩事件で流出した盗まれたパスワードのリストに含まれています。 別のパスワードでもう一度試してください。 +password_pwned_err=HaveIBeenPwnedへのリクエストを完了できませんでした [mail] activate_account=あなたのアカウントをアクティベートしてください。 @@ -351,6 +355,10 @@ lang_select_error=言語をリストから選択してください。 username_been_taken=ユーザー名が既に使用されています。 repo_name_been_taken=リポジトリ名が既に使用されています。 +repository_files_already_exist=このリポジトリのファイルはすでに存在します。システム管理者に問い合わせてください。 +repository_files_already_exist.adopt=このリポジトリのファイルはすでに存在しており、それらを登録することしかできません。 +repository_files_already_exist.delete=このリポジトリのファイルはすでに存在しています。 それらを削除する必要があります。 +repository_files_already_exist.adopt_or_delete=このリポジトリのファイルはすでに存在しています。 それらを登録するか削除してください。 visit_rate_limit=相手側でアクセス数制限されています。 2fa_auth_required=相手側へのアクセスに2要素認証が必要です。 org_name_been_taken=組織名が既に使用されています。 @@ -369,11 +377,12 @@ enterred_invalid_owner_name=新しいオーナーの名前が正しくありま enterred_invalid_password=入力されたパスワードが間違っています。 user_not_exist=指定されたユーザーは存在しません。 team_not_exist=チームが存在していません。 -last_org_owner='Owners'チームから最後のユーザーを削除することはできません。特定のチームには少なくとも一人のオーナーが必要です。 +last_org_owner='Owners'チームから最後のユーザーを削除することはできません。ひとつの組織には少なくとも一人のオーナーが必要です。 cannot_add_org_to_team=組織はチームメンバーとして追加できません。 invalid_ssh_key=SSHキーが確認できません: %s invalid_gpg_key=GPGキーが確認できません: %s +invalid_ssh_principal=無効なプリンシパル: %s unable_verify_ssh_key=SSHキーが確認できません。間違いが無いか、よく確認してください。 auth_failed=認証に失敗しました: %v @@ -421,6 +430,7 @@ uid=Uid u2f=セキュリティキー public_profile=公開プロフィール +biography_placeholder=自己紹介を少しだけ profile_desc=メールアドレスは、通知やその他の操作で使用されます。 password_username_disabled=非ローカルユーザーのユーザー名は変更できません。詳細はサイト管理者にお問い合わせください。 full_name=フルネーム @@ -491,9 +501,11 @@ keep_email_private_popup=あなたのメールアドレスは他のユーザー openid_desc=OpenIDを使うと外部プロバイダーに認証を委任することができます。 manage_ssh_keys=SSHキーの管理 +manage_ssh_principals=SSH証明書プリンシパルの管理 manage_gpg_keys=GPGキーの管理 add_key=キーを追加 ssh_desc=あなたのアカウントに関連付けられているSSH公開鍵です。 対応する秘密鍵で、あなたのリポジトリへのフルアクセスが可能です。 +principal_desc=これらのSSH証明書プリンシパルがあなたのアカウントに関連付けられており、あなたのリポジトリへのフルアクセスが許可されています。 gpg_desc=あなたのアカウントに関連付けられているGPG公開鍵です。 これらの鍵でコミットが検証できるよう、秘密鍵は安全に保管してください。 ssh_helper=ヘルプが必要ですか? GitHubのガイドをご覧ください: SSHキーの作成、SSHを使う際によくある問題 gpg_helper=ヘルプが必要ですか? GitHubのガイドをご覧ください: GPGについて @@ -501,23 +513,30 @@ add_new_key=SSHキーの追加 add_new_gpg_key=GPGキーの追加 key_content_ssh_placeholder=先頭は次のいずれか 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521' key_content_gpg_placeholder=先頭は '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal=プリンシパルを追加 ssh_key_been_used=このSSHキーは既にサーバーに追加されています。 -ssh_key_name_used=同じ名前のSSHキーが既にアカウントに追加されています。 +ssh_key_name_used=同じ名前のSSHキーが既にアカウントに存在しています。 +ssh_principal_been_used=このプリンシパルは既にサーバーに追加されています。 gpg_key_id_used=同じIDを持つGPG公開鍵が既に存在しています。 gpg_no_key_email_found=このGPGキーは、あなたのアカウントに関連付けられたどのメールアドレスでも使用できません。 subkeys=サブキー key_id=キーID key_name=キー名 key_content=内容 +principal_content=内容 add_key_success=SSHキー '%s' を追加しました。 add_gpg_key_success=GPGキー '%s' を追加しました。 +add_principal_success=SSH証明書プリンシパル '%s' を追加しました。 delete_key=削除 ssh_key_deletion=SSHキーの削除 gpg_key_deletion=GPGキーの削除 +ssh_principal_deletion=SSH証明書プリンシパルの削除 ssh_key_deletion_desc=SSHキーを削除して、アカウントへのアクセスを無効にします。 続行しますか? gpg_key_deletion_desc=GPGキーを削除すると、そのキーで署名したコミットは未検証となります。 続行しますか? +ssh_principal_deletion_desc=SSH証明書プリンシパルを削除して、アカウントへのアクセスを無効にします。 続行しますか? ssh_key_deletion_success=SSHキーを削除しました。 gpg_key_deletion_success=GPGキーを削除しました。 +ssh_principal_deletion_success=プリンシパルを削除しました。 add_on=追加日 valid_until=有効期限 valid_forever=永久に有効 @@ -527,10 +546,10 @@ can_read_info=読み取り can_write_info=書き込み key_state_desc=この鍵は過去7日以内に使用されています。 token_state_desc=このトークンは過去7日以内に使用されています。 +principal_state_desc=このプリンシパルは過去7日以内に使用されています show_openid=プロフィールに表示する hide_openid=プロフィールに表示しない ssh_disabled=SSHは無効です - manage_social=関連付けられているソーシャルアカウントを管理 social_desc=これらのソーシャルアカウントがGiteaアカウントと連携されています。 これらすべてが、あなたのGiteaアカウントのサインインに使用してよいものであることを、十分確認してください。 unbind=連携の解除 @@ -676,6 +695,15 @@ pick_reaction=リアクションを選択 reactions_more=さらに %d 件 unit_disabled=サイト管理者がこのリポジトリセクションを無効にしています。 language_other=その他 +adopt_search=未登録リポジトリを探すユーザー名を入力... (空ですべてを探索) +adopt_preexisting_label=ファイルを登録 +adopt_preexisting=既存ファイルの登録 +adopt_preexisting_content=%s からリポジトリを作成します +adopt_preexisting_success=%s のファイルを登録し、リポジトリを作成しました +delete_preexisting_label=削除 +delete_preexisting=既存のファイルを削除 +delete_preexisting_content=%s のファイルを削除します +delete_preexisting_success=%s の未登録ファイルを削除しました desc.private=プライベート desc.public=公開 @@ -705,15 +733,17 @@ form.name_reserved=リポジトリ名 '%s' は予約されています。 form.name_pattern_not_allowed='%s' の形式はリポジトリ名に使用できません。 need_auth=クローン時の認証 -migrate_type=移行の種類 -migrate_type_helper=このリポジトリをミラーにする -migrate_type_helper_disabled=サイト管理者の設定により、新規ミラーにはできません。 +migrate_options=移行オプション +migrate_service=移行するサービス +migrate_options_mirror_helper=このリポジトリをミラーにする +migrate_options_mirror_disabled=サイト管理者はミラーの新規作成を無効にしています。 migrate_items=移行する項目 migrate_items_wiki=Wiki migrate_items_milestones=マイルストーン migrate_items_labels=ラベル migrate_items_issues=課題 migrate_items_pullrequests=プルリクエスト +migrate_items_merge_requests=マージリクエスト migrate_items_releases=リリース migrate_repo=リポジトリを移行 migrate.clone_address=移行 / クローンするURL @@ -723,17 +753,24 @@ 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の場合は、ユーザー名を入力すると移行オプションが表示されます。 +migrate.migrate_items_options=追加の項目を移行するにはアクセストークンが必要です migrated_from=%[2]sから移行 migrated_from_fake=%[1]sから移行 +migrate.migrate=%s からの移行 migrate.migrating=%s から移行しています ... migrate.migrating_failed=%s からの移行が失敗しました。 +migrate.github.description=Github.com または Github Enterprise からデータを移行します。 +migrate.git.description=Gitサービスからgitデータを移行またはミラーを作成します +migrate.gitlab.description=GitLab.com またはセルフホストのgitlabサーバーからデータを移行します。 +migrate.gitea.description=Gitea.comまたはセルフホストのGiteaサーバーからデータを移行します。 mirror_from=ミラー元 forked_from=フォーク元 generated_from=generated from fork_from_self=自分が所有しているリポジトリはフォークできません。 fork_guest_user=リポジトリをフォークするにはサインインしてください。 +watch_guest_user=リポジトリをウォッチするにはサインインしてください。 +star_guest_user=リポジトリにスターをつけるにはサインインしてください。 copy_link=コピー copy_link_success=リンクをコピーしました。 copy_link_error=⌘C または Ctrl-C でコピーしてください。 @@ -756,6 +793,7 @@ code=コード code.desc=ソースコード、ファイル、コミット、ブランチにアクセス。 branch=ブランチ tree=ツリー +clear_ref=`現在の参照をクリア` filter_branch_and_tag=ブランチまたはタグを絞り込み branches=ブランチ tags=タグ @@ -831,9 +869,11 @@ 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.fail_to_update_file=ファイル '%s' を作成または変更できませんでした。 +editor.fail_to_update_file_summary=エラーメッセージ: editor.push_rejected_no_message=サーバーがメッセージを出さずに変更を拒否しました。 Gitフックを確認してください。 -editor.push_rejected=サーバーが次のメッセージとともに変更を拒否しました:
%s
Gitフックを確認してください。 +editor.push_rejected=サーバーが変更を拒否しました。 Gitフックを確認してください。 +editor.push_rejected_summary=拒否メッセージ全体: editor.add_subdir=ディレクトリを追加… editor.unable_to_upload_files='%s' へファイルをアップロードすることができませんでした: %v editor.upload_file_is_locked=ファイル '%[1]s' は %[2]s がロックしています。 @@ -856,8 +896,8 @@ commits.date=日付 commits.older=古い commits.newer=新しい commits.signed_by=署名者 -commits.signed_by_untrusted_user=部外の署名者 -commits.signed_by_untrusted_user_unmatched=コミッターと一致しない部外の署名者 +commits.signed_by_untrusted_user=信頼できないユーザーによる署名 +commits.signed_by_untrusted_user_unmatched=コミッターと一致しない信頼できないユーザーによる署名 commits.gpg_key_id=GPGキーID ext_issues=外部課題 @@ -868,7 +908,7 @@ projects.desc=プロジェクトボードで課題とプルを管理します。 projects.create=プロジェクトを作成 projects.title=タイトル projects.new=新しいプロジェクト -projects.new_subheader=ひとつの場所で、作業の調整、追跡、更新を行い、プロジェクトの透明性と良好な進捗を維持します。 +projects.new_subheader=作業の調整・追跡・更新をひとつの場所で行い、プロジェクトの透明性と良好な進捗を維持します。 projects.create_success=プロジェクト '%s' を作成しました。 projects.deletion=プロジェクトの削除 projects.deletion_desc=プロジェクトを削除し、関連するすべての課題から除去します。続行しますか? @@ -924,6 +964,9 @@ issues.new.clear_assignees=担当者をクリア issues.new.no_assignees=担当者なし issues.new.no_reviewers=レビューアなし issues.new.add_reviewer_title=レビュー依頼 +issues.choose.get_started=始める +issues.choose.blank=デフォルト +issues.choose.blank_about=デフォルトのテンプレートから課題を作成。 issues.no_ref=ブランチ/タグ指定なし issues.create=課題を作成 issues.new_label=新しいラベル @@ -1023,6 +1066,7 @@ issues.poster=投稿者 issues.collaborator=共同作業者 issues.owner=オーナー issues.re_request_review=レビューを再依頼 +issues.is_stale=このレビューのあと、このPRに変更がありました issues.remove_request_review=レビュー依頼を取り消し issues.remove_request_review_block=レビュー依頼の取り消しはできません issues.sign_in_require_desc=サインインしてこの会話に参加。 @@ -1147,6 +1191,7 @@ issues.review.remove_review_request_self=がレビューを拒否 %s issues.review.pending=保留 issues.review.review=レビュー issues.review.reviewers=レビューア +issues.review.outdated=古い内容 issues.review.show_outdated=古い内容を表示 issues.review.hide_outdated=古い内容を隠す issues.review.show_resolved=解決済みを表示 @@ -1190,6 +1235,8 @@ pulls.required_status_check_administrator=管理者であるため、このプ pulls.blocked_by_approvals=このプルリクエストはまだ承認数が足りません。 %[1]d/%[2]dの承認を得ています。 pulls.blocked_by_rejection=このプルリクエストは公式レビューアにより変更要請されています。 pulls.blocked_by_outdated_branch=このプルリクエストは遅れのためブロックされています。 +pulls.blocked_by_changed_protected_files_1=このプルリクエストは保護されたファイルが変更されているためブロックされています: +pulls.blocked_by_changed_protected_files_n=このプルリクエストは保護されたファイルが変更されているためブロックされています: pulls.can_auto_merge_desc=このプルリクエストは自動的にマージできます。 pulls.cannot_auto_merge_desc=コンフリクトが存在するため、このプルリクエストは自動的にマージできません。 pulls.cannot_auto_merge_helper=コンフリクトを解消するため手動でマージしてください。 @@ -1213,11 +1260,15 @@ pulls.rebase_merge_commit_pull_request=リベースしてマージ(--no-ff) pulls.squash_merge_pull_request=スカッシュしてマージ pulls.require_signed_wont_sign=ブランチでは署名されたコミットが必須ですが、このマージでは署名がされません pulls.invalid_merge_option=このプルリクエストでは、指定したマージ方法は使えません。 -pulls.merge_conflict=マージ失敗: マージ中にコンフリクトがありました: %[1]s
%[2]s
ヒント: 別のストラテジーを試してみてください -pulls.rebase_conflict=マージ失敗: コミットのリベース中にコンフリクトがありました: %[1]s
%[2]s
%[3]s
ヒント: 別のストラテジーを試してみてください +pulls.merge_conflict=マージ失敗: マージ中にコンフリクトがありました。 ヒント: 別のストラテジーを試してみてください +pulls.merge_conflict_summary=エラーメッセージ +pulls.rebase_conflict=マージ失敗: コミット %[1]s のリベース中にコンフリクトがありました。 ヒント: 別のストラテジーを試してみてください +pulls.rebase_conflict_summary=エラーメッセージ +; %[2]s
%[3]s
pulls.unrelated_histories=マージ失敗: マージHEADとベースには共通する履歴がありません。 ヒント: 別のストラテジーを試してみてください pulls.merge_out_of_date=マージ失敗: マージの生成中にベースが更新されました。 ヒント: もう一度試してみてください -pulls.push_rejected=マージ失敗: プッシュは次のメッセージとともに拒否されました:
%s
このリポジトリのGitフックを見直してください +pulls.push_rejected=マージ失敗: プッシュは拒否されました。 このリポジトリのGitフックを見直してください。 +pulls.push_rejected_summary=拒否メッセージ全体: pulls.push_rejected_no_message=マージ失敗: プッシュは拒否され、リモートからのメッセージはありません。
このリポジトリのGitフックを見直してください pulls.open_unmerged_pull_exists=`同じ条件のプルリクエスト (#%d) が未処理のため、再オープンはできません。` pulls.status_checking=いくつかのステータスチェックが待機中です @@ -1225,6 +1276,8 @@ pulls.status_checks_success=ステータスチェックはすべて成功しま pulls.status_checks_warning=ステータスチェックにより警告が出ています pulls.status_checks_failure=失敗したステータスチェックがあります pulls.status_checks_error=ステータスチェックによりエラーが出ています +pulls.status_checks_requested=必須 +pulls.status_checks_details=詳細 pulls.update_branch=ブランチを更新 pulls.update_branch_success=ブランチの更新が成功しました pulls.update_not_allowed=ブランチを更新する権限がありません @@ -1236,6 +1289,7 @@ milestones.new=新しいマイルストーン milestones.open_tab=%d件 オープン中 milestones.close_tab=%d件 クローズ済 milestones.closed=%s にクローズ +milestones.update_ago=%s 前に更新 milestones.no_due_date=期日なし milestones.open=オープン milestones.close=クローズ @@ -1275,6 +1329,7 @@ signing.wont_sign.basesigned=ベース側のコミットが署名されていな signing.wont_sign.headsigned=HEADコミットが署名されていないため、マージは署名されません signing.wont_sign.commitssigned=関連するコミットに署名されていないものがあるため、マージは署名されません signing.wont_sign.approved=PRが未承認のため、マージは署名されません +signing.wont_sign.not_signed_in=あなたはサインインしていません ext_wiki=外部Wiki ext_wiki.desc=外部Wikiへのリンク。 @@ -1441,6 +1496,19 @@ settings.transfer_desc=別のユーザーやあなたが管理者権限を持っ settings.transfer_notices_1=- 個人ユーザーに移転すると、あなたはリポジトリへのアクセス権を失います。 settings.transfer_notices_2=- あなたが所有(または共同で所有)している組織に移転すると、リポジトリへのアクセス権は維持されます。 settings.transfer_form_title=確認のためリポジトリ名を入力: +settings.signing_settings=署名検証の設定 +settings.trust_model=署名トラストモデル +settings.trust_model.default=デフォルトのトラストモデル +settings.trust_model.default.desc=デフォルトのリポジトリトラストモデルを使用します。 +settings.trust_model.collaborator=共同作業者 +settings.trust_model.collaborator.long=共同作業者: 共同作業者による署名を信頼します +settings.trust_model.collaborator.desc=このリポジトリの共同作業者による正常な署名は、(署名がコミッターのものかどうかにかかわらず)「信頼済み」とみなします。 署名が共同作業者ではないコミッターのものであれば「信頼不可」、それ以外は「不一致」となります。 +settings.trust_model.committer=コミッター +settings.trust_model.committer.long=コミッター: コミッターによる署名を信頼します (これはGitHub方式であり、Giteaの署名が付いたコミットはコミッターがGitea自身であることが強制されます) +settings.trust_model.committer.desc=正常な署名は、コミッターに一致する場合のみ「信頼済み」とみなし、それ以外は「不一致」となります。 Giteaが署名付きコミットのコミッターであることが強制され、本来のコミッターはコミットの最後に Co-Authored-By: と Co-Committed-By: で追加されます。 Giteaのデフォルト鍵はデータベース内のユーザー1人とマッチしなければなりません。 +settings.trust_model.collaboratorcommitter=共同作業者+コミッター +settings.trust_model.collaboratorcommitter.long=共同作業者+コミッター: コミッターと一致する共同作業者による署名を信頼します +settings.trust_model.collaboratorcommitter.desc=このリポジトリの共同作業者による正常な署名は、コミッターと一致する場合に「信頼済み」とみなします。 それ以外の正常な署名のうち、コミッターに一致するものは「信頼不可」、他は「不一致」となります。 Giteaが署名付きコミットのコミッターであることが強制され、本来のコミッターはコミットの最後に Co-Authored-By: と Co-Committed-By: で追加されます。 Giteaのデフォルト鍵はデータベース内のユーザー1人とマッチしなければなりません。 settings.wiki_delete=Wikiデータの削除 settings.wiki_delete_desc=Wikiデータの削除は恒久的で元に戻すことはできません。 settings.wiki_delete_notices_1=- この操作はリポジトリ %s のWikiを恒久的に削除して無効にします。 @@ -1720,6 +1788,7 @@ diff.review.comment=コメント diff.review.approve=承認 diff.review.reject=変更要請 diff.committed_by=committed by +diff.protected=保護されているファイル releases.desc=プロジェクトバージョンとダウンロードの追跡。 release.releases=リリース @@ -1820,7 +1889,9 @@ settings.repoadminchangeteam=リポジトリ管理者はチームのアクセス settings.visibility=表示 settings.visibility.public=公開 settings.visibility.limited=限定 (ログインしているユーザーにのみ表示) +settings.visibility.limited_shortname=限定 settings.visibility.private=プライベート (組織メンバーにのみ表示) +settings.visibility.private_shortname=プライベート settings.update_settings=設定の更新 settings.update_setting_success=組織の設定を更新しました。 @@ -1943,6 +2014,8 @@ dashboard.update_migration_poster_id=移行する投稿者IDの更新 dashboard.git_gc_repos=すべてのリポジトリでガベージコレクションを実行 dashboard.resync_all_sshkeys='.ssh/authorized_keys' ファイルをGitea上のSSHキーで更新 dashboard.resync_all_sshkeys.desc=(ビルトインSSHサーバーでは不要です) +dashboard.resync_all_sshprincipals='.ssh/authorized_principals' ファイルをGitea上のSSHプリンシパルで更新 +dashboard.resync_all_sshprincipals.desc=(ビルトインSSHサーバーでは不要です) dashboard.resync_all_hooks=すべてのリポジトリの pre-receive, update, post-receive フックを更新する。 dashboard.reinit_missing_repos=レコードが存在するが見当たらないすべてのGitリポジトリを再初期化する dashboard.sync_external_users=外部ユーザーデータの同期 @@ -2004,7 +2077,7 @@ users.prohibit_login=サインイン無効 users.is_admin=管理者 users.is_restricted=制限あり users.allow_git_hook=Gitフックを作成可 -users.allow_git_hook_tooltip=GitフックはGiteaと同じOSユーザーで実行され、ホストへのアクセス権限もそれに従います +users.allow_git_hook_tooltip=Gitフックは、Giteaを実行しているOSユーザーの権限で実行され、同じレベルのホストアクセス権を持つようになります。 その結果、この特別なGitフック権限を持つユーザーは、Gitea上のすべてのリポジトリとGiteaで使用されているデータベースにアクセスし、変更を加えることができます。 したがって、Giteaの管理者権限を取得することもできます。 users.allow_import_local=ローカルリポジトリをインポート可 users.allow_create_organization=組織を作成可 users.update_profile=ユーザーアカウントを更新 @@ -2033,6 +2106,8 @@ orgs.members=メンバー orgs.new_orga=新しい組織 repos.repo_manage_panel=リポジトリの管理 +repos.unadopted=未登録リポジトリ +repos.unadopted.no_more=未登録のリポジトリはありません repos.owner=オーナー repos.name=名称 repos.private=プライベート @@ -2082,6 +2157,11 @@ auths.filter=Userフィルター auths.admin_filter=Adminフィルター auths.restricted_filter=制限付きフィルター auths.restricted_filter_helper=どのユーザーも制限付きにしない場合は空にしてください。 アスタリスク('*')を指定すると、Adminフィルターにマッチしないユーザーはすべて制限付きとなります。 +auths.verify_group_membership=LDAPでグループのメンバーであることを確認 +auths.group_search_base=グループ検索のベースDN +auths.valid_groups_filter=有効グループのフィルター +auths.group_attribute_list_users=ユーザーリストを持つグループ属性 +auths.user_attribute_in_group=グループ内のリストに含まれるユーザー属性 auths.ms_ad_sa=MS AD 検索属性 auths.smtp_auth=SMTP認証タイプ auths.smtphost=SMTPホスト @@ -2240,7 +2320,6 @@ config.session_config=セッション設定 config.session_provider=セッション プロバイダー config.provider_config=プロバイダーの設定 config.cookie_name=Cookieの名称 -config.enable_set_cookie=Cookieを有効にする config.gc_interval_time=GCの間隔 config.session_life_time=セッションの有効期間 config.https_only=HTTPSのみ diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 99ce576bc9a7..16f8cd9f6345 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -330,7 +330,6 @@ enterred_invalid_owner_name=새로운 소유자 이름이 올바르지 않습니 enterred_invalid_password=입력한 비밀번호는 올바르지 않습니다. user_not_exist=존재하지 않는 사용자입니다. team_not_exist=팀이 존재하지 않습니다. -last_org_owner=소유자(owners) 팀의 마지막 사용자는 삭제할 수 없습니다. 어떤 팀이든 최소한 1명의 소유자는 존재해야합니다. cannot_add_org_to_team=이 조직은 팀 구성원으로 추가할 수 없습니다. invalid_ssh_key=확인되지 않은 SSH 키입니다: %s @@ -449,7 +448,6 @@ ssh_helper=도움이 필요하세요? GitHub의 설명서를 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=하위 키 @@ -477,7 +475,6 @@ token_state_desc=이 토큰은 최근 1주일 동안 사용된 적이 있습니 show_openid=프로필에 표시 hide_openid=프로필에서 숨기기 ssh_disabled=SSH 사용불가 - manage_social=SNS계정 관리 social_desc=이러한 소셜 계정이 Gitea 계정과 연결되어 있습니다. 소셜 계정을 통해 당신의 Gitea 계정으로 로그인 할 수 있다는 점을 기억하십시오. unbind=연결 해제 @@ -595,8 +592,6 @@ form.name_reserved=저장소 이름 '%s'은 예약 되어 있습니다. form.name_pattern_not_allowed='%s' 패턴은 저장소명으로 허용되지 않습니다. need_auth=클론시 인증 -migrate_type=마이그레이션 유형 -migrate_type_helper=이 저장소는 미러가 됩니다. migrate_items_wiki=위키 migrate_items_issues=이슈 migrate_repo=저장소 마이그레이션 @@ -677,7 +672,6 @@ 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' @@ -889,6 +883,7 @@ pulls.rebase_merge_pull_request=리베이스와 머지 pulls.rebase_merge_commit_pull_request=리베이스와 머지 (--no-ff) pulls.squash_merge_pull_request=스쿼시하고 머지 pulls.invalid_merge_option=이 풀 리퀘스트에서 설정한 머지 옵션을 사용하실 수 없습니다. +; %[2]s
%[3]s
milestones.new=새로운 마일스톤 milestones.open_tab=%d개 열림 @@ -1515,7 +1510,6 @@ config.session_config=세션 설정 config.session_provider=세션 공급자 config.provider_config=공급자 설정 config.cookie_name=쿠키 이름 -config.enable_set_cookie=쿠키 활성화 config.gc_interval_time=GC 인터벌 시간 config.session_life_time=세션 수명 config.https_only=HTTPS만 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 0466a7165eec..9e9886eebb4f 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -21,10 +21,12 @@ signed_in_as=Pierakstījies kā enable_javascript=Šī lapa labāk darbojas, ja pārlūkam ir iespējots JavaScript. toc=Satura rādītājs licenses=Licences +return_to_gitea=Atgriezties Gitea username=Lietotājvārds email=E-pasta adrese password=Parole +access_token=Piekļuves talons re_type=Atkārtoti ievadiet paroli captcha=Cilvēktests twofa=Divu faktoru autentifikācija @@ -52,6 +54,8 @@ new_migrate=Jauna migrācija new_mirror=Jauns spogulis new_fork=Jauns atdalīts repozitorijs new_org=Jauna organizācija +new_project=Jauns projekts +new_project_board=Jauns projekta dēlis manage_org=Pārvaldīt organizācijas admin_panel=Lapas administrēšana account_settings=Konta iestatījumi @@ -168,6 +172,7 @@ openid_signin=Iespējot OpenID autorizāciju openid_signin_popup=Iespējot lietotāju autorizāciju ar OpenID. openid_signup=Iespējot reģistrāciju, izmantojot OpenID openid_signup_popup=Iespējot lietotāju reģistrāciju pirms tam autorizējoties ar OpenID. +enable_captcha=Pieprasīt drošības kodu lietotāju reģistrācijā enable_captcha_popup=Lietotājam reģistrējoties, pieprasīt ievadīt drošības kodu. require_sign_in_view=Iespējot nepieciešamību autorizēties, lai aplūkotu lapas require_sign_in_view_popup=Tikai autorizēti lietotāji var aplūkot lapas. Apmeklētāji redzēs tikai autorizācijas un reģistrācijas lapu. @@ -294,6 +299,8 @@ authorize_title=Autorizēt "%s" piekļuvi jūsu kontam? authorization_failed=Autorizācija neizdevās authorization_failed_desc=Autorizācija neizdevās, jo tika veikts kļūdains pieprasījums. Sazinieties ar lietojumprogrammas, ar kuru mēģinājāt autorizēties, uzturētāju. sspi_auth_failed=SSPI autentifikācija neizdevās +password_pwned=Ievadītā parole ir zagto paroļu sarakstā, kas ir kādā no publicētājām datu zādzībām. Mēģiniet vēlreiz citu paroli. +password_pwned_err=Neizdevās pabeigt pieprasījumu uz HaveIBeenPwned [mail] activate_account=Lūdzu, aktivizējiet savu kontu @@ -348,6 +355,10 @@ lang_select_error=Izvēlieties valodu no saraksta. username_been_taken=Lietotājvārds jau ir aizņemts. repo_name_been_taken=Jau eksistē repozitorijs ar šādu nosaukumu. +repository_files_already_exist=Šī repozitorija faili jau eksistē, sazinieties ar sistēmas administratoru. +repository_files_already_exist.adopt=Šī repozitorija faili jau eksistē un var tikt tikai pārņemti. +repository_files_already_exist.delete=Šī repozitorija faili jau eksistē, nepieciešams tos dzēst. +repository_files_already_exist.adopt_or_delete=Šī repozitorija faili jau eksistē, tie ir jāpārņem vai jādzēš. visit_rate_limit=Attālinātā piekļuve ir ierobežota ar ātruma ierobežotāju. 2fa_auth_required=Attālinātai piekļuvei ir nepieciešama divu faktoru autentifikācija. org_name_been_taken=Organizācijas nosaukums jau ir aizņemts. @@ -366,11 +377,12 @@ enterred_invalid_owner_name=Pārliecinieties, vai ievadītā īpašnieka vārds enterred_invalid_password=Pārliecinieties, vai ievadītā parole ir pareiza. user_not_exist=Lietotājs neeksistē. team_not_exist=Komanda neeksistē. -last_org_owner=Nevar noņemt pēdējo īpašnieku komandas lietotāju, jo organizācijām ir jābūt vismaz vienam īpašniekam. +last_org_owner=Nevar noņemt pēdejo lietotāju no īpašnieku komandas. Organizācijai ir jābūt vismaz vienam īpašniekam. cannot_add_org_to_team=Organizāciju nevar pievienot kā komandas biedru. invalid_ssh_key=Nav iespējams pārbaudīt SSH atslēgu: %s invalid_gpg_key=Nav iespējams pārbaudīt GPG atslēgu: %s +invalid_ssh_principal=Kļūdaina identitāte: %s unable_verify_ssh_key=SSH atslēgu nav iespējams pārbaudīt, pārliecinieties, ka tajā nav kļūdu. auth_failed=Autentifikācija neizdevās: %v @@ -387,6 +399,7 @@ repositories=Repozitoriji activity=Publiskā aktivitāte followers=Sekotāji starred=Atzīmēti repozitoriji +projects=Projekti following=Seko follow=Sekot unfollow=Nesekot @@ -417,6 +430,7 @@ uid=Lietotāja ID u2f=Drošības atslēgas public_profile=Publiskais profils +biography_placeholder=Pastāstiet nedaudz par sevi profile_desc=Konta e-pasta adrese ir publiska un tiks izmantota visiem ar kontu saistītiem paziņojumiem un no pārlūka veiktajām darbībām. password_username_disabled=Ārējiem lietotājiem nav atļauts mainīt savu lietotāja vārdu. Sazinieties ar sistēmas administratoru, lai uzzinātu sīkāk. full_name=Pilns vārds @@ -487,31 +501,42 @@ keep_email_private_popup=Jūsu e-pasta adrese nebūs redzama citiem lietotājiem openid_desc=Jūsu OpenID adreses ļauj autorizēties, izmantojot, Jūsu izvēlēto pakalpojumu sniedzēju. manage_ssh_keys=Pārvaldīt SSH atslēgas +manage_ssh_principals=Pārvaldīt SSH sertifikātu identitātes manage_gpg_keys=Pārvaldīt GPG atslēgas add_key=Pievienot atslēgu ssh_desc=Šīs SSH atslēgas ir piesaistītas Jūsu kontam. Ir svarīgi pārliecināties, ka visas atpazīstat, jo tās ļauj piekļūt Jūsu repozitorijiem. +principal_desc=Šādas SSH sertifikātu identitiātes ir piesaistītas kontam un ar tām iespējams piekļūt visiem jūsu repozitorijiem. gpg_desc=Šīs publiskās GPG atslēgas ir saistītas ar Jūsu kontu. Paturiet privātās atslēgas drošībā, jo tās ļauj parakstīt revīzijas. ssh_helper=Vajadzīga palīdzība? Iepazīstieties ar GitHub pamācību kā izveidot jaunu SSH atslēgu vai atrisinātu biežāk sastopamās problēmas ar kurām varat saskarties, izmantojot SSH. gpg_helper=Vajadzīga palīdzība? Iepazīstieties ar GitHub pamācību par GPG. add_new_key=Pievienot SSH atslēgu add_new_gpg_key=Pievienot GPG atslēgu +key_content_ssh_placeholder=Sākas ar 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384' vai 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Sākas ar '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal=Pievienot identitāti ssh_key_been_used=Šī SSH atslēga jau ir pievienota šajā serverī. -ssh_key_name_used=Publiskā atslēga ar šādu nosaukumu jau eksistē Jūsu kontā. +ssh_key_name_used=SSH atslēga ar šādu nosaukumu šim kontam jau eksistē. +ssh_principal_been_used=Šāda identitāte jau ir pievienota šājā serverī. gpg_key_id_used=Publiskā GPG atslēga ar šādu ID jau eksistē. gpg_no_key_email_found=Jūsu kontam nav piesaistīta neviena no šīs GPG atslēgas e-pasta adresēm. subkeys=Apakšatslēgas key_id=Atslēgas ID key_name=Atslēgas nosaukums key_content=Saturs +principal_content=Saturs add_key_success=SSH atslēga '%s' tika pievienota. add_gpg_key_success=GPG atslēga '%s' tika pievienota. +add_principal_success=SSH sertifikāte identitāte '%s' tika pievienota. delete_key=Noņemt ssh_key_deletion=Noņemt SSH atslēgu gpg_key_deletion=Noņemt GPG atslēgu +ssh_principal_deletion=Noņemt SSH sertifikāta identitāti ssh_key_deletion_desc=Dzēšot šo SSH atslēgu, ar to vairs nebūs iespējams autorizēties Jūsu kontā. Vai turpināt? gpg_key_deletion_desc=Noņemot GPG atslēgu, ar to parakstītās revīzijas vairs netiks attēlotas kā verificētas. Vai turpināt? +ssh_principal_deletion_desc=Noņemot SSH sertifikāta identitāti, ar to vairs nebūs iespējams piekļūt šim kontam. Vai turpināt? ssh_key_deletion_success=SSH atslēga tika izdzēsta. gpg_key_deletion_success=GPG atslēga tika izdzēsta. +ssh_principal_deletion_success=Identitāte tika noņemta. add_on=Pievienota valid_until=Derīga līdz valid_forever=Derīgs mūžīgi @@ -521,10 +546,10 @@ can_read_info=Lasīt can_write_info=Rakstīt key_state_desc=Šī atslēga ir izmantota pēdējo 7 dienu laikā token_state_desc=Šis talons ir izmantots pēdējo 7 dienu laikā +principal_state_desc=Šī identitāte ir lietota pēdējās 7 dienās show_openid=Rādīt profilā hide_openid=Paslēpt no profila ssh_disabled=SSH atspējots - manage_social=Pārvaldīt piesaistītos sociālos kontus social_desc=Šis ir saraksts ar Jūsu Gitea kontam piesaistītajiem sociālajiem kontiem. Drošības nolūkos, pārliecinieties, ka atpazīstat visus no tiem, jo tos var izmantot, lai pieslēgtos Jūsu Gitea kontam. unbind=Atsaistīt @@ -670,6 +695,15 @@ pick_reaction=Izvēlieties reakciju reactions_more=un vēl %d unit_disabled=Administrators ir atspējojies šo repozitorija sadaļu. language_other=Citas +adopt_search=Ievadiet lietotāja vārdu, lai meklētu nepārņemtos repozitorijus... (atstājiet tukšu, lai meklētu visus) +adopt_preexisting_label=Pārņemt failus +adopt_preexisting=Pārņemt jau eksistējošos failus +adopt_preexisting_content=Izveidot repozitoriju no direktorijas %s +adopt_preexisting_success=Pārņemti faili un izveidots repozitorijs no %s +delete_preexisting_label=Dzēst +delete_preexisting=Dzēst jau eksistējošos failus +delete_preexisting_content=Dzēst failus direktorijā %s +delete_preexisting_success=Dzēst nepārņemtos failus direktorijā %s desc.private=Privāts desc.public=Publisks @@ -699,14 +733,17 @@ form.name_reserved=Repozitorija nosaukums '%s' ir jau rezervēts. form.name_pattern_not_allowed=Repozitorija nosaukums '%s' nav atļauts. need_auth=Nepieciešama autorizācija -migrate_type=Migrācijas veids -migrate_type_helper=Šis repozitorijs būs spogulis +migrate_options=Migrācijas opcijas +migrate_service=Migrācijas serviss +migrate_options_mirror_helper=Šis repozitorijs būs spogulis +migrate_options_mirror_disabled=Lapas administrators ir atslēdzies iespēju viedot jaunus spoguļus. migrate_items=Vienības, ko pārņemt migrate_items_wiki=Vikivietni migrate_items_milestones=Atskaites punktus migrate_items_labels=Etiķetes migrate_items_issues=Problēmas migrate_items_pullrequests=Izmaiņu pieprasījumus +migrate_items_merge_requests=Sapludināšanas pieprasījumi migrate_items_releases=Laidienus migrate_repo=Migrēt repozitoriju migrate.clone_address=Klonēšanas adrese @@ -716,17 +753,24 @@ migrate.permission_denied=Jums nav tiesību importēt lokālu repozitoriju. migrate.invalid_local_path=Nederīgs lokālais ceļš. Tas neeksistē vai nav direktorija. migrate.failed=Migrācija neizdevās: %v migrate.lfs_mirror_unsupported=LFS objektu spoguļošana netiek atbalstīta - tā vietā izmantojiet 'git lfs fetch --all' un 'git lfs push --all'. -migrate.migrate_items_options=Pārņemot datus no GitHub, ievadiet lietotāja vārdu, lai redzētu papildus iestatījumus. +migrate.migrate_items_options=Piekļuves talons ir nepieciešams, lai migrētu papildus datus migrated_from=Migrēts no %[2]s migrated_from_fake=Migrēts no %[1]s +migrate.migrate=Migrēt no %s migrate.migrating=Migrācija no %s ... migrate.migrating_failed=Migrācija no %s neizdevās. +migrate.github.description=Migrēt datus no Github.com vai Github Enterprise servera. +migrate.git.description=Migrēt vai spoguļot git datus no Git servisiem +migrate.gitlab.description=Migrēt datus no Gitlab.com vai cita Gitlab servera. +migrate.gitea.description=Migrēt datus no Gitea.com vai cita Gitea servera. mirror_from=spogulis no forked_from=atdalīts no generated_from=ģenerēts no fork_from_self=Nav iespējams atdalīt repozitoriju, kuram esat īpašnieks. fork_guest_user=Autorizējieties, lai atdalītu repozitoriju. +watch_guest_user=Autorizejieties, lai sekotu šim repozitorijam. +star_guest_user=Autorizejieties, lai atzīmētu ar zvaigznīti šo repozitoriju. copy_link=Kopēt copy_link_success=Saite nokopēta copy_link_error=Izmantojiet ⌘C vai Ctrl+C, lai nokopētu @@ -749,11 +793,13 @@ code=Kods code.desc=Piekļūt pirmkodam, failiem, revīzijām un atzariem. branch=Atzars tree=Koks +clear_ref=`Notīrīt pašreizējo atsauci` filter_branch_and_tag=Filtrēt atzarus vai tagus branches=Atzari tags=Tagi issues=Problēmas pulls=Izmaiņu pieprasījumi +project_board=Projekti labels=Etiķetes org_labels_desc=Organizācijas līmeņa etiķetes var tikt izmantotas visiem repozitorijiem šajā organizācijā org_labels_desc_manage=pārvaldīt @@ -772,6 +818,8 @@ audio_not_supported_in_browser=Jūsu pārlūks neatbalsta HTML5 audio. stored_lfs=Saglabāts Git LFS symbolic_link=Simboliska saite commit_graph=Revīziju grafs +commit_graph.monochrome=Melnbalts +commit_graph.color=Krāsains blame=Vainot normal_view=Parastais skats line=rinda @@ -821,9 +869,7 @@ editor.file_already_exists=Fails ar nosaukumu '%s' šajā repozitorijā jau eksi editor.commit_empty_file_header=Iesūtīt tukšu failu editor.commit_empty_file_text=Fails, ko vēlaties iesūtīt, ir tukšs. Vai turpināt? editor.no_changes_to_show=Nav izmaiņu, ko rādīt. -editor.fail_to_update_file=Neizdevās izmainīt/izveidot failu '%s', kļūda: %v editor.push_rejected_no_message=Izmaiņu iesūtīšana tika noraidīta, bet serveris neatgrieza paziņojumu. Pārbaudiet git āķus šim repozitorijam. -editor.push_rejected=Izmaiņu iesūtīšana tika noraidīta ar sekojošu ziņojumu:
%s
Pārbaudiet git āķus šim repozitorijam. editor.add_subdir=Pievienot direktoriju… editor.unable_to_upload_files=Neizdevās augšupielādēt failus uz direktoriju '%s', kļūda: %v editor.upload_file_is_locked=Failu '%s' ir nobloķējis %s. @@ -853,10 +899,40 @@ commits.gpg_key_id=GPG atslēgas ID ext_issues=Ārējās problēmas ext_issues.desc=Saite uz ārējo problēmu sekotāju. +projects=Projekti +projects.desc=Pārvaldi problēmas un izmaiņu pieprasījumus uz projektu dēļos. +projects.create=Izveidot projektu +projects.title=Nosaukums +projects.new=Jauns projekts +projects.new_subheader=Koordinē, seko un atjauno savu darbu centralizēti, lai projekts būtu izsekojams un vienmēr laikā. +projects.create_success=Projekts '%s' tika izveidots. +projects.deletion=Dzēst projektu +projects.deletion_desc=Dzēšot projektu no tā tiks atsaistītās visas tam piesaistītās problēmas. Vai turpināt? +projects.deletion_success=Šis projekts tika izdzēsts. +projects.edit=Labot projektu +projects.edit_subheader=Projekti organizē problēmas un ļauj izsekot to progresam. +projects.modify=Mainīt projektu +projects.edit_success=Projekta '%s' izmaiņas tika saglabātas. +projects.type.none=Nav +projects.type.basic_kanban=Vienkāršots "Kanban" +projects.type.bug_triage=Kļūdu šķirošana +projects.template.desc=Projekta sagatave +projects.template.desc_helper=Izvēlieties projekta sagatavi, lai sāktu darbu +projects.type.uncategorized=Bez kategorijas +projects.board.edit=Labot dēli +projects.board.edit_title=Dēļa nosaukums +projects.board.new_title=Dēļa nosaukums +projects.board.new_submit=Apstiprināt +projects.board.new=Jauns dēlis +projects.board.delete=Dzēst dēli +projects.board.deletion_desc=Dzēšot projekta dēli visas tam piesaistītās problēmas tiks pārliktas kā nekategorizētas. Vai turpināt? +projects.open=Aktīvie +projects.close=Pabeigtie issues.desc=Organizēt kļūdu ziņojumus, uzdevumus un atskaites punktus. issues.filter_assignees=Filtrēt pēc atbildīgajiem issues.filter_milestones=Filtrēt pēc atskaites punkta +issues.filter_projects=Filtrēt pēc projekta issues.filter_labels=Filtrēt pēc etiķetēm issues.filter_reviewers=Filtrēt pēc recenzentiem issues.new=Jauna problēma @@ -865,6 +941,12 @@ issues.new.labels=Etiķetes issues.new.add_labels_title=Apstiprināt etiķetes issues.new.no_label=Nav etiķešu issues.new.clear_labels=Noņemt etiķetes +issues.new.projects=Projekti +issues.new.add_project_title=Pievienot projektam +issues.new.clear_projects=Notīrīt projektus +issues.new.no_projects=Nav projektu +issues.new.open_projects=Aktīvie projekti +issues.new.closed_projects=Pabeigtie projekti issues.new.no_items=Nav neviena ieraksta issues.new.milestone=Atskaites punkts issues.new.add_milestone_title=Uzstādīt atskaites punktu @@ -878,6 +960,9 @@ issues.new.clear_assignees=Noņemt atbildīgo issues.new.no_assignees=Nav atbildīgo issues.new.no_reviewers=Nav recenzentu issues.new.add_reviewer_title=Pieprasīt recenziju +issues.choose.get_started=Sākt darbu +issues.choose.blank=Noklusējuma +issues.choose.blank_about=Izveidot problēmu ar noklusējuma sagatavi. issues.no_ref=Nav norādīts atzars/tags issues.create=Pieteikt problēmu issues.new_label=Jauna etiķete @@ -892,9 +977,13 @@ issues.label_templates.fail_to_load_file=Neizdevās ielādēt etiķetes sagatave issues.add_label_at=pievienoja etiķeti
%s
%s issues.remove_label_at=noņēma etiķeti
%s
%s issues.add_milestone_at=`pievienoja atskaites punktu %s %s` +issues.add_project_at=`pievienoja šo problēmu %s projektam %s` issues.change_milestone_at=`nomainīja atskaites punktu no %s uz %s %s` +issues.change_project_at=`pārvietoja šo problēmu no %s projekta uz %s %s` issues.remove_milestone_at=`noņēma atskaites punktu %s %s` +issues.remove_project_at=`noņēma šo problēmu no %s projekta %s` issues.deleted_milestone=`(dzēsts)` +issues.deleted_project=`(dzēsts)` issues.self_assign_at=`piešķīra sev %s` issues.add_assignee_at=`tika piešķirta problēma no %s %s` issues.remove_assignee_at=`tika noņemta problēma no %s %s` @@ -973,6 +1062,7 @@ issues.poster=Autors issues.collaborator=Līdzstrādnieks issues.owner=Īpašnieks issues.re_request_review=Pieprasīt atkārtotu recenziju +issues.is_stale=Šajā izmaiņu pieprasījumā ir notikušas izmaiņās, kopš veicāt tā recenziju issues.remove_request_review=Noņemt recenzijas pieprasījumu issues.remove_request_review_block=Nevar noņemt recenzījas pieprasījumu issues.sign_in_require_desc=Pierakstieties, lai pievienotos šai sarunai. @@ -1140,6 +1230,8 @@ pulls.required_status_check_administrator=Kā administrators Jūs varat sapludin 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.blocked_by_outdated_branch=Šis izmaiņu pieprasījums ir bloķēts, jo tas nav aktuāls. +pulls.blocked_by_changed_protected_files_1=Šis izmaiņu pieprasījums ir bloķēts, jo izmaina aizsargāto failu: +pulls.blocked_by_changed_protected_files_n=Šis izmaiņu pieprasījums ir bloķēts, jo izmaina aizsargātos failus: 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. @@ -1163,11 +1255,9 @@ pulls.rebase_merge_commit_pull_request=Pārbāzēt un sapludināt (--no-ff) pulls.squash_merge_pull_request=Saspiest un sapludināt pulls.require_signed_wont_sign=Atzarā var iesūtīt tikai parakstītas revīzijas, bet sapludināšanas revīzijas netiks parakstīta pulls.invalid_merge_option=Nav iespējams izmantot šādu sapludināšanas veidu šim izmaiņu pieprasījumam. -pulls.merge_conflict=Sapludināšana neizdevās: sapludinot radās konflikts: %[1]s
%[2]s
Ieteikums: izvēlieties citu sapludināšanas stratēģiju -pulls.rebase_conflict=Sapludināšana neizdevās: pārbāzējot radās konflikts: %[1]s
%[2]s
%[3]s
Ieteikums: izvēlieties citu sapludināšanas stratēģiju +; %[2]s
%[3]s
pulls.unrelated_histories=Sapludināšana neizdevās: mērķa un bāzes atzariem nav kopējas vēstures. Ieteikums: izvēlieties citu sapludināšanas stratēģiju pulls.merge_out_of_date=Sapludināšana neizdevās: sapludināšanas laikā, bāzes atzarā tika iesūtītas izmaiņas. Ieteikums: mēģiniet atkārtoti. -pulls.push_rejected=Sapludināšana neizdevās: Izmaiņu iesūtīšana tika noraidīta ar sekojošu ziņojumu:
%s
Pārbaudiet git āķus šim repozitorijam pulls.push_rejected_no_message=Sapludināšana neizdevās: Izmaiņu iesūtīšana tika noraidīta, bet serveris neatgrieza paziņojumu.
Pārbaudiet git āķus šim repozitorijam pulls.open_unmerged_pull_exists=`Jūs nevarat veikt atkārtotas atvēršanas darbību, jo jau eksistē izmaiņu pieprasījums (#%d) ar šādu sapludināšanas informāciju.` pulls.status_checking=Dažas pārbaudes vēl tiek veiktas @@ -1175,6 +1265,8 @@ pulls.status_checks_success=Visas pārbaudes ir veiksmīgas pulls.status_checks_warning=Dažas pārbaudes ziņoja brīdinājumus pulls.status_checks_failure=Dažas pārbaudes neizdevās izpildīt pulls.status_checks_error=Dažu pārbaužu izpildes laikā, radās kļūdas +pulls.status_checks_requested=Obligāts +pulls.status_checks_details=Papildu informācija pulls.update_branch=Atjaunot atzaru pulls.update_branch_success=Atzara atjaunināšana veiksmīgi pabeigta pulls.update_not_allowed=Jums nav tiesību veikt atzara atjaunošanu @@ -1186,6 +1278,7 @@ milestones.new=Jauns atskaites punkts milestones.open_tab=%d atvērti milestones.close_tab=%d aizvērti milestones.closed=Aizvērts %s +milestones.update_ago=Atjaunots pirms %s milestones.no_due_date=Bez termiņa milestones.open=Atvērta milestones.close=Aizvērt @@ -1225,6 +1318,7 @@ signing.wont_sign.basesigned=Sapludināšanas revīzija netiks parakstīta, jo b signing.wont_sign.headsigned=Sapludināšanas revīzija netiks parakstīta, jo pamata revīzija nav parakstīta signing.wont_sign.commitssigned=Sapludināšanas revīzija netiks parakstīta, jo visas saistītās revīzijas nav parakstītas signing.wont_sign.approved=Sapludināsanas revīzija netiks parakstīta, jo izmaiņu pieprasījums nav apstiprināts +signing.wont_sign.not_signed_in=Jūs neesat autorizējies ext_wiki=Ārējā vikivietne ext_wiki.desc=Ārējā vikivietne norāda uz ārējo vikivietnes adresi. @@ -1370,6 +1464,7 @@ settings.pulls.allow_merge_commits=Iespējot revīziju sapludināšanu settings.pulls.allow_rebase_merge=Iespējot pārbāzēšanu sapludinot revīzijas settings.pulls.allow_rebase_merge_commit=Iespējot pārbāzēšanu sapludinot revīzijas (--no-ff) settings.pulls.allow_squash_commits=Iespējot saspiešanu sapludinot revīzijas +settings.projects_desc=Iespējot repozitorija projektus settings.admin_settings=Administratora iestatījumi settings.admin_enable_health_check=Iespējot veselības pārbaudi (git fsck) šim repozitorijam settings.admin_enable_close_issues_via_commit_in_any_branch=Aizvērt problēmu ar izmaiņu komentāru iesūtītu jebkurā atzarā @@ -1390,6 +1485,19 @@ settings.transfer_desc=Mainīt šī repozitorija īpašnieku uz citu lietotāju settings.transfer_notices_1=- Jūs zaudēsiet piekļuvi, ja jaunais īpašnieks ir individuāls lietotājs. settings.transfer_notices_2=- Jūs saglabāsiet piekļuvi, ja jaunais īpašnieks ir organizācija un Jūs esat viens no tās īpašniekiem. settings.transfer_form_title=Ievadiet repozitorija nosaukumu, lai apstiprinātu: +settings.signing_settings=Parakstu pārbaudes iestatījumi +settings.trust_model=Uzticēšanās modelis parakstiem +settings.trust_model.default=Noklusējuma uzticēšanās modelis +settings.trust_model.default.desc=Izmantot noklusēto repozitorija uzticības modeli. +settings.trust_model.collaborator=Līdzstrādnieka +settings.trust_model.collaborator.long=Līdzstrādnieka: Uzticēties līdzstrādnieku parakstiem +settings.trust_model.collaborator.desc=Ticami līdzstrādnieku paraksti tiks atzīmēti kā "uzticami" (neatkarīgi no tā vai tie atbilst revīzijas iesūtītājam vai nē). Citos gadījumos ticami paraksti tiks atzīmēti kā "neuzticami", ja paraksts atbilst revīzijas iesūtītājam vai "nesakrītošs", ja neatbilst. +settings.trust_model.committer=Revīzijas iesūtītāja +settings.trust_model.committer.long=Revīzijas iesūtītāja: Uzticēties parakstiem, kas atbilst revīzijas iesūtītājiem (Šis atbilst GitHub uzvedībai un piespiedīs Gitea parakstītām revīzijām būt Gitea kā revīzijas iesūtītājam) +settings.trust_model.committer.desc=Ticami paraksti tiks atzīmēti kā "uzticami", ja tie atbilst revīzijas iesūtītājam, citos gadījumos tie tiks atzīmēti kā "nesakrītoši". Šis nozīmē, ka Gitea būs kā revīzijas iesūtītājs parakstītām revīzijām, kur īstais revīzijas iesūtītājs tiks atīzmēts revīzijas komentāra beigās ar tekstu Co-Authored-By: un Co-Committed-By:. Noklusētajai Gitea atslēgai ir jāatbilst lietotājam datu bāzē. +settings.trust_model.collaboratorcommitter=Līdzstrādnieka un revīzijas iesūtītāja +settings.trust_model.collaboratorcommitter.long=Līdzstrādnieka un revīzijas iesūtītāja: Uzticēties līdzstrādnieku parakstiem, kas atbilst revīzijas iesūtītājam +settings.trust_model.collaboratorcommitter.desc=Ticami līdzstrādnieku paraksti tiks atzīmēti kā "uzticami", ja tie atbilst revīzijas iesūtītājam, citos gadījumos tie tiks atzīmēti kā "neuzticami", ja paraksts atbilst revīzijas iesūtītajam, vai "nesakrītoši", ja neatbilst. Šis nozīmē, ka Gitea būs kā revīzijas iesūtītājs parakstītām revīzijām, kur īstais revīzijas iesūtītājs tiks atīzmēts revīzijas komentāra beigās ar tekstu Co-Authored-By: un Co-Committed-By:. Noklusētajai Gitea atslēgai ir jāatbilst lietotājam datu bāzē. settings.wiki_delete=Dzēst vikivietnes datus settings.wiki_delete_desc=Vikivietnes repozitorija dzēšana ir NEATGRIEZENISKA. Vai turpināt? settings.wiki_delete_notices_1=- Šī darbība dzēsīs un atspējos repozitorija %s vikivietni. @@ -1546,6 +1654,7 @@ settings.protect_enable_push=Atļaut iesūtīt izmaiņas settings.protect_enable_push_desc=Ikviens, kam ir rakstīšanas tiesības uz šo repozitoriju, varēs iesūtīt izmaiņas šajā atzarā (piespiedu izmaiņu iesūtīšanas netiks atļauta). settings.protect_whitelist_committers=Atļaut iesūtīt izmaiņas norādītajiem lietotājiem vai komandām settings.protect_whitelist_committers_desc=Tikai norādītiem lietotāji vai komandas varēs iesūtīt izmaiņas šajā atzarā (piespiedu izmaiņu iesūtīšanas netiks atļauta). +settings.protect_whitelist_deploy_keys=Atļaut izvietošanas atslēgām ar rakstīšanas tiesībām nosūtīt izmaiņas. settings.protect_whitelist_users=Lietotāji, kas var veikt izmaiņu nosūtīšanu: settings.protect_whitelist_search_users=Meklēt lietotājus… settings.protect_whitelist_teams=Komandas, kas var veikt izmaiņu nosūtīšanu: @@ -1668,6 +1777,7 @@ diff.review.comment=Komentēt diff.review.approve=Apstiprināt diff.review.reject=Pieprasīt izmaiņas diff.committed_by=revīziju iesūtīja +diff.protected=Aizsargāts releases.desc=Pārvaldiet projekta versijas un lejupielādes. release.releases=Laidieni @@ -1677,6 +1787,7 @@ release.prerelease=Pirmsizlaides versija release.stable=Stabila release.edit=labot release.ahead.commits=%d revīzijas +release.ahead.target=no %s kopš laidiena publicēšanas release.source_code=Izejas kods release.new_subheader=Laidieni palīdz organizēt projekta versijas. release.edit_subheader=Laidieni palīdz organizēt projekta versijas. @@ -1767,7 +1878,9 @@ settings.repoadminchangeteam=Repozitorija administrators var pievienot vai noņe settings.visibility=Redzamība settings.visibility.public=Publiska settings.visibility.limited=Ierobežota (redzama tikai autorizētiem lietotājiem) +settings.visibility.limited_shortname=Ierobežota settings.visibility.private=Privāta (redzama tikai organizācijas dalībniekiem) +settings.visibility.private_shortname=Privāta settings.update_settings=Mainīt iestatījumus settings.update_setting_success=Organizācijas iestatījumi tika saglabāti. @@ -1890,6 +2003,8 @@ dashboard.update_migration_poster_id=Atjaunot migrācijām autoru ID dashboard.git_gc_repos=Veikt atkritumu uzkopšanas darbus visiem repozitorijiem dashboard.resync_all_sshkeys=Atjaunot '.ssh/authorized_keys' failu ar Gitea SSH atslēgām. dashboard.resync_all_sshkeys.desc=(Nav nepieciešams iebūvētajam SSH serverim.) +dashboard.resync_all_sshprincipals=Atjaunot '.ssh/authorized_principals' failu ar Gitea SSH sertifikātu identitātēm. +dashboard.resync_all_sshprincipals.desc=(Nav nepieciešams iebūvētajam SSH serverim.) dashboard.resync_all_hooks=Pārsinhronizēt pirms-saņemšanas, atjaunošanas un pēc-saņemšanas āķus visiem repozitorijiem. dashboard.reinit_missing_repos=Atkārtoti inicializēt visus pazaudētos Git repozitorijus par kuriem eksistē ieraksti dashboard.sync_external_users=Sinhronizēt ārējo lietotāju datus @@ -1930,6 +2045,7 @@ users.full_name=Vārds, uzvārds users.activated=Aktivizēts users.admin=Administrators users.restricted=Ierobežots +users.2fa=2FA users.repos=Repozitoriji users.created=Izveidots users.last_login=Pēdējā autorizācija @@ -1950,7 +2066,7 @@ users.prohibit_login=Atspējota pieslēgšanās users.is_admin=Administratora tiesības users.is_restricted=Ir ierobežots users.allow_git_hook=Atļaut veidot git āķus -users.allow_git_hook_tooltip=Git āķi tiks izpildīti ar tādām pašām OS lietotāja sistēmas tiesībām kā Gitea serveris +users.allow_git_hook_tooltip=Git āķi tiek izpildīti ar OS lietotāju zem kura ir izpildīts Gitea serviss un tiem ir tāda paša līmeņa piekļuve serverim. Šī rezultātā, lietotājiem ar speciālajām Git āķu tiesībām ir iespēja piekļūt un mainīt visus Gitea repozitorijus, kā arī datu bāzi, ko izmanto Gitea. Tāpat šie lietotāji var iegūt Gitea administratora tiesības. users.allow_import_local=Atļauts importēt lokālus repozitorijus users.allow_create_organization=Atļauts veidot organizācijas users.update_profile=Mainīt lietotāja kontu @@ -1979,6 +2095,8 @@ orgs.members=Dalībnieki orgs.new_orga=Jauna organizācija repos.repo_manage_panel=Repozitoriju pārvaldība +repos.unadopted=Nepārņemtie repozitoriji +repos.unadopted.no_more=Netika atrasts neviens nepārņemtais repozitorijs repos.owner=Īpašnieks repos.name=Nosaukums repos.private=Privāts @@ -2028,6 +2146,11 @@ auths.filter=Lietotāju filts auths.admin_filter=Administratoru filtrs auths.restricted_filter=Ierobežoto lietotāju filtrs auths.restricted_filter_helper=Atstājiet tukšu, lai nevienam lietotajam neuzstādīt ierobežots pazīmi. Izmantojiet zvaigznīti ('*'), lai uzstādītu visiem lietotājiem, kas neatbilst administratora filtram. +auths.verify_group_membership=Pārbaudīt lietotāja piederību LDAP grupai +auths.group_search_base=Grupas pamatnosacījumi +auths.valid_groups_filter=Atļauto grupu filtrs +auths.group_attribute_list_users=Grupas atribūts, kas satur sarakstu ar lietotājiem +auths.user_attribute_in_group=Grupas atribūts, kas nosaka lietotāju auths.ms_ad_sa=MS AD meklēšanas atribūti auths.smtp_auth=SMTP autentifikācijas tips auths.smtphost=SMTP resursdators @@ -2168,6 +2291,7 @@ config.mailer_use_sendmail=Izmantot Sendmail config.mailer_sendmail_path=Ceļš līdz sendmail programmai config.mailer_sendmail_args=Papildus Sendmail komandrindas argumenti config.mailer_sendmail_timeout=Sendmail noildze +config.test_email_placeholder=E-pasts (piemēram, test@example.com) config.send_test_mail=Nosūtīt pārbaudes e-pastu config.test_mail_failed=Neizdevās nosūtīt pārbaudes e-pastu uz '%s': %v config.test_mail_sent=Pārbaudes e-pasts tika nosūtīts uz '%s'. @@ -2185,7 +2309,6 @@ config.session_config=Sesijas konfigurācja config.session_provider=Sesijas nodrošinātājs config.provider_config=Pakalpojumu sniedzēja konfigurācija config.cookie_name=Sīkdatnes nosaukums -config.enable_set_cookie=Ļaut izmantot sīkdatnes config.gc_interval_time=GC laika intervāls config.session_life_time=Sesijas ilgums config.https_only=Tikai HTTPS @@ -2327,6 +2450,7 @@ mirror_sync_create=ar spoguli sinhronizēta jauna atsauce %[ mirror_sync_delete=ar spoguli sinhronizēta un izdzēsta atsauce %[2]s repozitorijam %[3]s approve_pull_request=`apstiprināja %s#%[2]s` reject_pull_request=`ieteica izmaiņas %s#%[2]s` +publish_release=`publicēts laidiens "%[4]s" saitē %[3]s` [tool] ago=pirms %s diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index 37980ca0f99c..6fb298cf60a7 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -321,7 +321,6 @@ 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 @@ -441,7 +440,6 @@ 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=സബ് കീകള്‍ @@ -469,7 +467,6 @@ token_state_desc=ഈ ടോക്കൺ കഴിഞ്ഞ 7 ദിവസങ് show_openid=പ്രൊഫൈലിൽ കാണുക hide_openid=പ്രൊഫൈലിൽ നിന്ന് മറയ്‌ക്കുക ssh_disabled=SSH അപ്രാപ്‌തമാക്കി - manage_social=സഹവസിക്കുന്ന സോഷ്യൽ അക്കൗണ്ടുകളെ നിയന്ത്രിക്കുക social_desc=ഈ സോഷ്യൽ അക്കൗണ്ടുകൾ നിങ്ങളുടെ ഗിറ്റീ അക്കൗണ്ടുമായി ലിങ്കുചെയ്‌തു. ഇവ നിങ്ങളുടെ ഗീറ്റീ അക്കൗണ്ടിലേക്ക് പ്രവേശിക്കാൻ ഉപയോഗിക്കാവുന്നതിനാൽ അവയെല്ലാം നിങ്ങൾ തിരിച്ചറിഞ്ഞുവെന്ന് ഉറപ്പാക്കുക. unbind=അൺലിങ്ക് ചെയ്യുക @@ -613,8 +610,6 @@ 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=നാഴികക്കല്ലുകള്‍ @@ -630,7 +625,6 @@ 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 നിന്ന് മൈഗ്രേറ്റുചെയ്തു @@ -748,6 +742,7 @@ issues.dependency.add_error_cannot_create_circular=രണ്ട് ഇഷ്യ issues.dependency.add_error_dep_not_same_repo=രണ്ട് പ്രശ്നങ്ങളും ഒരേ കലവറയിലേതു് ആയിരിക്കണം. +; %[2]s
%[3]s
milestones.filter_sort.most_issues=മിക്ക ഇഷ്യൂകളും milestones.filter_sort.least_issues=കുറഞ്ഞ ഇഷ്യൂകളെങ്കിലും diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 35286eb465fe..1f4ad411ee6b 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -20,10 +20,13 @@ user_profile_and_more=Profiel en instellingen… signed_in_as=Aangemeld als enable_javascript=Deze website werkt beter met JavaScript. toc=Inhoudsopgave +licenses=Licenties +return_to_gitea=Terug naar Gitea username=Gebruikersnaam email=E-mail adres password=Wachtwoord +access_token=Toegangstoken re_type=Typ uw wachtwoord opnieuw in captcha=CAPTCHA twofa=Twee factor authenticatie @@ -51,6 +54,8 @@ new_migrate=Nieuwe migratie new_mirror=Nieuwe kopie new_fork=Nieuwe Repository Fork new_org=Nieuwe organisatie +new_project=Nieuw project +new_project_board=Nieuw projectbord manage_org=Beheer organisaties admin_panel=Website Administratie account_settings=Accountinstellingen @@ -71,6 +76,7 @@ issues=Kwesties milestones=Mijlpalen cancel=Annuleren +save=Opslaan add=Toevoegen add_all=Alles toevoegen remove=Verwijder @@ -166,6 +172,7 @@ openid_signin=OpenID-inloggen inschakelen openid_signin_popup=Gebruikerslogin via OpenID inschakelen. openid_signup=OpenID zelf-registratie inschakelen openid_signup_popup=OpenID zelfregistratie inschakelen. +enable_captcha=Registratie CAPTCHA inschakelen enable_captcha_popup=Vereis captcha validatie voor zelf-registratie van gebruiker. require_sign_in_view=Vereis inloggen om pagina's te kunnen bekijken require_sign_in_view_popup=Beperk de toegang tot de pagina tot ingelogde gebruikers. Bezoekers zullen alleen de 'login' en het registratiegedeelte van de pagina zien. @@ -292,6 +299,8 @@ authorize_title=Autoriseer "%s" voor toegang tot uw account? authorization_failed=Autorisatie mislukt authorization_failed_desc=De autorisatie is mislukt omdat we een ongeldige aanvraag gedetecteerd hebben. Neem contact op met de beheerder van de app die u geprobeerd heeft te autoriseren. sspi_auth_failed=SSPI-authenticatie mislukt +password_pwned=Het gekozen wachtwoord staat op een lijst van gestolen wachtwoorden die eerder zijn blootgesteld aan openbare gegevenslekken. Probeer het opnieuw met een ander wachtwoord. +password_pwned_err=Kan het verzoek om HaveIBeenPwned niet voltooien [mail] activate_account=Activeer uw account @@ -346,6 +355,10 @@ lang_select_error=Selecteer een taal uit de lijst. username_been_taken=Deze naam is al in gebruik. repo_name_been_taken=De repository-naam wordt al gebruikt. +repository_files_already_exist=Er bestaan al bestanden voor deze repository. Neem contact op met de systeembeheerder. +repository_files_already_exist.adopt=Bestanden bestaan al voor deze repository en kunnen alleen worden geadopteerd. +repository_files_already_exist.delete=Er bestaan al bestanden voor deze repository. U moet deze verwijderen. +repository_files_already_exist.adopt_or_delete=Er bestaan al bestanden voor deze repository. Adopteer of verwijder deze. visit_rate_limit=Bezoeklimiet op afstand gerichter. 2fa_auth_required=Extern bezoek vereist twee-factor authenticatie. org_name_been_taken=Naam van de organisatie wordt al gebruikt. @@ -364,11 +377,12 @@ enterred_invalid_owner_name=De nieuwe eigenaarnaam is niet geldig. enterred_invalid_password=Het ingevoerde wachtwoord is onjuist. user_not_exist=De gebruiker bestaat niet. team_not_exist=Dit team bestaat niet. -last_org_owner=Je kunt de laatste eigenaar van een organisatie niet verwijderen — er moet er minimaal één in zitten. +last_org_owner=Je kunt de laatste eigenaar van een organisatie niet verwijderen. Er moet er minimaal één eigenaar in een organisatie zitten. cannot_add_org_to_team=Een organisatie kan niet worden toegevoegd als een teamlid. invalid_ssh_key=Kan de SSH-sleutel niet verifiëren: %s invalid_gpg_key=Kan de GPG-sleutel niet verifiëren: %s +invalid_ssh_principal=Ongeldige verantwoordelijke: %s unable_verify_ssh_key=Kan de SSH-sleutel niet verifiëren; controleer hem op fouten. auth_failed=Verificatie mislukt: %v @@ -385,11 +399,13 @@ repositories=repositories activity=Openbare activiteit followers=Volgers starred=Repositories met ster +projects=Projecten following=Volgt follow=Volg unfollow=Niet meer volgen heatmap.loading=Heatmap wordt geladen… user_bio=Biografie +disabled_public_activity=Deze gebruiker heeft de publieke zichtbaarheid van de activiteit uitgeschakeld. form.name_reserved=De gebruikersnaam '%s' is gereserveerd. form.name_pattern_not_allowed=Het patroon '%s' is niet toegestaan in een gebruikersnaam. @@ -414,6 +430,7 @@ uid=uid u2f=Beveiligingssleutels public_profile=Openbaar profiel +biography_placeholder=Vertel ons iets over jezelf profile_desc=Je e-mailadres zal gebruikt worden voor notificaties en andere handelingen. password_username_disabled=Niet-lokale gebruikers kunnen hun gebruikersnaam niet veranderen. Neem contact op met de sitebeheerder voor meer details. full_name=Volledige naam @@ -428,6 +445,9 @@ continue=Doorgaan cancel=Annuleren language=Taal ui=Thema +privacy=Privacy +keep_activity_private=De activiteit van de profielpagina verbergen +keep_activity_private_popup=Maakt de activiteit alleen zichtbaar voor jou en de admins lookup_avatar_by_mail=Profielfoto van e-mailadres gebruiken federated_avatar_lookup=Gefedereerde Avatars inschakelen @@ -481,31 +501,42 @@ keep_email_private_popup=Je e-mailadres wordt verborgen voor andere gebruikers. openid_desc=Met OpenID kan je authenticatie uitbesteden aan een externe provider. manage_ssh_keys=Beheer SSH sleutels +manage_ssh_principals=Beheer SSH-certificaat verantwoordelijke manage_gpg_keys=Beheer GPG sleutels add_key=Sleutel toevoegen ssh_desc=Deze publieke SSH sleutels worden geassocieerd met uw account. De bijbehorende private sleutels geven volledige toegang toe tot je repositories. +principal_desc=Deze SSH-certificaatverantwoordelijken zijn gekoppeld aan uw account en geven volledige toegang tot uw repositories. gpg_desc=Deze publieke GPG-sleutels zijn verbonden met je account. Houd je privé-sleutels veilig, omdat hiermee commits kunnen worden ondertekend. ssh_helper=Weet u niet hoe? Lees dan onze handleiding voor het genereren van SSH sleutels of voor algemene SSH problemen. gpg_helper=Hulp nodig? Neem een kijkje op de GitHub handleiding over GPG. add_new_key=SSH sleutel toevoegen add_new_gpg_key=GPG sleutel toevoegen +key_content_ssh_placeholder=Begint met 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', of 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Begint met '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal=Verantwoordelijke toevoegen ssh_key_been_used=Deze SSH-sleutel is al toegevoegd aan de server. -ssh_key_name_used=Een SSH-sleutel met dezelfde naam is al toegevoegd aan uw account. +ssh_key_name_used=Er bestaat al een SSH sleutel met dezelfde naam in uw account. +ssh_principal_been_used=Deze verantwoordelijke is al toegevoegd aan de server. gpg_key_id_used=Een publieke GPG-sleutel met dit ID bestaat al. gpg_no_key_email_found=Deze GPG-sleutel kan met geen van de e-mailadressen van dit account gebruikt worden. subkeys=Subkeys key_id=Key-ID key_name=Sleutel naam key_content=Inhoud +principal_content=Inhoud add_key_success=De SSH-sleutel '%s' is toegevoegd. add_gpg_key_success=De GPG-sleutel '%s' is toegevoegd. +add_principal_success=De SSH-certificaat verantwoordelijke '%s' is toegevoegd. delete_key=Verwijder ssh_key_deletion=Verwijder SSH-sleutel gpg_key_deletion=Verwijder GPG-sleutel +ssh_principal_deletion=Verwijder de SSH-certificaat verantwoordelijke ssh_key_deletion_desc=Als je een SSH-sleutel verwijdert, heb er geen toegang meer mee. Doorgaan? gpg_key_deletion_desc=Als je een GPG-sleutel verwijdert, kunnen hiermee ondertekende commits niet meer geverifieerd worden. Doorgaan? +ssh_principal_deletion_desc=Als je een SSH-certificaat verandtwoordelijke verwijdert, heeft deze geen toegang meer tot je account. Doorgaan? ssh_key_deletion_success=De SSH-sleutel is verwijderd. gpg_key_deletion_success=De GPG-sleutel is verwijderd. +ssh_principal_deletion_success=De verantwoordelijke is verwijderd. add_on=Toegevoegd op valid_until=Geldig tot en met valid_forever=Voor altijd geldig @@ -515,10 +546,10 @@ can_read_info=Lezen can_write_info=Schrijven key_state_desc=Deze sleutel werd gebruikt in de laatste 7 dagen token_state_desc=Dit token werd gebruikt in de laatste 7 dagen +principal_state_desc=Deze verantwoordelijke werd gebruikt in de laatste 7 dagen show_openid=Tonen op profiel hide_openid=Verbergen van profiel ssh_disabled=SSH uitgeschakeld - manage_social=Beheer gekoppelde sociale accounts social_desc=Deze sociale accounts zijn verbonden aan je Gitea-account. Zorg er voor dat je ze allemaal herkent, omdat ermee ingelogd kan worden op je Gitea-account. unbind=Ontkoppelen @@ -664,7 +695,23 @@ pick_reaction=Kies je reactie reactions_more=en %d meer unit_disabled=De sitebeheerder heeft deze repositorie sectie uitgeschakeld. language_other=Andere - +adopt_search=Voer gebruikersnaam in om te zoeken naar niet-geadopteerde repositories... (laat leeg om alles te vinden) +adopt_preexisting_label=Bestanden adopteren +adopt_preexisting=Bestaamde bestanden adopteren +adopt_preexisting_content=Maak een repository van %s +adopt_preexisting_success=Bestanden geadopteerd en repository gemaakt van %s +delete_preexisting_label=Verwijderen +delete_preexisting=Verwijder reeds bestaande bestanden +delete_preexisting_content=Verwijder bestanden in %s +delete_preexisting_success=Niet-geadopteerde bestanden verwijderd in %s + +desc.private=Privé +desc.public=Openbaar +desc.private_template=Privé sjabloon +desc.public_template=Sjabloon +desc.internal=Interne +desc.internal_template=Intern sjabloon +desc.archived=Gearchiveerd template.items=Sjabloon items template.git_content=Git inhoud (standaard Branch) @@ -686,14 +733,17 @@ form.name_reserved=Repositorienaam '%s' is gereserveerd. form.name_pattern_not_allowed=Het patroon '%s' is niet toegestaan in de naam van een repository. need_auth=Authenticatie benodigd om te klonen -migrate_type=Migratie type -migrate_type_helper=Deze repository zal een kopie zijn +migrate_options=Migratie opties +migrate_service=Migratie Service +migrate_options_mirror_helper=Deze repository zal een kopie zijn +migrate_options_mirror_disabled=Uw sitebeheerder heeft nieuwe mirrors uitgeschakeld. migrate_items=Migratie Items migrate_items_wiki=Wiki migrate_items_milestones=Mijlpalen migrate_items_labels=Labels migrate_items_issues=Issues migrate_items_pullrequests=Pull requests +migrate_items_merge_requests=Samenvoegen verzoeken migrate_items_releases=Releases migrate_repo=Migreer repository migrate.clone_address=Migreer / kloon van URL @@ -703,17 +753,23 @@ migrate.permission_denied=U bent niet gemachtigd om deze lokale repositories te migrate.invalid_local_path=Het lokale pad is ongeldig, bestaat niet of is geen map. migrate.failed=Migratie is mislukt: %v migrate.lfs_mirror_unsupported=Het kopiëren van LFS-objecten wordt niet ondersteund - gebruik in plaats daarvan 'git lfs fetch --all' en 'git lfs push --all'. -migrate.migrate_items_options=Voer bij het migreren vanuit github een gebruikersnaam in en migratieopties zullen worden weergegeven. +migrate.migrate_items_options=Toegangstoken is vereist om extra items te migreren migrated_from=Gemigreerd van %[2]s migrated_from_fake=Gemigreerd van %[1]s +migrate.migrate=Migreer van %s migrate.migrating=Migreren van %s... migrate.migrating_failed=Migreren van %s is mislukt. +migrate.github.description=Gegevens migreren van Github.com of Github Enterprise. +migrate.git.description=Migreren of Mirroring git gegevens van Git services +migrate.gitlab.description=Gegevens migreren van GitLab.com of Self-Hosted gitlab server. mirror_from=kopie van forked_from=geforked van generated_from=gegenereerd van fork_from_self=U kunt geen repository forken die u al heeft. fork_guest_user=Log in om deze repository te vorken. +watch_guest_user=Log in om deze repository te bekijken. +star_guest_user=Log in om een ster aan deze repository toe te kennen. copy_link=Kopieer copy_link_success=De link is gekopieerd copy_link_error=Druk op ⌘-C of Ctrl-C om te kopiëren @@ -736,11 +792,13 @@ code=Code code.desc=Toegang tot broncode, bestanden, commits en branches. branch=Branch tree=Tree +clear_ref=`Huidige referentie wissen` filter_branch_and_tag=Filter op branch of tag branches=Branches tags=Labels issues=Kwesties pulls=Pull-aanvragen +project_board=Projecten labels=Labels org_labels_desc=Organisatielabel dat gebruikt kan worden met alle repositories onder deze organisatie org_labels_desc_manage=beheren @@ -759,6 +817,8 @@ audio_not_supported_in_browser=Je browser ondersteunt de HTML5 'audio'-tag niet. stored_lfs=Opgeslagen met Git LFS symbolic_link=Symbolic link commit_graph=Commit grafiek +commit_graph.monochrome=Monochroom +commit_graph.color=Kleur blame=Blame normal_view=Normale weergave line=regel @@ -808,9 +868,7 @@ editor.file_already_exists=Een bestand met de naam '%s' bestaat al in deze repos editor.commit_empty_file_header=Commit een leeg bestand editor.commit_empty_file_text=Het bestand dat u wilt committen is leeg. Doorgaan? editor.no_changes_to_show=Er zijn geen wijzigingen om weer te geven. -editor.fail_to_update_file=Update/maken van bestand '%s' is mislukt: %v editor.push_rejected_no_message=De wijziging is afgewezen door de server zonder bericht. Controleer githooks. -editor.push_rejected=De wijziging is afgewezen door de server met het volgende bericht:
%s
Controleer githooks. editor.add_subdir=Een map toevoegen… editor.unable_to_upload_files=Uploaden van bestand '%s' is mislukt: %v editor.upload_file_is_locked=Bestand '%s' is vergrendeld door %s. @@ -840,10 +898,40 @@ commits.gpg_key_id=GPG sleutel-ID ext_issues=Ext. issues ext_issues.desc=Koppelen aan een externe kwestie-tracker. +projects=Projecten +projects.desc=Beheer issues en pulls in projectborden. +projects.create=Project aanmaken +projects.title=Titel +projects.new=Nieuw project +projects.new_subheader=Coördineer, track en update uw werk op één plek, dus projecten blijven transparant en op schema. +projects.create_success=Het project '%s' is aangemaakt. +projects.deletion=Project verwijderen +projects.deletion_desc=Als een project wordt verwijdert, wordt deze van alle gerelateerde kwesties verwijderd. Doorgaan? +projects.deletion_success=Het project is verwijderd. +projects.edit=Projecten bewerken +projects.edit_subheader=Projecten organiseren kwesties en houden voortgang bij. +projects.modify=Project bijwerken +projects.edit_success=Project '%s' is bijgewerkt. +projects.type.none=Geen +projects.type.basic_kanban=Basis Kanban +projects.type.bug_triage=Bug Triage +projects.template.desc=Project sjabloon +projects.template.desc_helper=Selecteer een projecttemplate om aan de slag te gaan +projects.type.uncategorized=Ongecategoriseerd +projects.board.edit=Bord bewerken +projects.board.edit_title=Nieuwe boardnaam +projects.board.new_title=Nieuwe boardnaam +projects.board.new_submit=Versturen +projects.board.new=Nieuw bord +projects.board.delete=Verwijder bord +projects.board.deletion_desc=Als een projectbord wordt verwijdert, worden alle gerelateerde kwesties naar 'Ongecategoriseerd' verplaatst. Doorgaan? +projects.open=Open +projects.close=Sluiten issues.desc=Organiseer bugrapporten, taken en mijlpalen. issues.filter_assignees=Filter verantwoordelijke issues.filter_milestones=Filter mijlpaal +issues.filter_projects=Project filteren issues.filter_labels=Label filteren issues.filter_reviewers=Beoordeler filteren issues.new=Nieuw probleem @@ -852,6 +940,12 @@ issues.new.labels=Labels issues.new.add_labels_title=Labels toepassen issues.new.no_label=Geen label issues.new.clear_labels=Verwijder labels +issues.new.projects=Projecten +issues.new.add_project_title=Project instellen +issues.new.clear_projects=Projecten wissen +issues.new.no_projects=Geen project +issues.new.open_projects=Open projecten +issues.new.closed_projects=Gesloten Projecten issues.new.no_items=Geen items issues.new.milestone=Mijlpaal issues.new.add_milestone_title=Stel mijlpaal in @@ -865,6 +959,9 @@ issues.new.clear_assignees=Verwijder toegewezen aan issues.new.no_assignees=Niet toegewezen issues.new.no_reviewers=Geen beoordelaars issues.new.add_reviewer_title=Beoordeling aanvragen +issues.choose.get_started=Ga aan de slag +issues.choose.blank=Standaard +issues.choose.blank_about=Maak een issue aan via een standaard sjabloon. issues.no_ref=Geen Branch/Tag gespecificeerd issues.create=Maak probleem issues.new_label=Nieuw Label @@ -879,9 +976,13 @@ issues.label_templates.fail_to_load_file=Kan het labelsjabloonbestand '%s' niet issues.add_label_at=voegde het
%s
label %s toe issues.remove_label_at=verwijderde het
%s
label %s issues.add_milestone_at=`heeft dit %[2]s aan de mijlpaal %[1]s toegevoegd` +issues.add_project_at=`heeft dit toegevoegd aan het %s project %s` issues.change_milestone_at='mijlpaal bewerkt van %s %s %s' +issues.change_project_at=`heeft het project gewijzigd van %s naar %s %s` issues.remove_milestone_at=' %s is verwijderd uit de %s mijlpaal' +issues.remove_project_at=`verwijderd uit het %s project %s` issues.deleted_milestone=` (verwijderd)` +issues.deleted_project=` (verwijderd)` issues.self_assign_at=`heeft dit %s aan zichzelf toegewezen` issues.add_assignee_at=`was toegekend door %s %s` issues.remove_assignee_at=`is niet toegewezen door %s %s` @@ -951,11 +1052,16 @@ issues.reopened_at=`heropende dit probleem %[2]s issues.commit_ref_at=`verwees naar dit probleem vanuit commit %[2]s'` issues.ref_issue_from=`refereerde aan dit issue %[4]s %[2]s` issues.ref_pull_from=`refereerde aan deze pull request %[4]s %[2]s` +issues.ref_closing_from=`verwees naar een pull request %[4]s dat het issue zal sluiten %[2]s` +issues.ref_reopening_from=`verwees naar een pull request %[4]s dat dit issue heropent %[2]s ` +issues.ref_closed_from=`sloot dit issue %[4]s %[2]s` +issues.ref_reopened_from=`heropende dit issue %[4]s %[2]s` issues.ref_from=`van %[1]s` issues.poster=Poster issues.collaborator=Medewerker issues.owner=Eigenaar issues.re_request_review=Opnieuw aanvragen review +issues.is_stale=Er zijn wijzigingen aangebracht in deze PR sinds deze beoordeling issues.remove_request_review=Verwijder beoordelingsverzoek issues.remove_request_review_block=Kan beoordelingsverzoek niet verwijderen issues.sign_in_require_desc=Log in om deel te nemen aan deze discussie. @@ -1025,6 +1131,9 @@ issues.due_date=Vervaldatum issues.invalid_due_date_format=Het formaat van de deadline is moet 'jjjj-mm-dd' zijn. issues.error_modifying_due_date=Deadline aanpassen mislukt. issues.error_removing_due_date=Deadline verwijderen mislukt. +issues.push_commit_1=toegevoegd %d commit %s +issues.push_commits_n=toegevoegd %d commits %s +issues.force_push_codes=`geforceerd-pushed %[1]s van %[2]s naar %[4]s %[6]s` issues.due_date_form=jjjj-mm-dd issues.due_date_form_add=Vervaldatum toevoegen issues.due_date_form_edit=Bewerk @@ -1077,6 +1186,7 @@ issues.review.remove_review_request_self=beoordeling geweigerd %s issues.review.pending=In behandeling issues.review.review=Review issues.review.reviewers=Reviewers +issues.review.outdated=Verouderd issues.review.show_outdated=Toon verouderd issues.review.hide_outdated=Verouderde verbergen issues.review.show_resolved=Toon afgehandeld @@ -1084,6 +1194,7 @@ issues.review.hide_resolved=Verbergen afgehandeld issues.review.resolve_conversation=Gesprek oplossen issues.review.un_resolve_conversation=Gesprek niet oplossen issues.review.resolved_by=markeerde dit gesprek als opgelost +issues.assignee.error=Niet alle aangewezen personen zijn toegevoegd vanwege een onverwachte fout. pulls.desc=Schakel pull-aanvragen en code-beoordelingen in. pulls.new=Nieuwe Pull aanvraag @@ -1103,12 +1214,15 @@ pulls.tab_conversation=Discussie pulls.tab_commits=Commits pulls.tab_files=Bestanden gewijzigd pulls.reopen_to_merge=Heropen dit pull request aub om een een merge actie uit te voeren. +pulls.cant_reopen_deleted_branch=Deze pull-aanvraag kan niet opnieuw worden geopend omdat de branch is verwijderd. pulls.merged=Samengevoegd +pulls.merged_as=De pull request is samengevoegd als %[2]s. pulls.is_closed=Deze pull-aanvraag is gesloten. pulls.has_merged=Deze pull-aanvraag is al samengevoegd. pulls.title_wip_desc=`Start de titel met %s om te voorkomen dat deze pull-aanvraag per ongeluk wordt samengevoegd.` pulls.cannot_merge_work_in_progress=Deze pull-aanvraag is als "work in progress" gemarkeerd. Verwijder de %s-prefix van de titel zodra hij klaar is pulls.data_broken=Deze pull-aanvraag is ongeldig wegens missende fork-informatie. +pulls.files_conflicted=Dit pull request heeft wijzigingen die strijdig zijn met de doel branch. pulls.is_checking=Controle op samenvoegingsconflicten is nog bezig. Probeer later nog een keer. pulls.required_status_check_failed=Sommige vereiste controles waren niet succesvol. pulls.required_status_check_missing=Er ontbreken enkele vereiste controles. @@ -1116,6 +1230,8 @@ pulls.required_status_check_administrator=Als een beheerder kunt u deze pull-aan pulls.blocked_by_approvals=Deze pull-aanvraag heeft nog niet genoeg goedkeuringen. %d van de %d goedkeuringen zijn gegeven. pulls.blocked_by_rejection=Deze pull-aanvraag heeft wijzigingen aangevraagd door een officiële beoordelaar. pulls.blocked_by_outdated_branch=Deze pull-aanvraag is geblokkeerd omdat het verouderd is. +pulls.blocked_by_changed_protected_files_1=Deze pull-aanvraag is geblokkeerd omdat het een beschermd bestand veranderd: +pulls.blocked_by_changed_protected_files_n=Deze pull-aanvraag is geblokkeerd omdat het beschermde bestanden veranderd: pulls.can_auto_merge_desc=Dit pull-request kan automatisch samengevoegd worden. pulls.cannot_auto_merge_desc=Deze pull-aanvraag kan niet automatisch worden samengevoegd wegens conflicten. pulls.cannot_auto_merge_helper=Voeg handmatig samen om de conflicten op te lossen. @@ -1139,7 +1255,9 @@ pulls.rebase_merge_commit_pull_request=Rebase en voeg samen (--no-ff) pulls.squash_merge_pull_request=Squash en Merge pulls.require_signed_wont_sign=De branch heeft ondertekende commits nodig, maar deze merge zal niet worden ondertekend pulls.invalid_merge_option=Je kan de samenvoegingsoptie niet gebruiken voor deze pull-aanvraag. -pulls.push_rejected=Samenvoegen mislukt: De push is afgewezen met het volgende bericht:
%s
Controleer de githooks voor deze repository +; %[2]s
%[3]s
+pulls.unrelated_histories=Samenvoegen mislukt: de HEAD en base delen geen gemeenschappelijke geschiedenis. Tip: Probeer een andere strategie +pulls.merge_out_of_date=Samenvoegen mislukt: Tijdens het samenvoegen is de basis bijgewerkt. Tip: Probeer het opnieuw. pulls.push_rejected_no_message=Samenvoegen mislukt: De push is afgewezen maar er was geen extern bericht.
Controleer de githooks voor deze repository pulls.open_unmerged_pull_exists=`Je kan deze pull-aanvraag niet opnieuw openen omdat er een andere (#%d) met identieke eigenschappen open staat.` pulls.status_checking=Sommige controles zijn in behandeling @@ -1147,6 +1265,8 @@ pulls.status_checks_success=Alle checks waren succesvol pulls.status_checks_warning=Sommige controles hebben waarschuwingen gerapporteerd pulls.status_checks_failure=Sommige controles zijn mislukt pulls.status_checks_error=Sommige controles hebben foutmeldingen gerapporteerd +pulls.status_checks_requested=Vereist +pulls.status_checks_details=Details pulls.update_branch=Update branch pulls.update_branch_success=Branch update is geslaagd pulls.update_not_allowed=Je hebt geen toestemming om branch bij te werken @@ -1158,6 +1278,7 @@ milestones.new=Nieuwe mijlpaal milestones.open_tab=%d geopend milestones.close_tab=%d gesloten milestones.closed=%s werd gesloten +milestones.update_ago=%s dagen geleden bijgewerkt milestones.no_due_date=Geen vervaldatum milestones.open=Open milestones.close=Sluit @@ -1197,6 +1318,7 @@ signing.wont_sign.basesigned=De samenvoeging wordt niet ondertekend omdat de bas signing.wont_sign.headsigned=De samenvoeging wordt niet ondertekend omdat de hoofd-commit niet ondertekend is signing.wont_sign.commitssigned=De samenvoeging wordt niet ondertekend omdat alle bijbehorende commits niet ondertekend zijn signing.wont_sign.approved=De samenvoeging wordt niet ondertekend omdat de PR niet is goedgekeurd +signing.wont_sign.not_signed_in=U bent niet ingelogd ext_wiki=Ext. wiki ext_wiki.desc=Koppelen aan een externe wiki. @@ -1342,20 +1464,49 @@ settings.pulls.allow_merge_commits=Samenvoegen van commits inschakelen settings.pulls.allow_rebase_merge=Rebasen om samen te voegen inschakelen settings.pulls.allow_rebase_merge_commit=Samenvoegen met expliciete samenvoegingscommits (--no-ff) inschakelen settings.pulls.allow_squash_commits="Squash"-en om samen te voegen inschakelen +settings.projects_desc=Repository-projecten inschakelen settings.admin_settings=Beheerdersinstellingen settings.admin_enable_health_check=Repositoryintegriteitschecks ingeschakelen (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch=Sluit een issue via een commit gemaakt in een niet-standaard branch settings.danger_zone=Gevaren zone settings.new_owner_has_same_repo=De nieuwe eigenaar heeft al een repository met deze naam settings.convert=Converteren naar gewone repository settings.convert_desc=U kunt deze kopie converteren naar een gewone repository. Dit kan niet ongedaan worden gemaakt. settings.convert_notices_1=Deze operatie zet de kopie repository om in een gewone repository en dit kan niet ongedaan gemaakt worden. settings.convert_confirm=Converteer Repository +settings.convert_succeed=De mirror is omgezet in een gewone repository. +settings.convert_fork=Converteren naar gewone repository +settings.convert_fork_desc=U kunt deze fork converteren naar een gewone repository. Dit kan niet ongedaan worden gemaakt. +settings.convert_fork_notices_1=Deze operatie zet de fork om in een gewone repository en dit kan niet ongedaan gemaakt worden. +settings.convert_fork_confirm=Converteer Repository +settings.convert_fork_succeed=De fork is omgezet in een gewone repository. settings.transfer=Eigendom overdragen +settings.transfer_desc=Draag deze repo over aan een andere gebruiker of een organisatie waar u beheerders rechten heeft. +settings.transfer_notices_1=- U verliest de toegang tot de repository als u deze overdraagt aan een individuele gebruiker. +settings.transfer_notices_2=- U behoudt toegang tot de repository als u deze overdraag aan een organisatie waar u (mede)eigenaar van bent. settings.transfer_form_title=Voer de repository naam in als bevestiging: +settings.signing_settings=Ondertekening verificatie Instellingen +settings.trust_model=Handtekening vertrouwensmodel +settings.trust_model.default=Standaard vertrouwensmodel +settings.trust_model.default.desc=Gebruik het standaard repository vertrouwensmodel voor deze installatie. +settings.trust_model.collaborator=Medewerker +settings.trust_model.collaborator.long=Medewerker: Vertrouw handtekeningen door medewerkers +settings.trust_model.collaborator.desc=Geldige handtekeningen door medewerkers van deze repository worden gemarkeerd als "vertrouwd" - (ongeacht of ze overeenkomen met de committer of niet). Anders worden geldige handtekeningen gemarkeerd als "niet vertrouwd" als de handtekening overeenkomt met de committer en "niet overeenkomend" als dat niet het geval is. +settings.trust_model.committer=Committer +settings.trust_model.committer.long=Committer: Vertrouw handtekeningen die overeenkomen met committers (Dit komt overeen met GitHub en zal Gitea ondertekende commits dwingen om Gitea als de committer te hebben) +settings.trust_model.committer.desc=Geldige handtekeningen worden alleen gemarkeerd als "vertrouwd" als ze overeenkomen met de committer, anders worden ze gemarkeerd als "niet overeenkomend". Dit zal Gitea dwingen om de committer te zijn aan ondertekende commits met de eigenlijke committer gemarkeerd als Co-Authored-By: en Co-Committed-By: trailer in de commit. De standaard Gitea-sleutel moet overeenkomen met een gebruiker in de database. +settings.trust_model.collaboratorcommitter=Medewerker+Committer +settings.trust_model.collaboratorcommitter.long=Medewerker+Committer: Vertrouw handtekeningen door medewerkers die overeenkomen met de committer settings.wiki_delete=Wiki-gegevens verwijderen +settings.wiki_delete_desc=Het verwijderen van wiki-gegevens is permanent en kan niet ongedaan worden gemaakt. +settings.wiki_delete_notices_1=- Dit zal de repository wiki voor %s permanent verwijderen en uitschakelen. settings.confirm_wiki_delete=Wiki-gegevens verwijderen +settings.wiki_deletion_success=De repository wiki gegevens zijn verwijderd. settings.delete=Verwijder deze repository +settings.delete_desc=Het verwijderen van een repository is permanent en kan niet ongedaan worden gemaakt. settings.delete_notices_1=- Deze bewerking kan NIET ongedaan gemaakt worden. +settings.delete_notices_2=- Deze bewerking zal permanent de %s repository verwijderen, inclusief code, issues, opmerkingen, wikigegevens en instellingen voor medewerkers. +settings.delete_notices_fork_1=- Forks van deze repository zullen onafhankelijk worden na verwijdering. settings.deletion_success=De repository is verwijderd. settings.update_settings_success=De repository-instellingen zijn bijgewerkt. settings.transfer_owner=Nieuwe eigenaar @@ -1364,29 +1515,44 @@ settings.transfer_succeed=De repository is overgedragen. settings.confirm_delete=Verwijder repository settings.add_collaborator=Medewerker toevoegen settings.add_collaborator_success=De medewerker is toegevoegd. +settings.add_collaborator_inactive_user=Kan geen inactieve gebruiker toevoegen als medewerker. +settings.add_collaborator_duplicate=De collaborator is al toegevoegd aan deze repository. settings.delete_collaborator=Verwijder settings.collaborator_deletion=Verwijder medewerker +settings.collaborator_deletion_desc=Het verwijderen van een collaborator zal hun toegang tot deze repository intrekken. Doorgaan? +settings.remove_collaborator_success=De medewerker is verwijderd. settings.search_user_placeholder=Zoek gebruiker… +settings.org_not_allowed_to_be_collaborator=Organisaties kunnen niet worden toegevoegd als een medewerker. +settings.change_team_access_not_allowed=Het veranderen van team toegang voor de repository is beperkt tot de organisatie eigenaar +settings.team_not_in_organization=Het team zit niet in dezelfde organisatie als de repository settings.teams=Teams settings.add_team=Team toevoegen +settings.add_team_duplicate=Team heeft al de repository +settings.add_team_success=Het team heeft nu toegang tot de repository. settings.search_team=Zoek team… settings.change_team_permission_tip=Teammachtiging is ingesteld op de team-instellingspagina en kan niet per repository worden gewijzigd settings.delete_team_tip=Dit team heeft toegang tot alle repositories en kan niet verwijderd worden +settings.remove_team_success=De toegang van het team tot de repository is verwijderd. settings.add_webhook=Webhook toevoegen +settings.add_webhook.invalid_channel_name=Webhook channel naam mag niet leeg zijn en mag niet alleen een # teken bevatten. settings.hooks_desc=Webhooks maken automatisch een HTTP POST verzoek naar een server wanneer bepaalde Gitea-gebeurtenissen geactiveerd worden. Lees meer in de webhooks gids. settings.webhook_deletion=Verwijder webhook +settings.webhook_deletion_desc=Verwijderen van een webhook verwijdert de instellingen en de geschiedenis van afleveringen. Doorgaan? settings.webhook_deletion_success=Webhook is verwijderd. settings.webhook.test_delivery=Test-bezorging settings.webhook.test_delivery_desc=Test deze webhook met een nep-gebeurtenis. +settings.webhook.test_delivery_success=Een nep gebeurtenis is toegevoegd aan de wachtrij. Het kan enkele seconden duren voordat het in de geschiedenis van afleveringen wordt weergegeven. settings.webhook.request=Verzoek settings.webhook.response=Antwoord settings.webhook.headers=Headers settings.webhook.payload=Inhoud settings.webhook.body=Inhoud +settings.githooks_desc=Git hooks worden aangedreven door Git zelf. U kunt hieronder hook bestanden bewerken om aangepaste acties op te zetten. settings.githook_edit_desc=Als haak niet actief is, zal monster inhoud worden gepresenteerd. Verlof inhoud leeg zal deze haak uitschakelen. settings.githook_name=Haak naam settings.githook_content=Haak inhoud settings.update_githook=Haak bijwerken +settings.add_webhook_desc=Gitea stuurt POST verzoeken met een bepaald inhoudstype naar de doel-URL. Lees meer in de webhooks gids. settings.payload_url=Doel URL settings.http_method=HTTP-methode settings.content_type=POST inhoudstype @@ -1404,6 +1570,7 @@ settings.event_header_repository=Repository gebeurtenissen settings.event_create=Creëer settings.event_create_desc=Branch, of tag aangemaakt. settings.event_delete=Verwijder +settings.event_delete_desc=Branch of tag verwijderd. settings.event_fork=Fork settings.event_fork_desc=Repository geforked. settings.event_release=Release @@ -1412,61 +1579,118 @@ settings.event_push=Push settings.event_push_desc=Git push naar een repository. settings.event_repository=Repository settings.event_repository_desc=Repository gemaakt of verwijderd. +settings.event_header_issue=Issue gebeurtenissen settings.event_issues=Kwesties +settings.event_issues_desc=Issue geopend, gesloten, heropend of bewerkt. +settings.event_issue_assign=Probleem toegekend +settings.event_issue_assign_desc=Issue toegewezen of niet-toegewezen. settings.event_issue_label=Issue gelabeld +settings.event_issue_label_desc=Issue-labels bijgewerkt of verwijderd. +settings.event_issue_milestone=Issue gemilestoned +settings.event_issue_milestone_desc=Issue gemilestoned of gedemilestoned. +settings.event_issue_comment=Issue commentaar settings.event_issue_comment_desc=Issue reactie aangemaakt, bewerkt of verwijderd. +settings.event_header_pull_request=Pull Request Events settings.event_pull_request=Pull request +settings.event_pull_request_desc=Pull request geopend, gesloten, heropend of bewerkt. +settings.event_pull_request_assign=Pull request toegewezen +settings.event_pull_request_assign_desc=Pull request toegewezen of niet-toegewezen. +settings.event_pull_request_label=Pull-aanvraag gelabeld +settings.event_pull_request_label_desc=Pull request labels bijgewerkt of gewist. +settings.event_pull_request_milestone=Pull Request gemilestoned +settings.event_pull_request_milestone_desc=Pull Reguest gemilestoned of gedemilestoned. +settings.event_pull_request_comment=Pull request opmerking +settings.event_pull_request_comment_desc=Pull request commentaar gemaakt, bewerkt of verwijderd. +settings.event_pull_request_review=Pull request gereviewed +settings.event_pull_request_review_desc=Pull request goedgekeurd, afgewezen of review commentaar. +settings.event_pull_request_sync=Pull request gesynchroniseerd +settings.event_pull_request_sync_desc=Pull request gesynchroniseerd. settings.branch_filter=Branch filter settings.branch_filter_desc=Branch whitelist voor push, branch creatie en branch verwijdering events, gespecificeerd als glob patroon. Indien leeg of * worden events voor alle branches gerapporteerd. Zie github.com/gobwas/glob documentatie voor syntaxis. Voorbeelden: master, {master,release*}. settings.active=Actief +settings.active_helper=Informatie over geactiveerde gebeurtenissen wordt naar deze webhook URL gestuurd. settings.add_hook_success=De webhook is toegevoegd. settings.update_webhook=Bewerk webhook settings.update_hook_success=Webhook is bijgewerkt. settings.delete_webhook=Verwijder webhook settings.recent_deliveries=Recente bezorgingen settings.hook_type=Type hook +settings.add_slack_hook_desc=Integreer Slack in uw repository. settings.slack_token=Slack token settings.slack_domain=Slack domein settings.slack_channel=Slack kanaal +settings.add_discord_hook_desc=Integreer Discord in uw repository. +settings.add_dingtalk_hook_desc=Integreer Dingtalk in uw repository. +settings.add_telegram_hook_desc=Integreer Telegram in uw repository. settings.add_matrix_hook_desc=Integreer Matrix in uw repository. +settings.add_msteams_hook_desc=Integreer Microsoft Teams in uw repository. settings.add_feishu_hook_desc=Integreer Feishu in uw repository. settings.deploy_keys=Installeer sleutels settings.add_deploy_key=Toevoegen deploy sleutel +settings.deploy_key_desc=Deploy keys hebben alleen-lezen pull-toegang tot de repository. settings.is_writable=Schrijf toegang inschakelen +settings.is_writable_info=Sta deze deploy toets toe om te pushen naar de repository. settings.no_deploy_keys=Er zijn nog geen deploy sleutels. settings.title=Titel settings.deploy_key_content=Inhoud +settings.key_been_used=Een deploy key met identieke inhoud is al in gebruik. +settings.key_name_used=Een deploy sleutel met dezelfde naam bestaat al. +settings.add_key_success=De deploy sleutel '%s' is toegevoegd. settings.deploy_key_deletion=Verwijder deploy sleutel +settings.deploy_key_deletion_desc=Het verwijderen van een deploy sleutel zal de toegang tot deze repository intrekken. Doorgaan? +settings.deploy_key_deletion_success=De deploy-sleutel is verwijderd. settings.branches=Branches settings.protected_branch=Branch bescherming settings.protected_branch_can_push=Push toestaan? settings.protected_branch_can_push_yes=U mag pushen settings.protected_branch_can_push_no=U mag niet pushen +settings.branch_protection=Branch Bescherming voor branch '%s' settings.protect_this_branch=Branch bescherming inschakelen settings.protect_this_branch_desc=Voorkomt verwijdering en beperkt Git pushing en samenvoegen tot de branch. settings.protect_disable_push=Push uitschakelen settings.protect_disable_push_desc=Geen pushes zijn toegestaan in deze branch. settings.protect_enable_push=Push inschakelen +settings.protect_enable_push_desc=Iedereen met schrijftoegang heeft toegang om te pushen naar deze branch (maar niet force push). settings.protect_whitelist_committers=Whitelist Beperkte Push +settings.protect_whitelist_committers_desc=Alleen gewhiteliste gebruikers of teams mogen pushen naar deze branch (maar geen force push). +settings.protect_whitelist_deploy_keys=Whitelist deploy sleutels met schrijftoegang om te pushen. settings.protect_whitelist_users=Toegestane gebruikers voor push: settings.protect_whitelist_search_users=Zoek gebruiker… settings.protect_whitelist_teams=Toegestane teams voor push: settings.protect_whitelist_search_teams=Zoek teams… +settings.protect_merge_whitelist_committers=Samenvoegen whitelist inschakelen +settings.protect_merge_whitelist_committers_desc=Sta alleen gebruikers of teams van de whitelist toe om pull requests samen te voegen met deze branch. +settings.protect_merge_whitelist_users=Toegestane gebruikers voor samenvoegen: +settings.protect_merge_whitelist_teams=Toegestane teams voor samenvoegen: settings.protect_check_status_contexts=Status controle inschakelen +settings.protect_check_status_contexts_list=Status controles gevonden in de afgelopen week voor deze repository settings.protect_required_approvals=Vereiste goedkeuringen: +settings.protect_required_approvals_desc=Sta alleen toe om pull request samen te voegen met voldoende positieve beoordelingen. +settings.protect_approvals_whitelist_enabled=Beperk goedkeuringen tot gebruikers of teams op de whitelist +settings.protect_approvals_whitelist_enabled_desc=Alleen beoordelingen van gebruikers of teams op de whitelist zullen voor het vereiste aantal goedkeuringen tellen. Zonder een goedkeurings whitelist, tellen beoordelingen van iedereen met schrijfrechten mee voor het vereiste aantal goedkeuringen. +settings.protect_approvals_whitelist_users=Toegestane reviewers: +settings.protect_approvals_whitelist_teams=Toegestane teams voor beoordelingen: settings.dismiss_stale_approvals=Verouderde goedkeuringen afwijzen settings.dismiss_stale_approvals_desc=Wanneer nieuwe commits die de inhoud van het pull-verzoek veranderen, naar de branch worden gepusht, worden oude goedkeuringen verwijderd. settings.require_signed_commits=Ondertekende Commits vereisen +settings.require_signed_commits_desc=Weiger pushes naar deze branch als deze niet ondertekend of niet verifieerbaar is. settings.protect_protected_file_patterns=Beschermde bestandspatronen (gescheiden door een puntkomma '\;'): +settings.protect_protected_file_patterns_desc=Beschermde bestanden die niet direct gewijzigd mogen worden, zelfs als de gebruiker het recht heeft om bestanden in deze branch toe te voegen, te bewerken of te verwijderen. Meerdere patronen kunnen worden gescheiden met een puntkomma ('\;'). Zie github.com/gobwas/glob documentatie voor patroon syntaxis. Voorbeelden: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Bescherming aanzetten settings.delete_protected_branch=Bescherming uitzetten +settings.update_protect_branch_success=Branch bescherming voor branch '%s' is bijgewerkt. +settings.remove_protected_branch_success=Branch bescherming voor branch '%s' is uitgeschakeld. settings.protected_branch_deletion=Branch bescherming uitschakelen +settings.protected_branch_deletion_desc=Branch bescherming uitschakelen zorgt ervoor dat gebruikers met schrijfrechten naar de branch kunnen pushen. Doorgaan? settings.block_rejected_reviews=Samenvoegen van afgewezen beoordelingen blokkeren settings.block_rejected_reviews_desc=Samenvoegen zal niet mogelijk zijn wanneer er wijzigingen worden aangevraagd door officiële beoordelaars, zelfs niet als er genoeg goedkeuringen zijn. settings.block_outdated_branch=Samenvoegen blokkeren als pull request verouderd is settings.block_outdated_branch_desc=Samenvoegen is niet mogelijk als de hoofd branch achter loop op de basis branch. +settings.default_branch_desc=Selecteer een standaard repository branch voor pull requests en code commits: settings.choose_branch=Kies een branch… +settings.no_protected_branch=Er zijn geen beschermde branches. settings.edit_protected_branch=Bewerken +settings.protected_branch_required_approvals_min=Vereiste goedkeuringen kunnen niet negatief zijn. settings.bot_token=Bot Token settings.chat_id=Chat-ID settings.matrix.homeserver_url=Homeserver URL @@ -1475,10 +1699,24 @@ settings.matrix.access_token=Toegangstoken settings.matrix.message_type=Bericht type settings.archive.button=Repo archiveren settings.archive.header=Deze Repo archiveren +settings.archive.success=De repo is succesvol gearchiveerd. +settings.archive.error=Er is een fout opgetreden tijdens het archiveren van de repo. Zie het logboek voor meer informatie. +settings.archive.error_ismirror=U kunt geen gespiegelde repo archiveren. +settings.archive.branchsettings_unavailable=Branch instellingen zijn niet beschikbaar als de repo is gearchiveerd. settings.unarchive.button=Repo De-Archiveren settings.unarchive.header=Deze Repo de-archiveren +settings.unarchive.text=De-Archiveren van de repo herstelt zijn vermogen om commits en pushes te ontvangen, evenals nieuwe problemen en pull-requests. +settings.unarchive.success=De repo is met succes gede-archiveerd. +settings.unarchive.error=Er is een fout opgetreden tijdens het de-archiveren van de repo. Zie het logboek voor meer informatie. +settings.update_avatar_success=De repository avatar is bijgewerkt. settings.lfs=LFS +settings.lfs_filelist=LFS bestanden opgeslagen in deze repository +settings.lfs_no_lfs_files=Geen LFS bestanden opgeslagen in deze repository settings.lfs_findcommits=Vind commits +settings.lfs_lfs_file_no_commits=Geen Commits gevonden voor dit LFS-bestand +settings.lfs_noattribute=Dit pad heeft niet het vergrendelbare attribuut in de standaard branch +settings.lfs_delete=LFS-bestand met OID %s verwijderen +settings.lfs_delete_warning=Het verwijderen van een LFS bestand kan leiden tot 'object bestaat niet' fouten bij het uitchecken. Weet u het zeker? settings.lfs_findpointerfiles=Zoek pointer bestanden settings.lfs_locks=Vergrendeld settings.lfs_invalid_locking_path=Ongeldig pad: %s @@ -1487,7 +1725,9 @@ settings.lfs_lock_already_exists=Vergrendeling bestaat al: %s settings.lfs_lock=Vergrendel settings.lfs_lock_path=Bestandspad om te vergrendelen... settings.lfs_locks_no_locks=Geen Locks +settings.lfs_lock_file_no_exist=Vergrendeld bestand bestaat niet in de standaard branch settings.lfs_force_unlock=Forceer ontgrendelen +settings.lfs_pointers.found=%d blob-pointer(s) gevonden - %d gekoppeld, %d niet-gekoppeld (%d ontbreekt in de winkel) settings.lfs_pointers.sha=Blob SHA settings.lfs_pointers.oid=OID settings.lfs_pointers.inRepo=In Repo @@ -1508,6 +1748,8 @@ diff.show_split_view=Zij-aan-zij weergave diff.show_unified_view=Gecombineerde weergave diff.whitespace_button=Witregel diff.whitespace_show_everything=Toon alle wijzigingen +diff.whitespace_ignore_all_whitespace=Witruimte negeren bij het vergelijken van regels +diff.whitespace_ignore_amount_changes=Negeer veranderingen in de hoeveelheid witruimte diff.whitespace_ignore_at_eol=Negeren van wijzigingen in witruimte op EOL diff.stats_desc=%d gewijzigde bestanden met toevoegingen van %d en %d verwijderingen diff.bin=BIN @@ -1518,6 +1760,7 @@ diff.file_image_width=Breedte diff.file_image_height=Hoogte diff.file_byte_size=Grootte diff.file_suppressed=Diff onderdrukt omdat het te groot bestand +diff.too_many_files=Sommige bestanden worden niet getoond omdat te veel bestanden zijn gewijzigd in deze diff diff.comment.placeholder=Opmerking toevoegen diff.comment.markdown_info=Styling met markdown wordt ondersteund. diff.comment.add_single_comment=Één reactie toevoegen @@ -1530,6 +1773,8 @@ diff.review.placeholder=Commentaar controleren diff.review.comment=Opmerking diff.review.approve=Goedkeuren diff.review.reject=Wijzigingen aanvragen +diff.committed_by=gecommit door +diff.protected=Beveiligd releases.desc=Volg de projectversies en downloads. release.releases=Publicaties @@ -1538,6 +1783,8 @@ release.draft=Concept release.prerelease=Voorlopige versie release.stable=Stabiel release.edit=bewerken +release.ahead.commits=%d commits +release.ahead.target=aan %s sinds deze release release.source_code=Broncode release.tag_name=Tagnaam release.target=Doel @@ -1552,6 +1799,7 @@ release.save_draft=Concept opslaan release.edit_release=Update release release.delete_release=Verwijder release release.deletion=Verwijder release +release.deletion_success=De release is verwijderd. release.tag_name_already_exist=Een versie met deze naam bestaat al. release.tag_name_invalid=Labelnaam is niet geldig. release.downloads=Downloads @@ -1573,18 +1821,23 @@ branch.create_success=Branch '%s' is aangemaakt. branch.branch_already_exists=Branch '%s' bestaat al in deze repository. branch.deleted_by=Verwijderd door %s branch.restore_success=Branch '%s' is hersteld. +branch.restore_failed=Herstellen van branch '%s' is mislukt. branch.protected_deletion_failed=Branch '%s' is beschermd en kan niet verwijderd worden. branch.default_deletion_failed=Branch '%s' is de standaard branch en kan niet verwijderd worden. branch.restore=Herstel branch '%s' branch.download=Download Branch '%s' +branch.included_desc=Deze branch maakt deel uit van de standaard branch branch.included=Inbegrepen topic.manage_topics=Beheer topics topic.done=Klaar +topic.count_prompt=Je kunt niet meer dan 25 onderwerpen selecteren +topic.format_prompt=Onderwerpen moeten beginnen met een letter of nummer, kunnen streepjes bevatten ('-') en kunnen maximaal 35 tekens lang zijn. [org] org_name_holder=Organisatienaam org_full_name_holder=Volledige naam organisatie +org_name_helper=Organisatienamen horen kort en memorabel zijn. create_org=Nieuwe organisatie aanmaken repo_updated=Geupdate people=Mensen @@ -1596,10 +1849,15 @@ create_team=Maak team org_desc=Omschrijving team_name=Teamnaam team_desc=Omschrijving +team_name_helper=Teamnamen horen kort en memorabel zijn. +team_desc_helper=Beschrijf het doel of de rol van het team. team_access_desc=Repository toegang team_permission_desc=Machtiging +team_unit_desc=Toegang tot repository secties toestaan team_unit_disabled=(Uitgeschakeld) +form.name_reserved=Organisatienaam '%s' is gereserveerd. +form.name_pattern_not_allowed=Het patroon '%s' is niet toegestaan in een organisatienaam. form.create_org_not_allowed=U mag geen organisaties maken. settings=Instellingen @@ -1608,16 +1866,23 @@ settings.full_name=Volledige naam settings.website=Website settings.location=Locatie settings.permission=Machtigingen +settings.repoadminchangeteam=De repository admin kan toegang voor teams geven en verwijderen settings.visibility=Zichtbaarheid settings.visibility.public=Publiek +settings.visibility.limited=Beperkt (alleen zichtbaar voor ingelogde gebruikers) +settings.visibility.limited_shortname=Beperkt +settings.visibility.private=Privé (alleen zichtbaar voor organisatieleden) +settings.visibility.private_shortname=Privé settings.update_settings=Instellingen bijwerken settings.update_setting_success=Organisatie instellingen zijn succesvol bijgewerkt. +settings.change_orgname_prompt=Opmerking: het wijzigen van de organisatienaam verandert ook de URL van de organisatie. settings.update_avatar_success=De avatar van de organisatie is aangepast. settings.delete=Verwijder organisatie settings.delete_account=Verwijder deze organisatie settings.confirm_delete_account=Bevestig verwijdering settings.delete_org_title=Verwijder organisatie +settings.delete_org_desc=Deze organisatie zal permanent verwijderd worden. Doorgaan? settings.labels_desc=Voeg labels toe die kunnen worden gebruikt bij problemen voor alle repositories in deze organisatie. @@ -1637,28 +1902,40 @@ members.invite_now=Nu uitnodigen teams.join=Lid worden teams.leave=Vertlaat teams.can_create_org_repo=Maak repositories +teams.can_create_org_repo_helper=Leden kunnen nieuwe repositories aanmaken in de organisatie. De maker krijgt beheerder toegang tot de nieuwe repository. teams.read_access=Leestoegang +teams.read_access_helper=Leden kunnen teamrepositories bekijken en klonen. teams.write_access=Schrijf toegang +teams.write_access_helper=Leden kunnen lezen en pushen naar teamrepositories. teams.admin_access=Beheerder toegang teams.admin_access_helper=Leden kunnen van en naar repositories pullen, pushen, en er medewerkers aan toevoegen. teams.no_desc=Dit team heeft geen omschrijving teams.settings=Instellingen +teams.owners_permission_desc=Eigenaren hebben volledige toegang tot alle repositories en hebben beheerder rechten over de organisatie. teams.members=Team leden teams.update_settings=Instellingen bijwerken teams.delete_team=Verwijder team teams.add_team_member=Nieuwe team lid aanmaken teams.delete_team_title=Verwijder team +teams.delete_team_desc=Het verwijderen van een team heeft de toegang tot de repository van de leden. Doorgaan? teams.delete_team_success=Het team is verwijderd. +teams.read_permission_desc=Dit team heeft Lees rechten: leden kunnen repositories lezen en klonen. +teams.write_permission_desc=Dit team heeft Schrijf rechten: leden kunnen repositories lezen en push aanvragen verwerken. teams.admin_permission_desc=Dit team heeft beheersrechten: leden kunnen van en naar teamrepositories pullen, pushen, en er medewerkers aan toevoegen. teams.repositories=Teamrepositories teams.search_repo_placeholder=Repository zoeken… teams.remove_all_repos_title=Verwijder alle team repositories +teams.remove_all_repos_desc=Dit zal alle repositories uit het team verwijderen. teams.add_all_repos_title=Voeg alle repositories toe +teams.add_all_repos_desc=Dit zal alle repositories van de organisatie aan het team toevoegen. teams.add_nonexistent_repo=De opslagplaats die u probeert toe te voegen bestaat niet: maak deze eerst aan. teams.add_duplicate_users=Gebruiker is al een teamlid. +teams.repos.none=Er konden geen repositories worden benaderd door dit team. teams.members.none=Geen leden in dit team. teams.specific_repositories=Specifieke repositories teams.all_repositories=Alle repositories +teams.all_repositories_helper=Team heeft toegang tot alle repositories. Door dit te selecteren worden alle bestaande repositories aan het team toegevoegd. +teams.all_repositories_read_permission_desc=Dit team heeft Lees toegang tot alle repositories: leden kunnen repositories bekijken en klonen. [admin] dashboard=Overzicht @@ -1683,6 +1960,14 @@ dashboard.statistic_info=De Gitea database heeft %d gebruikers, %d dashboard.operation_name=Bewerking naam dashboard.operation_switch=Omschakelen dashboard.operation_run=Uitvoeren +dashboard.clean_unbind_oauth=Niet-verbonden OAuth verbindingen opschonen +dashboard.clean_unbind_oauth_success=Alle ongebonden OAuth verbindingen zijn verwijderd. +dashboard.task.started=Taak gestart: %[1]s +dashboard.task.process=Taak: %[1]s +dashboard.task.cancelled=Taak: %[1]s geannuleerd: %[3]s +dashboard.task.error=Fout in taak: %[1]s: %[3]s +dashboard.task.finished=Taak: %[1]s gestart door %[2]s is voltooid +dashboard.task.unknown=Onbekende taak: %[1]s dashboard.cron.started=Gestarte cron: %[1]s dashboard.cron.process=Cron: %[1]s dashboard.cron.cancelled=Cron: %s geannuleerd: %[3]s @@ -1692,6 +1977,7 @@ dashboard.delete_inactive_accounts=Verwijder alle niet geactiveerde accounts dashboard.delete_inactive_accounts.started=Verwijder alle niet geactiveerde accounts taak gestart. dashboard.delete_repo_archives=Verwijder alle repositories archieven dashboard.delete_repo_archives.started=Verwijder alle repositoryarchieven taak gestart. +dashboard.delete_missing_repos=Verwijder alle repositories waarvan hun Git bestanden missen dashboard.delete_missing_repos.started=Verwijder alle repositories die hun Git bestanden missen taak gestart. dashboard.delete_generated_repository_avatars=Verwijder gegenereerde repository avatars dashboard.update_mirrors=Mirrors bijwerken @@ -1700,8 +1986,13 @@ dashboard.check_repo_stats=Bekijk alle repository statistieken dashboard.archive_cleanup=Verwijder oude repositories archieven dashboard.deleted_branches_cleanup=Verwijderde branches opschonen dashboard.update_migration_poster_id=Werk migratie-poster IDs bij +dashboard.git_gc_repos=Voer garbage collectie uit voor alle repositories dashboard.resync_all_sshkeys=Werk de '.ssh/authorized_keys' bestand bij met Gitea SSH sleutels. dashboard.resync_all_sshkeys.desc=(Niet nodig voor de ingebouwde SSH server.) +dashboard.resync_all_sshprincipals=Update het '.ssh/authorized_principals' bestand met Gitea SSH verantwoordelijken. +dashboard.resync_all_sshprincipals.desc=(Niet nodig voor de ingebouwde SSH server.) +dashboard.resync_all_hooks=Opnieuw synchroniseren van pre-ontvangst, bewerk en post-ontvangst hooks voor alle repositories. +dashboard.reinit_missing_repos=Herinitialiseer alle ontbrekende Git repositories waarvoor records bestaan dashboard.sync_external_users=Externe gebruikersgegevens synchroniseren dashboard.server_uptime=Uptime server dashboard.current_goroutine=Huidige Goroutines @@ -1740,27 +2031,34 @@ users.full_name=Volledige naam users.activated=Geactiveerd users.admin=Beheerder users.restricted=Beperkt +users.2fa=2FA users.repos=Repos users.created=Aangemaakt users.last_login=Laatste keer ingelogd users.never_login=Nooit ingelogd +users.send_register_notify=Stuur gebruikersregistratie notificatie +users.new_success=Het gebruikersaccount '%s' is aangemaakt. users.edit=Bewerken users.auth_source=Authenticatiebron users.local=Lokaal users.auth_login_name=Authenticatie-loginnaam +users.password_helper=Laat het wachtwoord leeg om het ongewijzigd te houden. users.update_profile_success=Het gebruikersaccount is bijgewerkt. users.edit_account=Wijzig gebruikers account +users.max_repo_creation=Maximale aantal repositories users.max_repo_creation_desc=(Zet op -1 om de globale limiet te gebruiken) users.is_activated=Gebruikersaccount is geactiveerd users.prohibit_login=Inloggen uitschakelen users.is_admin=Is beheerder users.is_restricted=Is beperkt users.allow_git_hook=Mag Git hooks maken -users.allow_git_hook_tooltip=Git hooks worden uitgevoerd als de OS-gebruiker die Gitea draait en hetzelfde niveau van host toegang heeft +users.allow_git_hook_tooltip=Git haken worden uitgevoerd als de OS-gebruiker die Gitea uitvoert en zal hetzelfde niveau van host toegang hebben. Als gevolg daarvan hebben gebruikers met dit speciale Git Hook privilege toegang tot alle Gitea repositories en de door Gitea gebruikte database. Zij zijn dus ook in staat om Gitea beheerdersprivileges te verkrijgen. users.allow_import_local=Mag lokale repositories importeren users.allow_create_organization=Mag organisaties aanmaken users.update_profile=Update gebruikers account users.delete_account=Verwijder gebruikers account +users.still_own_repo=Deze gebruiker is nog steeds eigenaar van één of meerdere repositories. Verwijder of draag eerst deze repositories over. +users.still_has_org=Deze gebruiker is lid van een organisatie. Verwijder de gebruiker eerst uit alle organisaties. users.deletion_success=De gebruiker is verwijderd. emails.email_manage_panel=Gebruikers e-mail beheer @@ -1783,6 +2081,8 @@ orgs.members=Leden orgs.new_orga=Nieuwe organisatie repos.repo_manage_panel=Repositories beheren +repos.unadopted=Niet-geadopteerde repositories +repos.unadopted.no_more=Geen niet-geadopteerde repositories meer gevonden repos.owner=Eigenaar repos.name=Naam repos.private=Prive @@ -1813,19 +2113,28 @@ auths.host=Host auths.port=Poort auths.bind_dn=Binden DN auths.bind_password=Bind wachtwoord +auths.bind_password_helper=Waarschuwing: Dit wachtwoord wordt opgeslagen in platte tekst. Indien mogelijk gebruik dan een alleen-lezen account. auths.user_base=User Search Base auths.user_dn=User DN auths.attribute_username=Gebruikersnaam attribuut +auths.attribute_username_placeholder=Laat leeg om de gebruikersnaam in Gitea te gebruiken. auths.attribute_name=Voornaam attribuut auths.attribute_surname=Achternaam attribuut auths.attribute_mail=E-mail attribuut +auths.attribute_ssh_public_key=Publieke SSH sleutel attribuut auths.attributes_in_bind=Verkrijg attributes van de Bind DN context auths.allow_deactivate_all=Laat een leeg zoekresultaat toe om alle gebruikers te deactiveren +auths.use_paged_search=Gebruik Paged Search auths.search_page_size=Paginagrootte auths.filter=Gebruikersfilter auths.admin_filter=Beheerdersfilter auths.restricted_filter=Beperkt filter auths.restricted_filter_helper=Laat leeg om geen gebruikers als beperkt in te stellen. Gebruik een asterisk ('*') om alle gebruikers die niet overeenkomen met Admin Filter als beperkt in te stellen. +auths.verify_group_membership=Verifieer het groepslidmaatschap in LDAP +auths.group_search_base=Groep zoekbasis DN +auths.valid_groups_filter=Geldige groepen filter +auths.group_attribute_list_users=Groep Attribuut met lijst van gebruikers +auths.user_attribute_in_group=Gebruikerskenmerken vermeld in groep auths.smtp_auth=SMTP-authenticatietype auths.smtphost=SMTP host auths.smtpport=SMTP poort @@ -1836,25 +2145,41 @@ auths.pam_service_name=PAM servicenaam auths.oauth2_provider=OAuth2 Provider auths.oauth2_clientID=Client-ID (sleutel) auths.oauth2_clientSecret=Client-secret +auths.openIdConnectAutoDiscoveryURL=OpenID Connect Auto Discovery URL +auths.oauth2_use_custom_url=Aangepaste URL's gebruiken in plaats van standaard URL's auths.oauth2_tokenURL=Token URL auths.oauth2_authURL=Autorisatie URL auths.oauth2_profileURL=Profiel URL auths.oauth2_emailURL=E-mail URL auths.enable_auto_register=Activeer automatische registratie auths.sspi_auto_create_users=Automatisch gebruikers maken +auths.sspi_auto_create_users_helper=Toestaan dat de SSPI authenticatiemethode automatisch nieuwe accounts aanmaakt voor gebruikers die voor de eerste keer inloggen auths.sspi_auto_activate_users=Gebruikers automatisch activeren +auths.sspi_auto_activate_users_helper=SSPI authenticatie methode toestaan om automatisch nieuwe gebruikers te activeren +auths.sspi_strip_domain_names=Verwijder domeinnamen uit gebruikersnamen +auths.sspi_separator_replacement=Scheidingsteken voor gebruik in plaats van \, / en @ +auths.sspi_separator_replacement_helper=Het karakter dat moet worden gebruikt om de scheidingstekens van namen op downniveau logon te vervangen (bijv. de \ in "DOMAIN\user") en gebruikersverantwoordelijken (bijv. de @ in "user@example.org"). auths.sspi_default_language=Standaard gebruikerstaal auths.tips=Tips auths.tips.oauth2.general=OAuth2 authenticatie auths.tip.oauth2_provider=OAuth2 Provider auths.tip.nextcloud=Registreer een nieuwe OAuth consument op je installatie met behulp van het volgende menu "Instellingen -> Security -> OAuth 2.0 client" +auths.tip.dropbox=Maak een nieuwe applicatie aan op https://www.dropbox.com/developers/apps +auths.tip.facebook=Registreer een nieuwe applicatie op https://developers.facebook.com/apps en voeg het product "Facebook Login" toe +auths.tip.github=Registreer een nieuwe OAuth toepassing op https://github.com/settings/applications/new +auths.tip.gitlab=Registreer een nieuwe applicatie op https://gitlab.com/profile/applicaties +auths.tip.google_plus=Verkrijg OAuth2 client referenties van de Google API console op https://console.developers.google.com/ +auths.tip.openid_connect=Gebruik de OpenID Connect Discovery URL (/.well-known/openid-configuration) om de eindpunten op te geven auths.tip.yandex=Maak een nieuwe applicatie aan op https://oauth.yandex.com/client/new. Selecteer de volgende machtigingen van de "Yandex". assport API" sectie: "Toegang tot e-mailadres", "Toegang tot avatar" en "Toegang tot gebruikersnaam, voornaam en achternaam, geslacht" auths.edit=Authenticatiebron bewerken +auths.activated=Deze authenticatiebron is geactiveerd +auths.new_success=De authenticatie-bron '%s' is toegevoegd. auths.update_success=De authenticatie-bron is bijgewerkt. auths.update=Update authenticatiebron auths.delete=Authenticatiebron verwijderen auths.delete_auth_title=Authenticatiebron verwijderen auths.still_in_used=De authenticatiebron is nog in gebruik. Converteer of verwijder eerst een gebruiker met deze authenticatiebron. +auths.deletion_success=De authenticatie-bron is verwijderd. config.server_config=Serverconfiguratie config.app_name=Naam site @@ -1917,6 +2242,8 @@ config.default_allow_create_organization=Standaard toestaan om organisaties aan config.enable_timetracking=Tijdregistratie inschakelen config.default_enable_timetracking=Tijdregistratie standaard inschakelen config.no_reply_address=Verborgen e-maildomein +config.default_visibility_organization=Standaard zichtbaarheid voor nieuwe organisaties +config.default_enable_dependencies=Issue afhankelijkheden standaard inschakelen config.webhook_config=Webhook configuratie config.queue_length=Lengte van wachtrij @@ -1933,6 +2260,7 @@ config.mailer_use_sendmail=Gebruik Sendmail config.mailer_sendmail_path=Sendmail pad config.mailer_sendmail_args=Extra argumenten voor Sendmail config.mailer_sendmail_timeout=Sendmail time-out +config.test_email_placeholder=E-mail (bijv. test@example.com) config.send_test_mail=Test e-mail verzenden config.test_mail_failed=Verzenden van een testmail naar '%s' is mislukt: %v config.test_mail_sent=Test-email is verstuurd naar '%s'. @@ -1950,7 +2278,6 @@ config.session_config=Sessieconfiguratie config.session_provider=Sessieprovider config.provider_config=Provider config config.cookie_name=Cookie naam -config.enable_set_cookie=Set Cookie inschakelen config.gc_interval_time=GC interval time config.session_life_time=Sessie duur config.https_only=Alleen HTTPS @@ -1963,6 +2290,7 @@ config.enable_federated_avatar=Federated avatars toestaan config.git_config=Git configuratie config.git_disable_diff_highlight=Uitschakelen Diff Syntaxis-Highlight +config.git_max_diff_lines=Maximum Diff Lijnen (voor een enkel bestand) config.git_max_diff_files=Max Diff bestanden (om weer te geven) config.git_gc_args=GC Parameters config.git_migrate_timeout=Migratie time-out @@ -1975,6 +2303,7 @@ config.log_mode=Log-modus config.macaron_log_mode=Macaron logboek modus config.own_named_logger=Benoemde Logger config.routes_to_default_logger=Routes naar standaard Logger +config.go_log=Gebruikt Go Log (doorgestuurd naar standaard) config.router_log_mode=Router Log-modus config.disabled_logger=Uitgeschakeld config.access_log_mode=Toegangslog-modus @@ -1993,12 +2322,16 @@ monitor.desc=Omschrijving monitor.start=Starttijd monitor.execute_time=Uitvoertijd monitor.process.cancel=Annuleer proces +monitor.process.cancel_desc=Annuleren van een proces kan gegevensverlies veroorzaken monitor.process.cancel_notices=Annuleer: %s? monitor.queues=Wachtrijen monitor.queue=Wachtrij: %s monitor.queue.name=Naam monitor.queue.type=Type +monitor.queue.exemplar=Type voorbeeld monitor.queue.numberworkers=Aantal workers +monitor.queue.maxnumberworkers=Maximum aantal workers +monitor.queue.review=Configuratie herzien monitor.queue.review_add=Beoordeel/Voeg workers toe monitor.queue.configuration=Initiële configuratie monitor.queue.nopool.title=Geen Worker-pool @@ -2006,6 +2339,9 @@ monitor.queue.pool.timeout=Time-out monitor.queue.pool.addworkers.title=Voeg workers toe monitor.queue.pool.addworkers.submit=Voeg workers toe monitor.queue.pool.addworkers.numberworkers.placeholder=Aantal workers +monitor.queue.pool.addworkers.timeout.placeholder=Zet op 0 voor geen time-out +monitor.queue.pool.addworkers.mustnumbergreaterzero=Het aantal toe te voegen workers moet groter zijn dan nul +monitor.queue.pool.addworkers.musttimeoutduration=Time-out moet een golang duur zijn, bijv. 5 m of 0 monitor.queue.pool.flush.title=Wachtrij leegmaken monitor.queue.pool.flush.desc=Flush voegt een arbeider toe die zal afbreken zodra de wachtrij leeg is, of wanneer de wachtrij verlopen is. monitor.queue.pool.flush.submit=Flush Worker toevoegen @@ -2014,14 +2350,26 @@ monitor.queue.pool.flush.added=Flush Worker toegevoegd voor %[1]s monitor.queue.settings.title=Pool instellingen monitor.queue.settings.timeout=Boost time-out monitor.queue.settings.timeout.placeholder=Momenteel %[1]v +monitor.queue.settings.timeout.error=Time-out moet een golang duur zijn, bijv. 5 m of 0 +monitor.queue.settings.numberworkers=Vergroot aantal Workers monitor.queue.settings.numberworkers.placeholder=Momenteel %[1]d +monitor.queue.settings.numberworkers.error=Het aantal toe te voegen workers moet groter of gelijk zijn aan nul +monitor.queue.settings.maxnumberworkers=Maximum aantal workers monitor.queue.settings.maxnumberworkers.placeholder=Momenteel %[1]d +monitor.queue.settings.maxnumberworkers.error=Maximaal aantal workers moet een nummer zijn monitor.queue.settings.submit=Instellingen bijwerken monitor.queue.settings.changed=Instellingen bijgewerkt +monitor.queue.settings.blocktimeout=Huidige Blok Time-out monitor.queue.settings.blocktimeout.value=%[1]v +monitor.queue.pool.none=Deze wachtrij heeft geen pool monitor.queue.pool.added=Worker groep toegevoegd +monitor.queue.pool.max_changed=Maximum aantal workers veranderd +monitor.queue.pool.workers.title=Actieve Worker Groepen monitor.queue.pool.workers.none=Geen worker groepen. +monitor.queue.pool.cancel=Sluiten Worker Groep af +monitor.queue.pool.cancelling=Worker roep wordt afgesloten +monitor.queue.pool.cancel_notices=Deze groep van %s workers afsluiten? notices.system_notice_list=Systeem aankondigingen notices.view_detail_header=Bekijk notificatie details @@ -2058,6 +2406,7 @@ compare_branch=Vergelijk compare_commits=Vergelijk %d commits compare_commits_general=Vergelijk commits approve_pull_request=`keurde %s#%[2]s goed` +publish_release=`heeft "%[4]s" op %[3]s vrijgegeven` [tool] ago=%s geleden @@ -2100,7 +2449,9 @@ mark_all_as_read=Markeer alles als gelezen [gpg] default_key=Ondertekend met standaard sleutel +error.extract_sign=Handtekening uitpakken mislukt error.generate_hash=Genereren van commit hash mislukt +error.no_committer_account=Geen account gekoppeld aan het e-mailadres van de committer error.no_gpg_keys_found=Geen bekende sleutel gevonden voor deze handtekening in de database error.not_signed_commit=Geen ondertekende commit diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index e4e4344b6785..fa46f4354c59 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -20,6 +20,7 @@ user_profile_and_more=Profil i ustawienia… signed_in_as=Zalogowany jako enable_javascript=Strona działa najlepiej z włączonym JavaScript. toc=Spis treści +licenses=Licencje username=Nazwa użytkownika email=Adres e-mail @@ -51,6 +52,8 @@ new_migrate=Nowa migracja new_mirror=Nowa kopia lustrzana new_fork=Nowy fork repozytorium new_org=Nowa organizacja +new_project=Nowy projekt +new_project_board=Tablica nowego projektu manage_org=Zarządzaj organizacjami admin_panel=Administracja witryny account_settings=Ustawienia konta @@ -71,6 +74,7 @@ issues=Zgłoszenia milestones=Kamienie milowe cancel=Anuluj +save=Zapisz add=Dodaj add_all=Dodaj wszystko remove=Usuń @@ -166,6 +170,7 @@ openid_signin=Włącz logowanie za pomocą OpenID openid_signin_popup=Włącz logowanie użytkowników za pomocą OpenID. openid_signup=Włącz samodzielną rejestrację za pomocą OpenID openid_signup_popup=Włącz samodzielną rejestrację opartą o OpenID. +enable_captcha=Włącz CAPTCHA przy rejestracji enable_captcha_popup=Wymagaj walidacji CAPTCHA przy samodzielnej rejestracji użytkownika. require_sign_in_view=Wymagaj zalogowania w celu przeglądania stron require_sign_in_view_popup=Ogranicz dostęp do strony dla zalogowanych użytkowników. Odwiedzający zobaczą jedynie obszar logowania oraz strony umożliwiające rejestrację. @@ -364,7 +369,6 @@ enterred_invalid_owner_name=Nowa nazwa właściciela nie jest prawidłowa. enterred_invalid_password=Wprowadzone hasło jest nieprawidłowe. user_not_exist=Użytkownik nie istnieje. team_not_exist=Ten zespół nie istnieje. -last_org_owner=Nie możesz usunąć ostatniego użytkownika z zespołu "owners". Musi być co najmniej jeden właściciel w każdym zespole. cannot_add_org_to_team=Organizacja nie może zostać dodana jako członek zespołu. invalid_ssh_key=Nie można zweryfikować Twojego klucza SSH: %s @@ -385,11 +389,13 @@ repositories=Repozytoria activity=Publiczna aktywność followers=Obserwujący starred=Polubione repozytoria +projects=Projekty following=Obserwowani follow=Obserwuj unfollow=Przestań obserwować heatmap.loading=Ładowanie mapy cieplnej… user_bio=Biografia +disabled_public_activity=Ten użytkownik wyłączył publiczne wyświetlanie jego aktywności. form.name_reserved=Nazwa użytkownika '%s' jest zarezerwowana. form.name_pattern_not_allowed=Wzór "%s" nie jest dozwolony dla nazwy użytkownika. @@ -428,6 +434,9 @@ continue=Kontynuuj cancel=Anuluj language=Język ui=Motyw +privacy=Prywatność +keep_activity_private=Ukryj moją aktywność na stronie profilu +keep_activity_private_popup=Aktywność staje się widoczna tylko dla Ciebie i administratorów lookup_avatar_by_mail=Znajdź awatar po adresie e-mail federated_avatar_lookup=Wyszukiwanie zewnętrznych awatarów @@ -489,8 +498,9 @@ ssh_helper=Potrzebujesz pomocy? Sprawdź na GitHubie przewodnik gpg_helper=Potrzebujesz pomocy? Przeczytaj na GitHubie poradnik na temat GPG. add_new_key=Dodaj klucz SSH add_new_gpg_key=Dodaj klucz GPG +key_content_ssh_placeholder=Zaczyna się od 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384' lub 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Zaczyna się od '-----BEGIN PGP PUBLICZNEJ BLOKI KLUCZOWEJ PGP---' ssh_key_been_used=Ten klucz SSH został już dodany do tego serwera. -ssh_key_name_used=Klucz SSH z tą nazwą został już dodany do Twojego konta. gpg_key_id_used=Publiczny klucz GPG z tym ID już istnieje. gpg_no_key_email_found=Tego klucza GPG nie można używać z żadnym adresem e-mail powiązanym z Twoim kontem. subkeys=Podklucze @@ -518,7 +528,6 @@ token_state_desc=Ten token był użyty w ciągu ostatnich 7 dni show_openid=Pokaż w profilu hide_openid=Ukryj w profilu ssh_disabled=SSH jest wyłączony - manage_social=Zarządzaj powiązanymi kontami społecznościowymi social_desc=Te konta społecznościowe są powiązane z Twoim kontem Gitea. Upewnij się, że rozpoznajesz każde z nich, gdyż za ich pomocą można zalogować się do Twojego konta Gitea. unbind=Rozłącz @@ -665,6 +674,13 @@ reactions_more=i %d więcej unit_disabled=Administrator witryny wyłączył tę sekcję repozytorium. language_other=Pozostałe +desc.private=Prywatne +desc.public=Publiczne +desc.private_template=Szablon prywatny +desc.public_template=Szablon +desc.internal=Wewnętrzny +desc.internal_template=Wewnętrzny szablon +desc.archived=Zarchiwizowane template.items=Elementy szablonu template.git_content=Zawartość gita (domyślna gałąź) @@ -686,8 +702,6 @@ form.name_reserved=Nazwa repozytorium „%s” jest zarezerwowana. form.name_pattern_not_allowed=Wzór "%s" nie jest dozwolony w nazwie repozytorium. need_auth=Autoryzacja klonowania -migrate_type=Typ migracji -migrate_type_helper=To repozytorium będzie kopią lustrzaną migrate_items=Składniki migracji migrate_items_wiki=Wiki migrate_items_milestones=Kamienie milowe @@ -703,7 +717,6 @@ migrate.permission_denied=Nie możesz importować lokalnych repozytoriów. migrate.invalid_local_path=Lokalna ścieżka jest niepoprawna - nie istnieje lub nie jest katalogiem. migrate.failed=Migracja nie powiodła się: %v migrate.lfs_mirror_unsupported=Tworzenie kopii lustrzanych elementów LFS nie jest wspierane - użyj zamiast tego 'git lfs fetch --all' i 'git lfs push --all'. -migrate.migrate_items_options=Przy migracji z GitHub'a, wpisz nazwę użytkownika, a opcje migracji zostaną wyświetlone. migrated_from=Zmigrowane z %[2]s migrated_from_fake=Zmigrowane z %[1]s migrate.migrating=Migrowanie z %s... @@ -741,6 +754,7 @@ branches=Gałęzie tags=Tagi issues=Zgłoszenia pulls=Oczekujące zmiany +project_board=Projekty labels=Etykiety org_labels_desc=Etykiety organizacji, które mogą być używane z wszystkimi repozytoriami w tej organizacji org_labels_desc_manage=zarządzaj @@ -759,6 +773,8 @@ audio_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML stored_lfs=Przechowane za pomocą Git LFS symbolic_link=Dowiązanie symboliczne commit_graph=Wykres commitów +commit_graph.monochrome=Monochromatyczny +commit_graph.color=Kolor blame=Wina normal_view=Zwykły widok line=wiersz @@ -806,11 +822,8 @@ editor.file_deleting_no_longer_exists=Usuwany plik '%s' już nie istnieje w tym 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.push_rejected_no_message=Zmiana została odrzucona przez serwer bez wiadomości. Sprawdź githooki. -editor.push_rejected=Zmiana została odrzucona przez serwer z następującą wiadomością:
%s
Sprawdź githooki. 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. @@ -840,10 +853,39 @@ commits.gpg_key_id=ID klucza GPG ext_issues=Zgłoszenia zewn. ext_issues.desc=Link do zewnętrznego systemu śledzenia zgłoszeń. +projects=Projekty +projects.desc=Zarządzaj zgłoszeniami i pullami w tablicach projektów. +projects.create=Utwórz projekt +projects.title=Tytuł +projects.new=Nowy projekt +projects.new_subheader=Koordynuj, śledź i aktualizuj swoją pracę w jednym miejscu, aby projekty były przejrzyste i zgodne z harmonogramem. +projects.create_success=Projekt '%s' został utworzony. +projects.deletion=Usuń projekt +projects.deletion_desc=Usunięcie projektu usuwa go ze wszystkich powiązanych zgłoszeń. Kontynuować? +projects.deletion_success=Projekt został usunięty. +projects.edit=Edytuj projekty +projects.edit_subheader=Cele pozwalają na organizację zgłoszeń i śledzenie postępów. +projects.modify=Zaktualizuj projekt +projects.edit_success=Projekt '%s' został zaktualizowany. +projects.type.none=Brak +projects.type.basic_kanban=Basic Kanban +projects.type.bug_triage=Bug Triage +projects.template.desc=Szablon projektu +projects.template.desc_helper=Wybierz szablon projektu do rozpoczęcia +projects.type.uncategorized=Bez kategorii +projects.board.edit=Edytuj tablicę +projects.board.edit_title=Nazwa nowej tablicy +projects.board.new_title=Nazwa nowej tablicy +projects.board.new_submit=Zatwierdź +projects.board.new=Nowa tablica +projects.board.delete=Usuń tablicę +projects.open=Otwórz +projects.close=Zamknij issues.desc=Organizuj zgłoszenia o błędach, zadania i cele. issues.filter_assignees=Filtruj przypisania issues.filter_milestones=Filtruj kamienie milowe +issues.filter_projects=Filtruj projekt issues.filter_labels=Filtruj etykiety issues.filter_reviewers=Filtruj recenzentów issues.new=Nowe zgłoszenie @@ -852,6 +894,11 @@ issues.new.labels=Etykiety issues.new.add_labels_title=Zastosuj etykiety issues.new.no_label=Brak etykiety issues.new.clear_labels=Wyczyść etykiety +issues.new.projects=Projekty +issues.new.clear_projects=Wyczyść projekty +issues.new.no_projects=Brak projektu +issues.new.open_projects=Otwórz projekty +issues.new.closed_projects=Zamknięte projekty issues.new.no_items=Brak elementów issues.new.milestone=Kamień milowy issues.new.add_milestone_title=Ustaw kamień milowy @@ -925,7 +972,9 @@ issues.action_assignee_no_select=Brak przypisania issues.opened_by=otworzone %[1]s przez %[3]s pulls.merged_by=scalono %[1]s przez %[3]s pulls.merged_by_fake=scalono %[1]s przez %[2]s +issues.closed_by=przez %[3]s zamknięte %[1]s issues.opened_by_fake=otworzone %[1]s przez %[2]s +issues.closed_by_fake=przez %[2]s zamknięte %[1]s issues.previous=Poprzedni issues.next=Następny issues.open_title=Otwarty @@ -1029,6 +1078,7 @@ issues.error_modifying_due_date=Nie udało się zmodyfikować terminu realizacji issues.error_removing_due_date=Nie udało się usunąć terminu realizacji. issues.push_commit_1=dodał(-a) %d commit %s issues.push_commits_n=dodał(-a) %d commity(-ów) %s +issues.force_push_codes=`przymusowo wypchnął %[1]s z %[2]s do %[4]s %[6]s` issues.due_date_form=yyyy-mm-dd issues.due_date_form_add=Dodaj termin realizacji issues.due_date_form_edit=Edytuj @@ -1079,7 +1129,7 @@ issues.review.add_review_request=poprosił o recenzję %s %s issues.review.remove_review_request=usunięto prośbę o recenzję %s %s issues.review.remove_review_request_self=odmówił recenzji %s issues.review.pending=Oczekująca -issues.review.review=Recenzuj +issues.review.review=Recenzja issues.review.reviewers=Recenzenci issues.review.show_outdated=Pokaż przedawnione issues.review.hide_outdated=Ukryj przedawnione @@ -1147,11 +1197,9 @@ pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff) pulls.squash_merge_pull_request=Zmiażdż i scal pulls.require_signed_wont_sign=Ta gałąź wymaga podpisanych commitów, ale to scalenie nie będzie podpisane 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 +; %[2]s
%[3]s
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.push_rejected=Scalanie nie powiodło się: Wypchnięcie zostało odrzucone z następującą wiadomością:
%s
Przejrzyj githooki dla tego repozytorium pulls.push_rejected_no_message=Scalanie nie powiodło się: Wypchnięcie zostało odrzucone, ale serwer nie przesłał żadnej wiadomości.
Przejrzyj githooki dla tego repozytorium 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 @@ -1209,6 +1257,7 @@ signing.wont_sign.basesigned=Scalenie nie będzie podpisane, ponieważ commit ba signing.wont_sign.headsigned=Scalenie nie będzie podpisane, ponieważ head commit nie jest podpisany signing.wont_sign.commitssigned=Scalenie nie będzie podpisane, ponieważ wszystkie powiązane commity nie są podpisane signing.wont_sign.approved=Scalenie nie będzie podpisane, ponieważ PR nie został zatwierdzony +signing.wont_sign.not_signed_in=Nie jesteś zalogowany ext_wiki=Zewn. wiki ext_wiki.desc=Link do zewnętrznego wiki. @@ -1354,6 +1403,7 @@ settings.pulls.allow_merge_commits=Włącz scalanie poprzez commity settings.pulls.allow_rebase_merge=Włącz zmianę bazy do scalania commitów settings.pulls.allow_rebase_merge_commit=Włącz zmianę bazy ze stworzeniem commita ze scaleniem (--no-ff) settings.pulls.allow_squash_commits=Włącz miażdżenie do scalania commitów +settings.projects_desc=Włącz projekty w repozytorium settings.admin_settings=Ustawienia administratora settings.admin_enable_health_check=Włącz sprawdzanie stanu zdrowia repozytoriów (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Zamknij zgłoszenie poprzez commit wprowadzony do nie-domyślnej gałęzi @@ -1364,6 +1414,11 @@ settings.convert_desc=Możesz przekonwertować tę kopię lustrzaną na zwykłe settings.convert_notices_1=Ta operacja przekonwertuje kopię lustrzaną w zwykłe repozytorium i nie może być cofnięta. settings.convert_confirm=Konwertuj repozytorium settings.convert_succeed=Kopia lustrzana została przekonwertowana w zwykłe repozytorium. +settings.convert_fork=Konwertuj na zwykłe repozytorium +settings.convert_fork_desc=Możesz przekonwertować ten fork w zwykłe repozytorium. Ta czynność jest nieodwracalna. +settings.convert_fork_notices_1=Ta operacja przekonwertuje fork w zwykłe repozytorium i nie może być cofnięta. +settings.convert_fork_confirm=Konwertuj repozytorium +settings.convert_fork_succeed=Fork został przekonwertowany w zwykłe repozytorium. settings.transfer=Przeniesienie własności settings.transfer_desc=Przenieś to repozytorium do innego użytkownika lub organizacji, w której posiadasz uprawnienia administratora. settings.transfer_notices_1=- Stracisz dostęp do tego repozytorium, jeśli przeniesiesz je do innego użytkownika. @@ -1402,6 +1457,7 @@ settings.add_team=Dodaj zespół settings.add_team_duplicate=Zespół już posiada repozytorium settings.add_team_success=Zespół ma teraz dostęp do repozytorium. settings.search_team=Szukaj zespołu… +settings.change_team_permission_tip=Uprawnienia zespołu ustawione są konfigurowane na stronie ustawień zespołu i nie mogą być zmieniane dla pojedynczych repozytoriów settings.delete_team_tip=Ten zespół ma dostęp do wszystkich repozytoriów i nie może zostać usunięty settings.remove_team_success=Dostęp zespołu do repozytorium został usunięty. settings.add_webhook=Dodaj webhooka @@ -1454,15 +1510,26 @@ settings.event_header_issue=Zdarzenia zgłoszeń settings.event_issues=Zgłoszenia settings.event_issues_desc=Zgłoszenie otwarte, zamknięte, ponownie otwarte lub zmodyfikowane. settings.event_issue_assign=Zgłoszenie przypisane +settings.event_issue_assign_desc=Zgłoszenie przypisane bądź nieprzypisane. settings.event_issue_label=Zgłoszenie oznaczone +settings.event_issue_label_desc=Etykieta zgłoszenia zaktualizowana lub usunięta. +settings.event_issue_milestone=Ustawiono cel zgłoszenia +settings.event_issue_milestone_desc=Ustawiono lub usunięto cel zgłoszenia. settings.event_issue_comment=Komentarz w zgłoszeniu settings.event_issue_comment_desc=Komentarz w zgłoszeniu stworzony, edytowany lub usunięty. settings.event_header_pull_request=Zdarzenia Pull Requestów settings.event_pull_request=Pull Request settings.event_pull_request_desc=Pull request otwarty, zamknięty, ponownie otwarty lub zmodyfikowany. settings.event_pull_request_assign=Pull Request przypisany +settings.event_pull_request_assign_desc=Pull Request przypisany bądz nieprzypisany. +settings.event_pull_request_label=Pull Request zaetykietowany +settings.event_pull_request_label_desc=Etykieta pull requesta zaktualizowana lub usunięta. +settings.event_pull_request_milestone=Ustawiono cel Pull Requesta +settings.event_pull_request_milestone_desc=Ustawiono lub usunięto cel pull requesta. settings.event_pull_request_comment=Pull Request skomentowany +settings.event_pull_request_comment_desc=Komentarz pull requestu stworzony, edytowany lub usunięty. settings.event_pull_request_review=Pull Request zrecenzowany +settings.event_pull_request_review_desc=Pull request zatwierdzony, odrzucony lub zrecenzowany. settings.event_pull_request_sync=Pull Request Zsynchronizowany settings.event_pull_request_sync_desc=Pull request zsynchronizowany. settings.branch_filter=Filtr gałęzi @@ -1522,6 +1589,7 @@ settings.protect_merge_whitelist_committers_desc=Zezwól jedynie dopuszczonym u 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 wypchnięte do innej gałęzi, a następnie scalone lub wypchnię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. @@ -1532,6 +1600,9 @@ settings.protect_approvals_whitelist_teams=Dopuszczone zespoły do recenzji: settings.dismiss_stale_approvals=Unieważnij przestarzałe zatwierdzenia settings.dismiss_stale_approvals_desc=Kiedy nowe commity zmieniające zawartość Pull Requesta są wypychane do gałęzi, wcześniejsze zatwierdzenia zostaną unieważnione. settings.require_signed_commits=Wymagaj podpisanych commitów +settings.require_signed_commits_desc=Odrzucaj zmiany wypychane do tej gałęzi, jeśli nie są podpisane, lub są niemożliwe do zweryfikowania. +settings.protect_protected_file_patterns=Wzory chronionych plików (rozdzielone średnikiem '\;'): +settings.protect_protected_file_patterns_desc=Chronione pliki, które nie mogą być zmienione bezpośrednio, nawet jeśli użytkownik ma uprawnienia do dodawania, edytowania lub usuwania plików w tej gałęzi. Wzorce można rozdzielić za pomocą średnika ('\;'). Zobacz dokumentację github.com/gobwas/glob dla składni wzorca. Przykłady: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Włącz ochronę settings.delete_protected_branch=Wyłącz ochronę settings.update_protect_branch_success=Ochrona gałęzi dla gałęzi "%s" została zaktualizowana. @@ -1630,6 +1701,7 @@ diff.review.placeholder=Komentarz recenzji diff.review.comment=Skomentuj diff.review.approve=Zatwierdź diff.review.reject=Zażądaj zmian +diff.committed_by=zatwierdzone przez releases.desc=Śledź wersje projektu i pobrania. release.releases=Wydania @@ -1638,6 +1710,8 @@ release.draft=Szkic release.prerelease=Wersja wstępna release.stable=Stabilna release.edit=edytuj +release.ahead.commits=%d commitów +release.ahead.target=do %s od tego wydania release.source_code=Kod źródłowy release.new_subheader=Wydania pozwalają na zorganizowanie wersji projektu. release.edit_subheader=Wydania pozwalają na zorganizowanie wersji projektu. @@ -1824,6 +1898,12 @@ dashboard.operation_switch=Przełącz dashboard.operation_run=Uruchom dashboard.clean_unbind_oauth=Usuń wychodzące połączenia OAuth dashboard.clean_unbind_oauth_success=Wszystkie połączenia wychodzące OAuth zostały usunięte. +dashboard.task.started=Rozpoczęto zadanie: %[1]s +dashboard.task.process=Zadanie: %[1]s +dashboard.task.cancelled=Zadanie: %[1]s anulowane: %[3]s +dashboard.task.error=Błąd w zadaniu: %[1]s: %[3]s +dashboard.task.finished=Zadanie: %[1]s rozpoczęte przez %[2]s zostało ukończone +dashboard.task.unknown=Nieznane zadanie: %[1]s dashboard.cron.started=Uruchomiono Crona: %[1]s dashboard.cron.process=Cron: %[1]s dashboard.cron.cancelled=Cron: %s anulowany: %[3]s @@ -1904,7 +1984,6 @@ users.prohibit_login=Wyłącz logowanie users.is_admin=Jest administratorem users.is_restricted=Jest ograniczone users.allow_git_hook=Może tworzyć hooki Gita -users.allow_git_hook_tooltip=Git hooki są wykonywane jako użytkownik OS obsługujący Gitea i będą miały taki sam poziom dostępu do hosta users.allow_import_local=Może importować lokalne repozytoria users.allow_create_organization=Może tworzyć organizacje users.update_profile=Zaktualizuj konto użytkownika @@ -1946,6 +2025,7 @@ hooks.desc=Webhooki automatycznie tworzą zapytania HTTP POST do serwera, kiedy hooks.add_webhook=Dodaj domyślny Webhook hooks.update_webhook=Zaktualizuj domyślny Webhook +systemhooks.desc=Webhooki automatycznie tworzą zapytania HTTP POST do serwera, kiedy następują pewne zdarzenia w Gitea. Webhooki zdefiniowane w tym miejscu będą wykonywane dla wszystkich repozytoriów, więc rozważ ewentualne konsekwencje pod względem wydajności. Przeczytaj o tym więcej w przewodniku o Webhookach. systemhooks.add_webhook=Dodaj Webhook Systemowy systemhooks.update_webhook=Aktualizuj Webhook Systemowy @@ -2025,6 +2105,7 @@ auths.tip.openid_connect=Użyj adresu URL OpenID Connect Discovery (/.we 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.tip.yandex=Utwórz nową aplikację na https://oauth.yandex.com/client/new. Wybierz następujące uprawnienia z "Yandex.Passport API": "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender" auths.edit=Edytuj źródło uwierzytelniania auths.activated=To źródło uwierzytelniania jest aktywne auths.new_success=Uwierzytelnienie '%s' zostało dodane. @@ -2120,6 +2201,7 @@ config.mailer_use_sendmail=Używaj Sendmail config.mailer_sendmail_path=Ścieżka Sendmail config.mailer_sendmail_args=Dodatkowe argumenty Sendmail config.mailer_sendmail_timeout=Limit czasu Sendmail +config.test_email_placeholder=Email (np. test@example.com) 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'. @@ -2137,7 +2219,6 @@ config.session_config=Konfiguracja sesji config.session_provider=Dostawca sesji config.provider_config=Konfiguracja dostawcy config.cookie_name=Nazwa ciasteczka -config.enable_set_cookie=Włącz ciasteczka config.gc_interval_time=Interwał usuwania śmieci config.session_life_time=Czas ważności sesji config.https_only=Tylko HTTPS diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index d7c7a9277ef3..79fc69cace17 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -203,7 +203,7 @@ my_repos=Repositórios show_more_repos=Mostrar mais repositórios… collaborative_repos=Repositórios colaborativos my_orgs=Minhas organizações -my_mirrors=Meus espelhamentos +my_mirrors=Meus espelhos view_home=Ver %s search_repos=Encontre um repositório… filter=Outros filtros @@ -359,7 +359,6 @@ enterred_invalid_owner_name=O nome do novo proprietário não é válido. enterred_invalid_password=A senha que você digitou está incorreta. user_not_exist=O usuário não existe. team_not_exist=A equipe não existe. -last_org_owner=Você não pode remover o último usuário do time 'proprietários'. Deve haver pelo menos um proprietário em qualquer time. cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de um time. invalid_ssh_key=Não é possível verificar sua chave SSH: %s @@ -484,7 +483,6 @@ gpg_helper=Precisa de ajuda? Dê uma olhada no guia do GitHub < add_new_key=Adicionar Chave SSH add_new_gpg_key=Adicionar chave GPG ssh_key_been_used=Esta chave SSH já foi adicionada ao servidor. -ssh_key_name_used=Uma chave SSH com o mesmo nome já foi adicionada à sua conta. gpg_key_id_used=Uma chave GPG pública com a mesma ID já existe. gpg_no_key_email_found=Esta chave GPG não é utilizável com qualquer endereço de e-mail associado à sua conta. subkeys=Subchaves @@ -512,7 +510,6 @@ token_state_desc=Este token tem sido utilizado nos últimos 7 dias show_openid=Mostrar no perfil hide_openid=Ocultar no perfil ssh_disabled=SSH desabilitado - manage_social=Gerenciar contas sociais associadas social_desc=Essas contas sociais estão vinculadas à sua conta do Gitea. Certifique-se de reconhecer todas elas, pois elas podem ser usadas para acessar a sua conta do Gitea. unbind=Desvincular @@ -683,9 +680,6 @@ form.name_reserved=O nome de repositório '%s' está reservado e não pode ser u form.name_pattern_not_allowed=O padrão de '%s' não é permitido em um nome de repositório. need_auth=Autorização de clone -migrate_type=Tipo de migração -migrate_type_helper=Este repositório será um espelhamento -migrate_type_helper_disabled=O administrador do site desabilitou novos espelhamentos. migrate_items=Itens da migração migrate_items_wiki=Wiki migrate_items_milestones=Marcos @@ -701,7 +695,6 @@ migrate.permission_denied=Você não pode importar repositórios locais. migrate.invalid_local_path=O caminho local é inválido. Ele não existe ou não é um diretório. migrate.failed=Migração falhou: %v migrate.lfs_mirror_unsupported=Espelhamento de objetos Git LFS não é suportado; ao invés use 'git lfs fetch --all' e 'git lfs push --all'. -migrate.migrate_items_options=Ao migrar do github, insira um nome de usuário e as opções de migração serão exibidas. migrated_from=Migrado de %[2]s migrated_from_fake=Migrado de %[1]s migrate.migrating=Migrando a partir de %s ... @@ -804,9 +797,7 @@ editor.file_already_exists=Um arquivo com nome '%s' já existe neste repositóri editor.commit_empty_file_header=Fazer commit de um arquivo vazio editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar? editor.no_changes_to_show=Nenhuma alteração a mostrar. -editor.fail_to_update_file=Houve erro ao criar ou atualizar arquivo '%s': %v editor.push_rejected_no_message=A alteração foi rejeitada pelo servidor sem uma mensagem. Por favor, verifique os githooks. -editor.push_rejected=A alteração foi rejeitada pelo servidor com a seguinte mensagem:
%s
Por favor, verifique os githooks. editor.add_subdir=Adicionar um subdiretório... editor.unable_to_upload_files=Houve erro ao fazer upload de arquivos para '%s': %v editor.upload_file_is_locked=Arquivo '%s' está bloqueado por %s. @@ -1106,11 +1097,9 @@ pulls.rebase_merge_commit_pull_request=Aplicar Rebase e Merge (--no-ff) pulls.squash_merge_pull_request=Aplicar Squash e Merge pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request. -pulls.merge_conflict=Merge falhou: Houve um conflito durante o merge: %[1]s
%[2]s
Dica: Tente uma estratégia diferente -pulls.rebase_conflict=Merge Falhou: Houve um conflito durante o rebase do commit: %[1]s
%[2]s
%[3]s
Dica: Tente uma estratégia diferente +; %[2]s
%[3]s
pulls.unrelated_histories=Merge falhou: O merge do principal e da base não compartilham uma história comum. Dica: Tente uma estratégia diferente pulls.merge_out_of_date=Merge falhou: durante a geração do merge, a base não foi atualizada. Dica: Tente novamente. -pulls.push_rejected=Merge falhou: O push foi rejeitado com a seguinte mensagem:
%s
Revise os githooks para este repositório pulls.push_rejected_no_message=Merge falhou: O push foi rejeitado mas não havia mensagem remota.
Revise os githooks para este repositório pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.` pulls.status_checking=Algumas verificações estão pendentes @@ -2037,7 +2026,6 @@ config.session_config=Configuração da sessão config.session_provider=Provedor da sessão config.provider_config=Configuração do provedor config.cookie_name=Nome do cookie -config.enable_set_cookie=Habilitar uso de cookie config.gc_interval_time=Tempo de Intervalo do GC config.session_life_time=Tempo de vida da sessão config.https_only=Apenas HTTPS diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index f20bbd552056..d0f7a44bedb9 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -20,10 +20,13 @@ user_profile_and_more=Perfil e configurações… signed_in_as=Sessão iniciada como enable_javascript=Este sítio funciona melhor com JavaScript. toc=Índice +licenses=Licenças +return_to_gitea=Retornar ao Gitea username=Nome de utilizador email=Endereço de email password=Senha +access_token=Código de acesso re_type=Introduza novamente a senha captcha=CAPTCHA twofa=Autenticação com dois passos @@ -51,6 +54,8 @@ new_migrate=Nova migração new_mirror=Novo espelho new_fork=Nova bifurcação do repositório new_org=Nova organização +new_project=Novo projecto +new_project_board=Novo quadro de projecto manage_org=Gerir organizações admin_panel=Administração do sítio account_settings=Configurações da conta @@ -71,6 +76,7 @@ issues=Questões milestones=Etapas cancel=Cancelar +save=Guardar add=Adicionar add_all=Adicionar tudo remove=Remover @@ -166,6 +172,7 @@ openid_signin=Habilitar início de sessão com OpenID openid_signin_popup=Habilitar o início de sessão do utilizador usando o OpenID. openid_signup=Habilitar a auto-inscrição com OpenID openid_signup_popup=Habilitar a utilização do OpenID para fazer auto-inscrições. +enable_captcha=Habilitar CAPTCHA na inscrição enable_captcha_popup=Exigir CAPTCHA na auto-inscrição de utilizadores. require_sign_in_view=Exigir sessão iniciada para visualizar páginas require_sign_in_view_popup=Limitar o acesso às páginas aos utilizadores inscritos. Os visitantes só poderão visualizar as páginas de início de sessão e de inscrição. @@ -292,6 +299,8 @@ authorize_title=Autorizar o acesso de "%s" à sua conta? authorization_failed=A autorização falhou authorization_failed_desc=A autorização falhou porque encontrámos um pedido inválido. Entre em contacto com o responsável pela aplicação que tentou autorizar. sspi_auth_failed=Falhou a autenticação SSPI +password_pwned=A senha utilizada está numa lista de senhas roubadas anteriormente expostas em fugas de dados públicas. Tente novamente com uma senha diferente. +password_pwned_err=Não foi possível completar o pedido ao HaveIBeenPwned [mail] activate_account=Por favor, ponha a sua conta em funcionamento @@ -346,6 +355,10 @@ lang_select_error=Escolha um idioma da lista. username_been_taken=O nome de utilizador já foi tomado. repo_name_been_taken=O nome do repositório já foi usado. +repository_files_already_exist=Já existem ficheiros neste repositório. Contacte o administrador do sistema. +repository_files_already_exist.adopt=Já existem ficheiros neste repositório e só podem ser adoptados. +repository_files_already_exist.delete=Já existem ficheiros neste repositório. Tem que os eliminar. +repository_files_already_exist.adopt_or_delete=Já existem ficheiros neste repositório. Adopte-os ou elimine-os. visit_rate_limit=Limitação da taxa de visita remota. 2fa_auth_required=A visita remota requer autenticação em dois passos. org_name_been_taken=O nome da organização já foi tomado. @@ -364,11 +377,12 @@ enterred_invalid_owner_name=O novo nome de proprietário não é válido. enterred_invalid_password=A senha que inseriu está errada. user_not_exist=O utilizador não existe. team_not_exist=A equipa não existe. -last_org_owner=Não pode remover o último utilizador da equipa 'proprietários'. Tem que haver pelo menos um proprietário em qualquer equipa. +last_org_owner=Não pode remover o último utilizador da equipa 'proprietários'. Tem que haver pelo menos um proprietário numa organização. cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de uma equipa. invalid_ssh_key=Não é possível verificar sua chave SSH: %s invalid_gpg_key=Não é possível verificar sua chave GPG: %s +invalid_ssh_principal=Protagonista inválido: %s unable_verify_ssh_key=Não é possível verificar a chave SSH; verifique novamente se há erros. auth_failed=Falha na autenticação: %v @@ -385,11 +399,13 @@ repositories=Repositórios activity=Trabalho público followers=Seguidores starred=Repositórios favoritos +projects=Projectos following=Que segue follow=Seguir unfollow=Deixar de seguir heatmap.loading=Carregando mapa de laboração… user_bio=Biografia +disabled_public_activity=Este utilizador desabilitou a visibilidade pública do trabalho. form.name_reserved=O nome de utilizador '%s' está reservado. form.name_pattern_not_allowed=O padrão '%s' não é permitido no nome de utilizador. @@ -414,6 +430,7 @@ uid=Uid u2f=Chaves de segurança public_profile=Perfil público +biography_placeholder=Conte-nos um pouco sobre si profile_desc=O seu endereço de email será usado para notificações e outras operações. password_username_disabled=Utilizadores não-locais não podem mudar os seus nomes de utilizador. Entre em contacto com o administrador do sítio saber para mais detalhes. full_name=Nome completo @@ -428,6 +445,9 @@ continue=Continuar cancel=Cancelar language=Idioma ui=Tema +privacy=Privacidade +keep_activity_private=Esconder o trabalho da página do perfil +keep_activity_private_popup=Torna o trabalho visível apenas para si e para os administradores lookup_avatar_by_mail=Procurar avatar com base no endereço de email federated_avatar_lookup=Pesquisa de avatar federada @@ -481,31 +501,42 @@ keep_email_private_popup=Seu endereço de email será escondido dos outros utili openid_desc=O OpenID permite delegar a autenticação num fornecedor externo. manage_ssh_keys=Gerir chaves SSH +manage_ssh_principals=Gerir Protagonistas de Certificados SSH manage_gpg_keys=Gerir chaves GPG add_key=Adicionar chave ssh_desc=Essas chaves públicas SSH estão associadas à sua conta. As chaves privadas correspondentes permitem acesso total aos seus repositórios. +principal_desc=Estes protagonistas de certificados SSH estão associados à sua conta e permitem acesso total aos seus repositórios. gpg_desc=Essas chaves GPG públicas estão associadas à sua conta. Mantenha as suas chaves privadas seguras, uma vez que elas permitem a verificação dos cometimentos. ssh_helper=Precisa de ajuda? Dê uma vista de olhos no guia do GitHub para criar as suas próprias chaves SSH ou para resolver problemas comuns que pode encontrar ao usar o SSH. gpg_helper=Precisa de ajuda? Dê uma vista de olhos no guia do GitHub sobre GPG. add_new_key=Adicionar Chave SSH add_new_gpg_key=Adicionar chave GPG +key_content_ssh_placeholder=Começa com 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', ou 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Começa com '-----BEGIN PGP PUBLIC KEY BLOCK-----' +add_new_principal=Adicional Protagonista ssh_key_been_used=Esta chave SSH já tinha sido adicionada ao servidor. -ssh_key_name_used=Já tinha sido adicionada uma chave SSH com o mesmo nome à sua conta. +ssh_key_name_used=Já existe uma chave SSH com o mesmo nome na sua conta. +ssh_principal_been_used=Este protagonista já tinha sido adicionado ao servidor. gpg_key_id_used=Já existe uma chave pública GPG com o mesmo ID. gpg_no_key_email_found=Esta chave GPG não é utilizável com qualquer endereço de email associado à sua conta. subkeys=Subchaves key_id=ID da chave key_name=Nome da chave key_content=Conteúdo +principal_content=Conteúdo add_key_success=A chave SSH '%s' foi adicionada. add_gpg_key_success=A chave GPG '%s' foi adicionada. +add_principal_success=O protagonista de certificado SSH '%s' foi adicionado. delete_key=Remover ssh_key_deletion=Remover chave SSH gpg_key_deletion=Remover chave GPG +ssh_principal_deletion=Remover Protagonista de Certificado SSH ssh_key_deletion_desc=Remover uma chave SSH revoga o acesso dessa chave à sua conta. Quer continuar? gpg_key_deletion_desc=Remover uma chave GPG retira as verificações feitas sobre os cometimentos assinados com ela. Quer continuar? +ssh_principal_deletion_desc=Remover um Protagonista de Certificado SSH revoga o seu acesso à sua conta. Quer continuar? ssh_key_deletion_success=A chave SSH foi removida. gpg_key_deletion_success=A chave GPG foi removida. +ssh_principal_deletion_success=O protagonista foi removido. add_on=Adicionada em valid_until=Válida até valid_forever=Válida para sempre @@ -515,10 +546,10 @@ can_read_info=Leitura can_write_info=Escrita key_state_desc=Esta chave foi usada nos últimos 7 dias token_state_desc=Este código foi usado nos últimos 7 dias +principal_state_desc=Este protagonista foi usado nos últimos 7 dias show_openid=Mostrar no perfil hide_openid=Ocultar do perfil ssh_disabled=SSH desabilitado - manage_social=Gerir contas sociais associadas social_desc=Estas contas sociais estão vinculadas à sua conta do Gitea. Certifique-se que as reconhece todas, uma vez que podem ser usadas para iniciar sessão na sua conta do Gitea. unbind=Desvincular @@ -664,7 +695,23 @@ pick_reaction=Escolha sua resposta reactions_more=e mais %d unit_disabled=O administrador desabilitou esta secção do repositório. language_other=Outros - +adopt_search=Insira o nome de utilizador para procurar repositórios adoptados... (deixe em branco para encontrar todos) +adopt_preexisting_label=Adoptar ficheiros +adopt_preexisting=Adoptar ficheiros pré-existentes +adopt_preexisting_content=Criar repositório a partir de %s +adopt_preexisting_success=Ficheiros adoptados e repositório criado a partir de %s +delete_preexisting_label=Eliminar +delete_preexisting=Eliminar ficheiros pré-existentes +delete_preexisting_content=Eliminar ficheiros em %s +delete_preexisting_success=Eliminar ficheiros não adoptados em %s + +desc.private=Privado +desc.public=Público +desc.private_template=Modelo privado +desc.public_template=Modelo +desc.internal=Interno +desc.internal_template=Modelo interno +desc.archived=Arquivado template.items=Itens do modelo template.git_content=Conteúdo Git (ramo padrão) @@ -686,14 +733,17 @@ form.name_reserved=O nome de repositório '%s' está reservado. form.name_pattern_not_allowed=O padrão '%s' não é permitido no nome de um repositório. need_auth=Autorização de clonagem -migrate_type=Tipo de migração -migrate_type_helper=Este repositório será um espelho +migrate_options=Opções de migração +migrate_service=Serviço de migração +migrate_options_mirror_helper=Este repositório irá ser um espelho +migrate_options_mirror_disabled=O administrador desabilitou novos espelhos. migrate_items=Itens da migração migrate_items_wiki=Wiki migrate_items_milestones=Etapas migrate_items_labels=Etiquetas migrate_items_issues=Questões migrate_items_pullrequests=Pedidos de integração +migrate_items_merge_requests=Pedidos de integração migrate_items_releases=Lançamentos migrate_repo=Migrar o repositório migrate.clone_address=Migrar / clonar a partir do URL @@ -703,17 +753,24 @@ migrate.permission_denied=Não está autorizado a importar repositórios locais. migrate.invalid_local_path=O caminho local é inválido. Não existe ou não é uma pasta. migrate.failed=A migração falhou: %v migrate.lfs_mirror_unsupported=O espelhamento de elementos LFS não é suportado - ao invés disso use 'git lfs fetch --all' e 'git lfs push --all'. -migrate.migrate_items_options=Ao migrar do GitHub serão apresentadas opções de migração e de inserção de um nome de utilizador. +migrate.migrate_items_options=É necessário um código de acesso para migrar itens adicionais migrated_from=Migrado de %[2]s migrated_from_fake=Migrado de %[1]s +migrate.migrate=Migrar de %s migrate.migrating=Migrando de %s... migrate.migrating_failed=A migração de %s falhou. +migrate.github.description=Migrando dados do Github.com ou do Github Enterprise. +migrate.git.description=Migrando ou espelhando dados git a partir de serviços Git +migrate.gitlab.description=Migrando dados do GitLab.com ou de um servidor GitLab auto-hospedado. +migrate.gitea.description=Migrando dados do Gitea.com ou de um servidor Gitea auto-hospedado. mirror_from=espelho de forked_from=derivado de generated_from=gerado a partir de fork_from_self=Não pode derivar de um repositório que já é seu. fork_guest_user=Inicie a sessão para derivar este repositório. +watch_guest_user=Inicie sessão para começar a vigiar este repositório. +star_guest_user=Inicie sessão para marcar este repositório como favorito. copy_link=Copiar copy_link_success=A ligação foi copiada copy_link_error=Use ⌘C ou Ctrl-C para copiar @@ -736,18 +793,20 @@ code=Código code.desc=Aceder ao código fonte, ficheiros, cometimentos e ramos. branch=Ramo tree=Árvore +clear_ref=`Apagar a referência vigente` filter_branch_and_tag=Filtrar ramo ou etiqueta branches=Ramos tags=Etiquetas issues=Questões pulls=Pedidos de integração +project_board=Projectos labels=Etiquetas org_labels_desc=Etiquetas ao nível da organização que podem ser usadas em todos os repositórios desta organização org_labels_desc_manage=gerir milestones=Etapas commits=Cometimentos -commit=Cometer +commit=Cometimento releases=Lançamentos file_raw=Em bruto file_history=Histórico @@ -759,6 +818,8 @@ audio_not_supported_in_browser=O seu navegador não suporta a etiqueta 'audio' d stored_lfs=Armazenado com Git LFS symbolic_link=Ligação simbólica commit_graph=Gráfico de cometimentos +commit_graph.monochrome=Monocromático +commit_graph.color=Colorido blame=Responsabilidade normal_view=Vista normal line=linha @@ -808,9 +869,7 @@ editor.file_already_exists=Já existe um ficheiro com o nome '%s' neste reposit editor.commit_empty_file_header=Cometer um ficheiro vazio editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar? editor.no_changes_to_show=Não existem alterações a mostrar. -editor.fail_to_update_file=Falhou a modificação ou a criação do ficheiro '%s' com o seguinte erro: %v editor.push_rejected_no_message=A alteração foi rejeitada pelo servidor sem qualquer mensagem. Verifique os automatismos do Git. -editor.push_rejected=A alteração foi rejeitada pelo servidor com a seguinte mensagem:
%s
Verifique os automatismos do Git. editor.add_subdir=Adicionar uma pasta… editor.unable_to_upload_files=Falha ao enviar ficheiros para '%s' com erro: %v editor.upload_file_is_locked=O ficheiro '%s' está bloqueado por %s. @@ -840,10 +899,40 @@ commits.gpg_key_id=ID da chave GPG ext_issues=Questões ext. ext_issues.desc=Ligação para um rastreador de questões externo. +projects=Projectos +projects.desc=Gerir questões e integrações nos quadros do projecto. +projects.create=Criar projecto +projects.title=Título +projects.new=Novo projecto +projects.new_subheader=Coordene, acompanhe e modifique o seu trabalho num só lugar, para que os projectos se mantenham transparentes e cumpram o calendário. +projects.create_success=O projecto '%s' foi criado. +projects.deletion=Eliminar projecto +projects.deletion_desc=Eliminar um projecto remove-o de todas as questões relacionadas. Continuar? +projects.deletion_success=O projecto foi eliminado. +projects.edit=Editar projectos +projects.edit_subheader=Projectos organizam questões e acompanham o progresso. +projects.modify=Modificar projecto +projects.edit_success=O projecto '%s' foi modificado. +projects.type.none=Nenhum +projects.type.basic_kanban=Kanban básico +projects.type.bug_triage=Triagem de erros +projects.template.desc=Modelo de projecto +projects.template.desc_helper=Escolha um modelo de projecto para começar +projects.type.uncategorized=Sem categoria +projects.board.edit=Editar quadro +projects.board.edit_title=Novo nome para o quadro +projects.board.new_title=Novo nome para o quadro +projects.board.new_submit=Submeter +projects.board.new=Novo quadro +projects.board.delete=Eliminar quadro +projects.board.deletion_desc=Eliminar um quadro de projecto faz com que todas as questões relacionadas sejam movidas para 'Sem categoria'. Continuar? +projects.open=Abrir +projects.close=Fechar issues.desc=Organize relatórios de erros, tarefas e etapas. issues.filter_assignees=Filtrar responsável issues.filter_milestones=Filtrar etapa +issues.filter_projects=Filtrar projecto issues.filter_labels=Filtrar etiqueta issues.filter_reviewers=Filtrar avaliador issues.new=Nova questão @@ -852,6 +941,12 @@ issues.new.labels=Etiquetas issues.new.add_labels_title=Aplicar etiquetas issues.new.no_label=Sem etiquetas issues.new.clear_labels=Limpar etiquetas +issues.new.projects=Projectos +issues.new.add_project_title=Definir projecto +issues.new.clear_projects=Limpar projectos +issues.new.no_projects=Nenhum projecto +issues.new.open_projects=Projectos abertos +issues.new.closed_projects=Projectos fechados issues.new.no_items=Sem itens issues.new.milestone=Etapa issues.new.add_milestone_title=Definir etapa @@ -865,6 +960,9 @@ issues.new.clear_assignees=Limpar responsáveis issues.new.no_assignees=Sem responsáveis issues.new.no_reviewers=Sem revisores issues.new.add_reviewer_title=Solicitar revisão +issues.choose.get_started=Começar +issues.choose.blank=Padrão +issues.choose.blank_about=Cria uma questão a partir do modelo padrão. issues.no_ref=Sem ramo ou etiqueta especificados issues.create=Criar questão issues.new_label=Nova etiqueta @@ -879,9 +977,13 @@ issues.label_templates.fail_to_load_file=Falha ao carregar o ficheiro modelo de issues.add_label_at=adicionou a etiqueta
%s
%s issues.remove_label_at=removeu a etiqueta
%s
%s issues.add_milestone_at=`adicionou esta questão à etapa %s %s` +issues.add_project_at=`adicionou isto ao projecto %s %s` issues.change_milestone_at=`modificou a etapa de %s para %s %s` +issues.change_project_at=`modificou o projecto de %s para %s %s` issues.remove_milestone_at=`removeu esta questão da etapa %s %s` +issues.remove_project_at=`removeu isto do projecto %s %s` issues.deleted_milestone=`(eliminada)` +issues.deleted_project=`(eliminado)` issues.self_assign_at=`atribuiu a si mesmo(a) esta questão %s` issues.add_assignee_at=`foi atribuída por %s %s` issues.remove_assignee_at=`a atribuição foi retirada por %s %s` @@ -960,6 +1062,7 @@ issues.poster=Autor issues.collaborator=Colaborador(a) issues.owner=Proprietário(a) issues.re_request_review=Voltar a solicitar revisão +issues.is_stale=Houve alterações neste pedido de integração posteriormente a esta revisão issues.remove_request_review=Remover solicitação de revisão issues.remove_request_review_block=Não é possível remover a solicitação de revisão issues.sign_in_require_desc=Inicie a sessão para participar neste diálogo. @@ -1029,6 +1132,9 @@ issues.due_date=Date limite issues.invalid_due_date_format=O formato da data limite tem que ser 'aaaa-mm-dd'. issues.error_modifying_due_date=Falha ao modificar a data limite. issues.error_removing_due_date=Falha ao remover a data limite. +issues.push_commit_1=adicionou %d cometimento %s +issues.push_commits_n=adicionou %d cometimentos %s +issues.force_push_codes=`forçou o envio de %[1]s de %[2]s para %[4]s %[6]s` issues.due_date_form=yyyy-mm-dd issues.due_date_form_add=Adicionar data limite issues.due_date_form_edit=Editar @@ -1081,6 +1187,7 @@ issues.review.remove_review_request_self=recusou-se a avaliar %s issues.review.pending=Pendente issues.review.review=Revisão issues.review.reviewers=Revisores +issues.review.outdated=Obsoleta issues.review.show_outdated=Mostrar as obsoletas issues.review.hide_outdated=Esconder as obsoletas issues.review.show_resolved=Mostrar resolvidos @@ -1124,6 +1231,8 @@ pulls.required_status_check_administrator=Uma vez que é administrador, ainda po pulls.blocked_by_approvals=Este pedido de integração ainda não tem aprovações suficientes. Já foram concedidas %d de um total de%d aprovações. pulls.blocked_by_rejection=Este pedido de integração tem alterações solicitadas por um avaliador oficial. pulls.blocked_by_outdated_branch=Este pedido de integração foi bloqueado por ser obsoleto. +pulls.blocked_by_changed_protected_files_1=Este pedido de integração está bloqueado porque altera um ficheiro protegido: +pulls.blocked_by_changed_protected_files_n=Este pedido de integração está bloqueado porque altera ficheiros protegidos: pulls.can_auto_merge_desc=A integração constante neste pedido pode ser executada automaticamente. pulls.cannot_auto_merge_desc=A integração constante neste pedido não pode ser executada automaticamente porque existem conflitos. pulls.cannot_auto_merge_helper=Faça a integração manualmente para resolver os conflitos. @@ -1147,11 +1256,9 @@ pulls.rebase_merge_commit_pull_request=Mudar a base e integrar (--no-ff) pulls.squash_merge_pull_request=Comprimir e integrar pulls.require_signed_wont_sign=O ramo requer que os cometimentos sejam assinados mas esta integração não vai ser assinada pulls.invalid_merge_option=Não pode usar esta opção de integração neste pedido de integração. -pulls.merge_conflict=Integração falhada: Houve um conflito durante a integração: %[1]s
%[2]s
Dica: tente uma estratégia diferente -pulls.rebase_conflict=Integração falhada: Ocorreu um conflito durante a mudança da base no cometimento: %[1]s
%[2]s
%[3]s
Dica: Tente uma estratégia diferente +; %[2]s
%[3]s
pulls.unrelated_histories=Integração falhada: A cabeça da integração e a base não partilham um histórico comum. Dica: Tente uma estratégia diferente pulls.merge_out_of_date=Falhou a integração: Enquanto estava a gerar a integração, a base foi modificada. Dica: Tente de novo. -pulls.push_rejected=Integração falhada: O envio foi rejeitado com a seguinte mensagem:
%s
Reveja os automatismos do git para este repositório pulls.push_rejected_no_message=Integração falhada: O envio foi rejeitado mas não houve qualquer mensagem remota.
Reveja os automatismos do git para este repositório pulls.open_unmerged_pull_exists=`Não pode executar uma operação de reabertura porque há um pedido de integração pendente (#%d) com propriedades idênticas.` pulls.status_checking=Algumas verificações estão pendentes @@ -1159,6 +1266,8 @@ pulls.status_checks_success=Todas as verificações foram bem sucedidas pulls.status_checks_warning=Algumas verificações reportaram avisos pulls.status_checks_failure=Algumas verificações falharam pulls.status_checks_error=Algumas verificações reportaram erros +pulls.status_checks_requested=Obrigatório +pulls.status_checks_details=Detalhes pulls.update_branch=Sincronizar ramo pulls.update_branch_success=A sincronização do ramo foi bem sucedida pulls.update_not_allowed=Não tem autorização para sincronizar o ramo @@ -1170,6 +1279,7 @@ milestones.new=Nova etapa milestones.open_tab=%d abertas milestones.close_tab=%d fechadas milestones.closed=Encerrada %s +milestones.update_ago=Modificada há %s milestones.no_due_date=Sem data limite milestones.open=Abrir milestones.close=Fechar @@ -1209,6 +1319,7 @@ signing.wont_sign.basesigned=A integração não irá ser assinada, uma vez que signing.wont_sign.headsigned=A integração não irá ser assinada, uma vez que o cometimento de topo não foi assinado signing.wont_sign.commitssigned=A integração não irá ser assinada, uma vez que nenhum dos cometimentos associados foram assinados signing.wont_sign.approved=A integração não irá ser assinada, uma vez que o pedido de integração não foi assinado +signing.wont_sign.not_signed_in=Não tem a sessão iniciada ext_wiki=Wiki Ext. ext_wiki.desc=Ligação para um wiki externo. @@ -1354,6 +1465,7 @@ settings.pulls.allow_merge_commits=Habilitar integração de cometimentos settings.pulls.allow_rebase_merge=Habilitar cometimentos de mudança de base para integrar settings.pulls.allow_rebase_merge_commit=Habilitar mudança de base com cometimentos de integração explícitos (--no-ff) settings.pulls.allow_squash_commits=Habilitar cometimentos de condensação para integrar +settings.projects_desc=Habilitar projectos no repositório settings.admin_settings=Configurações do administrador settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório settings.admin_enable_close_issues_via_commit_in_any_branch=Fechar uma questão através de um cometimento feito num ramo não padrão @@ -1364,11 +1476,29 @@ settings.convert_desc=Pode converter este espelho num repositório normal. Esta settings.convert_notices_1=Esta operação irá converter o espelho num repositório normal e não poderá ser revertida. settings.convert_confirm=Converter repositório settings.convert_succeed=O espelho foi convertido num repositório normal. +settings.convert_fork=Converter para um repositório normal +settings.convert_fork_desc=Pode converter esta derivação num repositório normal. Esta operação não pode ser revertida. +settings.convert_fork_notices_1=Esta operação irá converter a derivação num repositório normal e não poderá ser revertida. +settings.convert_fork_confirm=Converter repositório +settings.convert_fork_succeed=A derivação foi convertida num repositório normal. settings.transfer=Transferir a propriedade settings.transfer_desc=Transferir este repositório para um utilizador ou para uma organização na qual você tenha direitos de administrador. settings.transfer_notices_1=- Você perderá o acesso ao repositório se transferir para um utilizador individual. settings.transfer_notices_2=- Você manterá o acesso ao repositório se o transferir para uma organização da qual você é (co-)proprietário. settings.transfer_form_title=Insira o nome do repositório para confirmar: +settings.signing_settings=Configurações de verificação de assinatura +settings.trust_model=Modelo de confiança na assinatura +settings.trust_model.default=Modelo de confiança padrão +settings.trust_model.default.desc=Usar o modelo de confiança padrão do repositório para esta instalação. +settings.trust_model.collaborator=Colaborador +settings.trust_model.collaborator.long=Colaborador: Confiar nas assinaturas dos colaboradores +settings.trust_model.collaborator.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "fiável" (quer correspondam ou não ao autor do cometimento). Caso contrário, assinaturas válidas serão marcadas como "não fiável" se a assinatura corresponder ao autor do cometimento e "não corresponde", se não corresponder. +settings.trust_model.committer=Autor do cometimento +settings.trust_model.committer.long=Autor do cometimento: Confiar nas assinaturas que correspondam aos autores dos cometimentos (isto corresponde ao funcionamento do GitHub e força a que os cometimentos assinados do Gitea tenham o Gitea como autor do cometimento) +settings.trust_model.committer.desc=Assinaturas válidas apenas serão marcadas como "fiável" se corresponderem ao autor do cometimento, caso contrário serão marcadas como "não corresponde". Isto irá forçar a que o Gitea seja o autor do cometimento nos cometimentos assinados, ficando o autor real marcado como Co-Autorado-Por: e Co-Cometido-Por: no resumo do cometimento. A chave padrão do Gitea tem que corresponder a um utilizador na base de dados. +settings.trust_model.collaboratorcommitter=Colaborador + Autor do cometimento +settings.trust_model.collaboratorcommitter.long=Colaborador + Autor do cometimento: Confiar nas assinaturas dos colaboradores que correspondam ao autor do cometimento +settings.trust_model.collaboratorcommitter.desc=Assinaturas válidas feitas por colaboradores deste repositório serão marcadas como "fiável" se corresponderem ao autor do cometimento. Caso contrário, assinaturas válidas serão marcadas como "não fiável" se a assinatura corresponder ao autor do cometimento e "não corresponde" se não corresponder. Isto irá forçar a que o Gitea seja marcado como sendo o autor do cometimento nos cometimentos assinados, ficando o autor real marcado como Co-Autorado-Por: e Co-Cometido-Por: no resumo do cometimento. A chave padrão do Gitea tem que corresponder a um utilizador na base de dados. settings.wiki_delete=Eliminar dados do wiki settings.wiki_delete_desc=Eliminar os dados do repositório do wiki é permanente e não pode ser revertido. settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente o repositório do wiki para %s. @@ -1525,6 +1655,7 @@ settings.protect_enable_push=Habilitar envio settings.protect_enable_push_desc=Qualquer utilizador com acesso de escrita terá permissão para enviar para este ramo (mas não poderá fazer envios forçados). settings.protect_whitelist_committers=Lista de permissão restrita para envio settings.protect_whitelist_committers_desc=Apenas os utilizadores ou equipas da lista terão permissão para enviar para este ramo (mas não poderão fazer envios forçados). +settings.protect_whitelist_deploy_keys=Dar permissão às chaves de instalação para terem acesso de escrita para enviar. settings.protect_whitelist_users=Utilizadores com permissão para enviar: settings.protect_whitelist_search_users=Procurar utilizadores… settings.protect_whitelist_teams=Equipas com permissão para enviar: @@ -1534,6 +1665,7 @@ settings.protect_merge_whitelist_committers_desc=Permitir que somente utilizador settings.protect_merge_whitelist_users=Utilizadores com permissão para executar integrações: settings.protect_merge_whitelist_teams=Equipas com permissão para executar integrações: settings.protect_check_status_contexts=Habilitar verificação de estado +settings.protect_check_status_contexts_desc=Exigir que as verificações de estado passem antes de ser aplicada a integração. Escolha quais as verificações de estado que têm de passar para que os ramos possam ser integrados num ramo que corresponda a esta regra. Quando habilitado, os cometimentos primeiro têm de ser enviados para outro ramo e depois integrados, ou então enviados imediatamente para um ramo que corresponda a esta regra, após terem passado as verificações de estado. Se não forem escolhidos quaisquer contextos, o último cometimento tem que ser bem sucedido, independentemente do contexto. settings.protect_check_status_contexts_list=Verificações de estado encontradas na última semana para este repositório settings.protect_required_approvals=Aprovações necessárias: settings.protect_required_approvals_desc=Permitir somente a integração constante de pedidos que tenham avaliações positivas suficientes. @@ -1544,7 +1676,9 @@ settings.protect_approvals_whitelist_teams=Equipas com permissão para rever: settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals_desc=Quando novos cometimentos que mudam o conteúdo do pedido de integração são enviados para o ramo, as aprovações antigas serão descartadas. settings.require_signed_commits=Exigir cometimentos assinados +settings.require_signed_commits_desc=Rejeitar envios para este ramo se não estiverem assinados ou não forem verificáveis. settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula '\;'): +settings.protect_protected_file_patterns_desc=Ficheiros protegidos que não podem ser alterados, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula ('\;'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Habilitar salvaguarda settings.delete_protected_branch=Desabilitar salvaguarda settings.update_protect_branch_success=A salvaguarda do ramo '%s' foi modificada. @@ -1643,6 +1777,8 @@ diff.review.placeholder=Comentário da avaliação diff.review.comment=Comentar diff.review.approve=Aprovar diff.review.reject=Solicitar alterações +diff.committed_by=cometido por +diff.protected=Protegido releases.desc=Acompanhe as versões e as descargas do repositório. release.releases=Lançamentos @@ -1651,6 +1787,8 @@ release.draft=Rascunho release.prerelease=Pré-lançamento release.stable=Estável release.edit=editar +release.ahead.commits=%d cometimentos +release.ahead.target=para %s desde este lançamento release.source_code=Código fonte release.new_subheader=Lançamentos organizam as versões do trabalho. release.edit_subheader=Lançamentos organizam as versões do trabalho. @@ -1741,7 +1879,9 @@ settings.repoadminchangeteam=O administrador do repositório pode adicionar e re settings.visibility=Visibilidade settings.visibility.public=Público settings.visibility.limited=Limitado (visível apenas para utilizadores com sessão iniciada) +settings.visibility.limited_shortname=Limitada settings.visibility.private=Privada (visível apenas para os membros da organização) +settings.visibility.private_shortname=Privado settings.update_settings=Modificar configurações settings.update_setting_success=As configurações da organização foram modificadas. @@ -1837,6 +1977,12 @@ dashboard.operation_switch=Comutar dashboard.operation_run=Executar dashboard.clean_unbind_oauth=Limpar conexões OAuth não vinculadas dashboard.clean_unbind_oauth_success=Todas as conexões OAuth não vinculadas foram eliminadas. +dashboard.task.started=Tarefa iniciada: %[1]s +dashboard.task.process=Tarefa: %[1]s +dashboard.task.cancelled=Tarefa: %[1]s cancelada: %[3]s +dashboard.task.error=Erro na tarefa: %[1]s: %[3]s +dashboard.task.finished=Tarefa: %[1]s iniciada por %[2]s foi concluída +dashboard.task.unknown=Tarefa desconhecida: %[1]s dashboard.cron.started=Cron iniciado: %[1] dashboard.cron.process=Cron: %[1]s dashboard.cron.cancelled=Cron: %s cancelado: %[3]s @@ -1858,6 +2004,8 @@ dashboard.update_migration_poster_id=Sincronizar os IDs do remetente da migraç dashboard.git_gc_repos=Fazer a recolha do lixo em todos os repositórios dashboard.resync_all_sshkeys=Sincronizar o ficheiro '.ssh/authorized_keys' com as chaves SSH do Gitea. dashboard.resync_all_sshkeys.desc=(não é necessário no caso do servidor SSH integrado) +dashboard.resync_all_sshprincipals=Modificar o ficheiro '.ssh/authorized_principals' com os protagonistas SSH do Gitea. +dashboard.resync_all_sshprincipals.desc=(não é necessário no caso do servidor SSH integrado). dashboard.resync_all_hooks=Voltar a sincronizar automatismos de pré-acolhimento, modificação e pós-acolhimento de todos os repositórios. dashboard.reinit_missing_repos=Reinicializar todos os repositórios Git em falta para os quais existam registos dashboard.sync_external_users=Sincronizar dados externos do utilizador @@ -1898,6 +2046,7 @@ users.full_name=Nome completo users.activated=Operante users.admin=Admin. users.restricted=Restrita +users.2fa=A2F users.repos=Repos. users.created=Criada users.last_login=Último acesso @@ -1918,7 +2067,7 @@ users.prohibit_login=Desabilitar início de sessão users.is_admin=É administrador(a) users.is_restricted=É restrito users.allow_git_hook=Pode criar automatismos do Git -users.allow_git_hook_tooltip=Os automatismos do Git são executados como o utilizador do sistema operativo que corre o Gitea e irão ter o mesmo nível de acesso ao servidor +users.allow_git_hook_tooltip=Os Automatismos do Git são executados em nome do utilizador do sistema operativo que corre o Gitea e têm o mesmo nível de acesso ao servidor. Por causa disso, utilizadores com este privilégio especial de Automatismo do Git podem aceder e modificar todos os repositórios do Gitea, assim como a base de dados usada pelo Gitea. Consequentemente, também podem ganhar privilégios de administrador do Gitea. users.allow_import_local=Pode importar repositórios locais users.allow_create_organization=Pode criar organizações users.update_profile=Modificar conta do utilizador @@ -1947,6 +2096,8 @@ orgs.members=Membros orgs.new_orga=Nova organização repos.repo_manage_panel=Gestão dos repositórios +repos.unadopted=Repositórios não adoptados +repos.unadopted.no_more=Não foram encontrados mais repositórios não adoptados repos.owner=Proprietário(a) repos.name=Nome repos.private=Privado @@ -1996,6 +2147,11 @@ auths.filter=Filtro de utilizador auths.admin_filter=Filtro de administrador auths.restricted_filter=Filtro restrito auths.restricted_filter_helper=Deixe em branco para não definir quaisquer utilizadores como restritos. Use um asterisco ('*') para definir todos os utilizadores que não correspondam ao filtro de administrador como restritos. +auths.verify_group_membership=Verificar afiliação ao grupo no LDAP +auths.group_search_base=Base DN para a pesquisa de grupos +auths.valid_groups_filter=Filtro de grupos válidos +auths.group_attribute_list_users=Atributo de grupo que contém a lista de utilizadores +auths.user_attribute_in_group=Atributo de utilizador listado no grupo auths.ms_ad_sa=Atributos de pesquisa do MS AD auths.smtp_auth=Tipo de autenticação SMTP auths.smtphost=Servidor SMTP @@ -2136,6 +2292,7 @@ config.mailer_use_sendmail=Usar o sendmail config.mailer_sendmail_path=Caminho do sendmail config.mailer_sendmail_args=Argumentos extras para o sendmail config.mailer_sendmail_timeout=Tempo limite do Sendmail +config.test_email_placeholder=Email (ex.: teste@exemplo.com) config.send_test_mail=Enviar email de teste config.test_mail_failed=Ocorreu uma falha ao enviar um email de teste para '%s': %v config.test_mail_sent=Foi enviado um email de teste para '%s'. @@ -2153,7 +2310,6 @@ config.session_config=Configuração de sessão config.session_provider=Fornecedor da sessão config.provider_config=Configuração do fornecedor config.cookie_name=Nome do cookie -config.enable_set_cookie=Habilitar o criação de cookie config.gc_interval_time=Intervalo da recolha do lixo config.session_life_time=Tempo de vida da sessão config.https_only=Apenas HTTPS @@ -2295,6 +2451,7 @@ mirror_sync_create=sincronizou a nova referência %[2]s mirror_sync_delete=sincronizou e eliminou a referência %[2]s em %[3]s do ficheiro approve_pull_request=`aprovou %s#%[2]s` reject_pull_request=`sugeriu alterações para %s#%[2]s` +publish_release=`lançou "%[4]s" à %[3]s` [tool] ago=há %s diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index e34606bd4e2a..59b602956b74 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -20,10 +20,13 @@ user_profile_and_more=Профиль и настройки... signed_in_as=Вы вошли как enable_javascript=Пожалуйста, включите JavaScript. toc=Содержание +licenses=Лицензии +return_to_gitea=Вернуться к Gitea username=Имя пользователя email=Адрес эл. почты password=Пароль +access_token=Токен доступа re_type=Введите пароль еще раз captcha=CAPTCHA twofa=Двухфакторная аутентификация @@ -51,6 +54,8 @@ new_migrate=Новая миграция new_mirror=Новое зеркало new_fork=Новый форк репозитория new_org=Новая организация +new_project=Новый проект +new_project_board=Новая доска проекта manage_org=Управление организациями admin_panel=Панель управления account_settings=Настройки аккаунта @@ -71,6 +76,7 @@ issues=Задачи milestones=Этапы cancel=Отмена +save=Сохранить add=Добавить add_all=Добавить все remove=Удалить @@ -89,6 +95,7 @@ report_message=Если вы уверены, что это ошибка Gitea, [startpage] app_desc=Удобный сервис собственного хостинга репозиториев Git install=Простой в установке +install_desc=Просто запустите исполняемый файл для вашей платформы, разверните через Docker, или установите с помощью менеджера пакетов. platform=Кроссплатформенный platform_desc=Gitea работает на любой операционной системе, которая может компилировать Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! lightweight=Легковесный @@ -117,7 +124,7 @@ sqlite_helper=Путь к файлу базы данных SQLite3.
Введ err_empty_db_path=Путь к базе данных SQLite3 не может быть пустым. no_admin_and_disable_registration=Вы не можете отключить регистрацию до создания учётной записи администратора. err_empty_admin_password=Пароль администратора не может быть пустым. -err_empty_admin_email=Email администратора не может быть пустым. +err_empty_admin_email=Адрес электронной почты администратора не может быть пустым. err_admin_name_is_reserved=Неверное имя администратора, это имя зарезервировано err_admin_name_pattern_not_allowed=Неверное имя администратора, имя попадает под зарезервированный шаблон err_admin_name_is_invalid=Неверное имя администратора @@ -126,7 +133,7 @@ general_title=Основные настройки app_name=Название сайта app_name_helper=Здесь вы можете ввести название своей компании. repo_path=Путь корня репозитория -repo_path_helper=Все удаленные Git репозиториии будут сохранены в этот каталог. +repo_path_helper=Все удалённые Git репозитории будут сохранены в этот каталог. lfs_path=Корневой путь Git LFS lfs_path_helper=В этой папке будут храниться файлы Git LFS. Оставьте пустым, чтобы отключить LFS. run_user=Запуск от имени пользователя @@ -143,10 +150,10 @@ log_root_path=Путь к журналу log_root_path_helper=Файлы журнала будут записываться в этот каталог. optional_title=Расширенные настройки -email_title=Настройки Email +email_title=Настройки электронной почты smtp_host=Узел SMTP -smtp_from=Отправлять Email от имени -smtp_from_helper=Адрес электронной почты, который будет использоваться Gitea. Введите обычный адрес электронной почты или используйте формат «Имя» . +smtp_from=Отправить эл. почту как +smtp_from_helper=Адрес электронной почты, который будет использоваться Gitea. Введите обычный адрес электронной почты или используйте формат "Имя" . mailer_user=SMTP логин mailer_password=SMTP пароль register_confirm=Требовать подтверждение по электронной почте для регистрации @@ -165,6 +172,7 @@ 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=Ограничение доступа к страницам только для пользователей, выполнивших вход. Посетители увидят только страницы входа и регистрации. @@ -194,7 +202,7 @@ no_reply_address=Скрытый почтовый домен no_reply_address_helper=Доменное имя для пользователей со скрытым адресом электронной почты. Например, имя пользователя 'joe' будет зарегистрировано в Git как 'joe@noreply.example.org' если скрытый домен электронной почты установлен как 'noreply.example.org'. [home] -uname_holder=Имя пользователя / Email +uname_holder=Имя пользователя / Адрес эл. почты password_holder=Пароль switch_dashboard_context=Переключить контекст панели управления my_repos=Репозитории @@ -204,8 +212,17 @@ my_orgs=Мои организации my_mirrors=Мои зеркала view_home=Показать %s search_repos=Поиск репозитория… +filter=Другие фильтры +show_archived=Архивировано +show_both_archived_unarchived=Показаны архивированные и разархивированные +show_only_archived=Показаны только архивированные +show_only_unarchived=Показаны только разархивированные +show_private=Приватный +show_both_private_public=Показаны как публичные, так и частные +show_only_private=Показаны только приватные +show_only_public=Показаны только публичные issues.in_your_repos=В ваших репозиториях @@ -239,8 +256,8 @@ allow_password_change=Требовать смену пароля пользов reset_password_mail_sent_prompt=Письмо с подтверждением было отправлено на %s. Пожалуйста, проверьте входящую почту в течение %s, чтобы завершить процесс восстановления аккаунта. active_your_account=Активируйте свой аккаунт account_activated=Учётная запись была активирована -prohibit_login=Вход запрещен -prohibit_login_desc=Вход для вашей учетной записи был запрещен, пожалуйста, свяжитесь с администратором сайта. +prohibit_login=Вход запрещён +prohibit_login_desc=Вход для вашей учётной записи был запрещён, пожалуйста, свяжитесь с администратором сайта. resent_limit_prompt=Извините, вы уже запросили активацию по электронной почте недавно. Пожалуйста, подождите 3 минуты, а затем повторите попытку. has_unconfirmed_mail=Здравствуйте, %s! У вас есть неподтвержденный адрес электронной почты (%s). Если вам не приходило письмо с подтверждением или нужно выслать новое письмо, нажмите на кнопку ниже. resend_mail=Нажмите здесь, чтобы переотправить активационное письмо @@ -261,11 +278,11 @@ 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=Привязать учетную запись +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 неизвестен. Свяжите с новой учетной записью здесь. @@ -273,20 +290,22 @@ 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 регистрация невозможна. +email_domain_blacklisted=С данным адресом электронной почты регистрация невозможна. authorize_application=Авторизация приложения authorize_redirect_notice=Вы будете перенаправлены на %s, если вы авторизуете это приложение. authorize_application_created_by=Это приложение было создано %s. -authorize_application_description=Если вы предоставите доступ, оно сможет получить доступ и редактировать любую информацию о вашей учетной записи, включая содержимое частных репозиториев и организаций. -authorize_title=Разрешить «%s» доступ к вашей учетной записи? +authorize_application_description=Если вы предоставите доступ, оно сможет получить доступ и редактировать любую информацию о вашей учётной записи, включая содержимое частных репозиториев и организаций. +authorize_title=Разрешить "%s" доступ к вашей учётной записи? authorization_failed=Ошибка авторизации authorization_failed_desc=Ошибка авторизации, обнаружен неверный запрос. Пожалуйста, свяжитесь с автором приложения, которое вы пытались авторизовать. sspi_auth_failed=SSPI аутентификация не удалась +password_pwned=Выбранный вами пароль находится в списке украденных паролей ранее выставленных в публичных нарушениях данных. Повторите попытку с другим паролем. +password_pwned_err=Не удалось завершить запрос к HaveIBeenPwned [mail] activate_account=Пожалуйста активируйте свой аккаунт activate_email=Подтвердите адрес своей электронной почты -reset_password=Восстановить учетную запись +reset_password=Восстановить учётную запись register_success=Регистрация прошла успешно register_notify=Добро пожаловать на Gitea @@ -297,7 +316,7 @@ modify=Изменить [form] UserName=Пользователь -RepoName=Имя репозитория +RepoName=Название репозитория Email=Адрес эл. почты Password=Пароль Retype=Введите пароль еще раз @@ -325,7 +344,7 @@ git_ref_name_error=` должно быть правильным ссылочны size_error=` должен быть размер %s.` min_size_error=«должен содержать по крайней мере %s символов.» max_size_error=` должен содержать максимум %s символов.` -email_error=«не является адресом электронной почты.» +email_error=`не является адресом электронной почты.` url_error=` не является допустимым URL-адресом.` include_error=` должен содержать '%s'.` glob_pattern_error=` неверный glob шаблон: %s.` @@ -335,9 +354,13 @@ password_not_match=Пароли не совпадают. lang_select_error=Выберите язык из списка. username_been_taken=Имя пользователя уже занято. -repo_name_been_taken=Имя репозитория уже используется. -visit_rate_limit=Удаленный вход отклонён в связи с ограничением количества попыток в секунду. -2fa_auth_required=Удаленный вход требует двухфакторную аутентификацию. +repo_name_been_taken=Название репозитория уже используется. +repository_files_already_exist=Файлы уже существуют для этого репозитория. Обратитесь к системному администратору. +repository_files_already_exist.adopt=Файлы уже существуют для этого репозитория и могут быть только приняты. +repository_files_already_exist.delete=Файлы уже существуют для этого репозитория. Вы должны удалить их. +repository_files_already_exist.adopt_or_delete=Файлы уже существуют для этого репозитория. Или принять их или удалить их. +visit_rate_limit=Удалённый вход отклонён в связи с ограничением количества попыток в секунду. +2fa_auth_required=Удалённый вход требует двух-факторную аутентификацию. org_name_been_taken=Название организации уже занято. team_name_been_taken=Название команды уже занято. team_no_units_error=Разрешите доступ хотя бы к одному разделу репозитория. @@ -349,12 +372,12 @@ password_lowercase_one=Как минимум один строчный симв password_uppercase_one=Как минимум один заглавный символ password_digit_one=По крайней мере одна цифра password_special_one=По крайней мере один специальный символ (знаки пунктуации, скобки, кавычки и т. д.) -enterred_invalid_repo_name=Введенное вами имя репозитория неверно. +enterred_invalid_repo_name=Введённое вами название репозитория неверно. enterred_invalid_owner_name=Имя нового владельца недоступно. enterred_invalid_password=Введенный пароль неверный. user_not_exist=Пользователь не существует. team_not_exist=Команда не существует. -last_org_owner=Вы не можете удалить последнего пользователя из команды 'владельцы'. В любой команде должен быть хотя бы один владелец. +last_org_owner=Вы не можете удалить последнего пользователя из команды 'Владельцы'. Для организации должен быть хотя бы один владелец. cannot_add_org_to_team=Организацию нельзя добавить в качестве члена команды. invalid_ssh_key=Не удается проверить SSH ключ: %s @@ -370,16 +393,18 @@ target_branch_not_exist=Целевая ветка не существует [user] change_avatar=Измените свой авататар… -join_on=Присоединился +join_on=Присоединился(-ась) repositories=Репозитории activity=Активность followers=Подписчики starred=Избранные репозитории -following=Подписан +projects=Проекты +following=Подписки follow=Подписаться unfollow=Отписаться heatmap.loading=Загрузка тепловой карты… user_bio=О себе +disabled_public_activity=Этот пользователь отключил публичную видимость активности. form.name_reserved=Имя пользователя '%s' зарезервировано. form.name_pattern_not_allowed=Шаблон '%s' не допускается в имени пользователя. @@ -404,6 +429,7 @@ uid=UID u2f=Ключи безопасности public_profile=Открытый профиль +biography_placeholder=Расскажите немного о себе profile_desc=Ваш адрес электронной почты будет использован для уведомлений и других операций. password_username_disabled=Нелокальным пользователям запрещено изменение их имени пользователя. Для получения более подробной информации обратитесь к администратору сайта. full_name=ФИО @@ -418,6 +444,9 @@ continue=Далее cancel=Отмена language=Язык ui=Тема +privacy=Приватность +keep_activity_private=Скрыть активность со страницы профиля +keep_activity_private_popup=Делает активность видимой только для вас и администраторов lookup_avatar_by_mail=Найти аватар по адресу эл. почты federated_avatar_lookup=Найти внешний аватар @@ -438,7 +467,7 @@ change_password_success=Ваш пароль был изменён. С этого password_change_disabled=Нелокальные аккаунты не могут изменить пароль через Gitea. emails=Email адреса -manage_emails=Управление Email адресами +manage_emails=Управление адресами электронной почты manage_themes=Выберите тему по умолчанию manage_openid=Управление OpenID email_desc=Ваш основной адрес электронной почты будет использован для уведомлений и других операций. @@ -452,7 +481,7 @@ activations_pending=Ожидает активации delete_email=Удалить email_deletion=Удалить адрес электронной почты email_deletion_desc=Адрес электронной почты и вся связанная с ним информация будет удалена из вашего аккаунта. Коммиты, сделанные от имени этого адреса электронной почты, не будут изменены. Продолжить? -email_deletion_success=Ваш Email адрес был удален. +email_deletion_success=Ваш адрес электронной почты был удалён. theme_update_success=Тема была изменена. theme_update_error=Выбранная тема не существует. openid_deletion=Удалить OpenID URI @@ -479,8 +508,9 @@ ssh_helper=Нужна помощь? Ознакомьтесь с gpg_helper=Нужна помощь? Взгляните на руководство GitHub по GPG. add_new_key=Добавить SSH ключ add_new_gpg_key=Добавить GPG ключ +key_content_ssh_placeholder=Начинается с 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', или 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Начинается с '-----BEGIN PGP PUBLIC KEY BLOCK-----' ssh_key_been_used=Этот SSH ключ уже был добавлен на сервер. -ssh_key_name_used=SSH ключ с таким именем уже добавлен в вашу учетную запись. gpg_key_id_used=Публичный GPG ключ с таким же идентификатором уже существует. gpg_no_key_email_found=Этот ключ GPG не может использоваться с любым адресом электронной почты, привязанной к вашей учетной записи. subkeys=Подключи @@ -508,7 +538,6 @@ token_state_desc=Этот токен использовался в течени show_openid=Показывать в профиле hide_openid=Скрыть из профиля ssh_disabled=SSH отключён - manage_social=Управление привязанными учетными записями в соцсетях social_desc=Эти социальные сети связаны с вашим аккаунтом Gitea. Их можно использовать для входа в учетную запись Gitea, поэтому необходимо быть уверенным в том, что никаких посторонних аккаунтов не подключено. unbind=Удалить связь @@ -549,8 +578,8 @@ oauth2_regenerate_secret=Сгенерировать новый ключ oauth2_regenerate_secret_hint=Потеряли свой ключ? oauth2_client_secret_hint=Секретный ключ не будет показан, если вы повторно откроете эту страницу. Пожалуйста сохраните секретный ключ. oauth2_application_edit=Изменить -oauth2_application_create_description=Приложения OAuth2 предоставляет стороннему приложению доступ к учетным записям пользователей данного сервиса. -oauth2_application_remove_description=Удаление приложения OAuth2 приведёт к отмене его доступа к авторизованным учетным записям пользователей в данном экземпляре. Продолжить? +oauth2_application_create_description=Приложения OAuth2 предоставляет стороннему приложению доступ к учётным записям пользователей данного сервиса. +oauth2_application_remove_description=Удаление приложения OAuth2 приведёт к отмене его доступа к авторизованным учётным записям пользователей в данном экземпляре. Продолжить? authorized_oauth2_applications=Авторизованные приложения OAuth2 authorized_oauth2_applications_description=Вы предоставили доступ к вашему персональному аккаунту Gitea этим сторонним приложениям. Пожалуйста, отзовите доступ у приложений, которые больше не используются. @@ -601,13 +630,13 @@ delete_account_title=Удалить аккаунт delete_account_desc=Вы уверены, что хотите навсегда удалить этот аккаунт? email_notifications.enable=Включить почтовые уведомления -email_notifications.onmention=Только уведомлять по почте при упоминании +email_notifications.onmention=Посылать письмо на эл. почту только при упоминании email_notifications.disable=Отключить почтовые уведомления email_notifications.submit=Установить настройки электронной почты [repo] owner=Владелец -repo_name=Имя репозитория +repo_name=Название репозитория repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов. repo_size=Размер репозитория template=Шаблон @@ -643,7 +672,7 @@ mirror_prune_desc=Удаление устаревших отслеживаемы mirror_interval=Интервал зеркалирования (допустимые единицы измерения 'h', 'm', 's'). Значение 0 отключает синхронизацию. mirror_interval_invalid=Недопустимый интервал зеркалирования. mirror_address=Клонировать по URL -mirror_address_desc=Поместите все необходимые учетные данные в раздел Авторизация клона. +mirror_address_desc=Поместите все необходимые учётные данные в раздел Авторизация клона. mirror_address_url_invalid=Указанный url неверный. Вы должны правильно экранировать все компоненты url. mirror_address_protocol_invalid=Указанный url неверный. Только http(s):// или git:// местоположения могут быть зеркалированы. mirror_last_synced=Последняя синхронизация @@ -654,12 +683,28 @@ pick_reaction=Оставьте свою оценку! reactions_more=и ещё %d unit_disabled=Администратор сайта отключил этот раздел репозитория. language_other=Разное - +adopt_search=Введите имя пользователя для поиска неутверждённых репозиториев... (оставьте пустым, чтобы найти все) +adopt_preexisting_label=Принятые файлы +adopt_preexisting=Принять уже существующие файлы +adopt_preexisting_content=Создать репозиторий из %s +adopt_preexisting_success=Приняты файлы и создан репозиторий из %s +delete_preexisting_label=Удалить +delete_preexisting=Удалить уже существующие файлы +delete_preexisting_content=Удалить файлы из %s +delete_preexisting_success=Удалены непринятые файлы в %s + +desc.private=Приватный +desc.public=Публичный +desc.private_template=Приватный шаблон +desc.public_template=Шаблон +desc.internal=Внутренний +desc.internal_template=Внутренний шаблон +desc.archived=Архивировано template.items=Элементы шаблона template.git_content=Содержимое Git (Ветвь По Умолчанию) -template.git_hooks=Git хуки -template.git_hooks_tooltip=В настоящее время вы не можете изменить или удалить Git хуки после добавления. Выберите, только если вы доверяете репозиторию шаблона. +template.git_hooks=Git hook'и +template.git_hooks_tooltip=В настоящее время вы не можете изменить или удалить Git hook'и после добавления. Выберите, только если вы доверяете репозиторию шаблона. template.webhooks=Веб-хуки template.topics=Темы template.avatar=Аватар @@ -672,18 +717,21 @@ archive.issue.nocomment=Этот репозиторий в архиве. Вы н archive.pull.nocomment=Это архивный репозиторий. Вы не можете комментировать запросы на слияние. form.reach_limit_of_creation=Вы уже достигли ваш предел %d репозиториев. -form.name_reserved=Имя репозитория '%s' зарезервировано. +form.name_reserved=Название репозитория '%s' зарезервировано. form.name_pattern_not_allowed=Шаблон имени репозитория '%s' не допускается. need_auth=Требуется авторизация -migrate_type=Тип миграции -migrate_type_helper=Этот репозиторий будет зеркалом +migrate_options=Параметры миграции +migrate_service=Сервис миграции +migrate_options_mirror_helper=Этот репозиторий будет зеркалом +migrate_options_mirror_disabled=Администратор вашего сайта отключил новые зеркала. migrate_items=Элементы миграции migrate_items_wiki=Вики migrate_items_milestones=Этапы migrate_items_labels=Метки migrate_items_issues=Задачи migrate_items_pullrequests=Запросы на слияние +migrate_items_merge_requests=Запросы на слияние migrate_items_releases=Релизы migrate_repo=Перенос репозитория migrate.clone_address=Перенос / Клонирование по URL @@ -693,17 +741,23 @@ 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, укажите имя пользователя - и появятся параметры миграции. +migrate.migrate_items_options=Токен доступа необходим для миграции дополнительных элементов migrated_from=Перенесено с %[2]s migrated_from_fake=Перенесено с %[1]s +migrate.migrate=Миграция из %s migrate.migrating=Перенос из %s... migrate.migrating_failed=Перенос из %s не удался. +migrate.github.description=Миграция данных с Github.com или Github Enterprise. +migrate.git.description=Миграция или зеркалирование данных git из служб Git +migrate.gitlab.description=Миграция данных с GitLab.com или сервера Self-Hosted gitlab. mirror_from=зеркало из forked_from=форкнуто от generated_from=создано из fork_from_self=Вы не можете форкнуть репозиторий, так как вы уже его владелец. fork_guest_user=Войдите, чтобы форкнуть репозиторий. +watch_guest_user=Войдите, чтобы следить за этим репозиторием. +star_guest_user=Войдите, чтобы добавить в избранное этот репозиторий. copy_link=Скопировать copy_link_success=Ссылка была скопирована copy_link_error=Нажмите ⌘-C или Ctrl-C для копирования @@ -726,11 +780,13 @@ code=Код code.desc=Исходный код, файлы, коммиты и ветки. branch=ветка tree=Дерево +clear_ref=`Удалить текущую ссылку` filter_branch_and_tag=Фильтр по ветке или тегу branches=Ветки tags=Теги issues=Задачи pulls=Pull Request'ы +project_board=Проекты labels=Метки org_labels_desc=Метки уровня организации, которые можно использовать с всеми репозиториями в этой организации org_labels_desc_manage=управлять @@ -749,6 +805,8 @@ audio_not_supported_in_browser=Ваш браузер не поддерживае stored_lfs=Хранится Git LFS symbolic_link=Символическая ссылка commit_graph=Граф коммитов +commit_graph.monochrome=Моно +commit_graph.color=Цвет blame=Вина normal_view=Обычный вид line=строка @@ -774,8 +832,8 @@ editor.cancel_lower=Отменить editor.commit_signed_changes=Подписанные изменения editor.commit_changes=Сохранить правки editor.add_tmpl=Добавить '' -editor.add=Добавить '%s' -editor.update=Изменить '%s' +editor.add=Создал(а) '%s' +editor.update=Изменил(а) на '%s' editor.delete=Удалить '%s' editor.commit_message_desc=Добавьте необязательное расширенное описание… editor.commit_directly_to_this_branch=Сделайте коммит прямо в ветку %s. @@ -796,11 +854,9 @@ 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.commit_empty_file_text=Файл, который в коммите, пуст. Продолжить? editor.no_changes_to_show=Нет изменений. -editor.fail_to_update_file=Не удалось обновить/создать файл «%s» из-за ошибки: %v editor.push_rejected_no_message=Сервер отклонил изменение без сообщения. Пожалуйста, проверьте githooks. -editor.push_rejected=Изменение было отклонено сервером со следующим сообщением:
%s
Пожалуйста, проверьте githooks. editor.add_subdir=Добавить каталог… editor.unable_to_upload_files=Не удалось загрузить файлы в «%s» из-за ошибки: %v editor.upload_file_is_locked=Файл '%s' заблокирован %s. @@ -811,7 +867,7 @@ editor.user_no_push_to_branch=Пользователь не может отпр editor.require_signed_commit=Ветка ожидает подписанный коммит commits.desc=Просмотр истории изменений исходного кода. -commits.commits=коммитов +commits.commits=Коммитов commits.no_commits=Ничего общего в коммитах. '%s' и '%s' имеют совершенно разные истории. commits.search=Поиск коммитов… commits.search.tooltip=Вы можете предварять ключевые слова словами "author:", "committer:", "after:", или "before:", например, "revert author:Alice before:2019-04-01". @@ -830,10 +886,40 @@ commits.gpg_key_id=Идентификатор GPG ключа ext_issues=Внешние задачи ext_issues.desc=Ссылка на внешнюю систему отслеживания ошибок. +projects=Проекты +projects.desc=Управление задачами и pull'ами в проектных досках. +projects.create=Создать проект +projects.title=Заголовок +projects.new=Новый проект +projects.new_subheader=Координация, отслеживание и обновление работы в одном месте, так что проекты остаются прозрачными и по графику. +projects.create_success=Проект '%s' был создан. +projects.deletion=Удалить проект +projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить? +projects.deletion_success=Проект был удалён. +projects.edit=Редактировать проекты +projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс. +projects.modify=Обновить проект +projects.edit_success=Проект '%s' был обновлён. +projects.type.none=Нет +projects.type.basic_kanban=Обычный Канбан +projects.type.bug_triage=Планирование работы с багами +projects.template.desc=Шаблон проекта +projects.template.desc_helper=Выберите шаблон проекта для начала +projects.type.uncategorized=Без категории +projects.board.edit=Редактировать доску +projects.board.edit_title=Новое имя доски +projects.board.new_title=Название новой доски +projects.board.new_submit=Отправить +projects.board.new=Новая доска +projects.board.delete=Удалить доску +projects.board.deletion_desc=Удаление доски проектов перемещает все связанные задачи в 'Без категории'. Продолжить? +projects.open=Открыть +projects.close=Закрыть issues.desc=Организация отчетов об ошибках, задач и этапов. issues.filter_assignees=Фильтр назначений issues.filter_milestones=Фильтр этапов +issues.filter_projects=Фильтровать проекты issues.filter_labels=Фильтр меток issues.filter_reviewers=Фильтр рецензентов issues.new=Новая задача @@ -842,6 +928,12 @@ issues.new.labels=Метки issues.new.add_labels_title=Применить метки issues.new.no_label=Нет меток issues.new.clear_labels=Отчистить метки +issues.new.projects=Проекты +issues.new.add_project_title=Задать проект +issues.new.clear_projects=Очистить проекты +issues.new.no_projects=Нет проекта +issues.new.open_projects=Открытые проекты +issues.new.closed_projects=Закрытые проекты issues.new.no_items=Нет элементов issues.new.milestone=Этап issues.new.add_milestone_title=Установить этап @@ -855,6 +947,9 @@ issues.new.clear_assignees=Убрать ответственных issues.new.no_assignees=Нет назначенных лиц issues.new.no_reviewers=Нет рецензентов issues.new.add_reviewer_title=Запросить отзыв +issues.choose.get_started=Начать +issues.choose.blank=По умолчанию +issues.choose.blank_about=Создать запрос из шаблона по умолчанию. issues.no_ref=Не указана ветка или тэг issues.create=Добавить задачу issues.new_label=Новая метка @@ -866,12 +961,18 @@ issues.label_templates.info=Меток пока не существует. Со issues.label_templates.helper=Выберите метку issues.label_templates.use=Использовать набор меток issues.label_templates.fail_to_load_file=Не удалось загрузить файл шаблона метки «%s»: %v -issues.add_milestone_at=`добавил к этапу %s %s` +issues.add_label_at=добавил(а) метку
%s
%s +issues.remove_label_at=убрал метку
%s
%s +issues.add_milestone_at=`добавил(а) к этапу %s %s` +issues.add_project_at=`добавил в %s проект %s` issues.change_milestone_at=`поменял целевой этап с %s на %s %s` +issues.change_project_at=`изменил проект с %s на %s %s` issues.remove_milestone_at=`удалил из этапа %s %s` +issues.remove_project_at=`удалил это из проекта %s %s` issues.deleted_milestone=`(удалено)` +issues.deleted_project=`(удалено)` issues.self_assign_at=`самоназначился %s` -issues.add_assignee_at=`был назначен %s %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` @@ -915,7 +1016,7 @@ pulls.merged_by=принят %[1]s %[3]s pulls.merged_by_fake=%[1]s слита пользователем %[2]s issues.closed_by=на %[3]s закрытых %[1]s issues.opened_by_fake=%[1]s открыта %[2]s -issues.closed_by_fake=на %[2]s закрытым %[1]s +issues.closed_by_fake=%[2]s закрыл(а) %[1]s issues.previous=Предыдущая страница issues.next=Следующая страница issues.open_title=Открыто @@ -934,6 +1035,7 @@ issues.close_comment_issue=Прокомментировать и закрыть issues.reopen_issue=Открыть снова issues.reopen_comment_issue=Прокомментировать и открыть снова 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` @@ -947,6 +1049,7 @@ issues.poster=Автор issues.collaborator=Соавтор issues.owner=Владелец issues.re_request_review=Повторить запрос на отзыв +issues.is_stale=Со времени этого обзора в этот PR были внесены некоторые изменения issues.remove_request_review=Удалить запрос на отзыв issues.remove_request_review_block=Невозможно удалить запрос на отзыв issues.sign_in_require_desc=Войдите, чтобы присоединиться к обсуждению. @@ -995,7 +1098,7 @@ issues.comment_on_locked=Вы не можете оставить коммент issues.tracker=Отслеживание времени issues.start_tracking_short=Начать issues.start_tracking=Начать отслеживание времени -issues.start_tracking_history=`начал работать %s` +issues.start_tracking_history=`начал(а) работать %s` issues.tracker_auto_close=Таймер будет остановлен автоматически, когда эта проблема будет закрыта issues.tracking_already_started=`Вы уже начали отслеживать время для этой задачи!` issues.stop_tracking=Остановить @@ -1003,7 +1106,7 @@ issues.stop_tracking_history=`перестал работать %s` issues.add_time=Вручную добавить время issues.add_time_short=Добавить время issues.add_time_cancel=Отмена -issues.add_time_history=`добавил потраченное время %s` +issues.add_time_history=`добавил(а) к затраченному времени %s` issues.del_time_history=`удалил потраченное время %s` issues.add_time_hours=Часы issues.add_time_minutes=Минуты @@ -1016,6 +1119,9 @@ issues.due_date=Срок выполнения issues.invalid_due_date_format=Дата окончания должна быть в формате 'гггг-мм-дд'. issues.error_modifying_due_date=Не удалось изменить срок выполнения. issues.error_removing_due_date=Не удалось убрать срок выполнения. +issues.push_commit_1=добавил(а) %d коммит %s +issues.push_commits_n=добавил(а) %d коммитов %s +issues.force_push_codes=`Принудительный push %[1]s ветки из %[2]s to %[4]s. %[6]s` issues.due_date_form=гггг-мм-дд issues.due_date_form_add=Добавить срок выполнения issues.due_date_form_edit=Редактировать @@ -1046,7 +1152,7 @@ issues.dependency.blocks_short=Блоки issues.dependency.blocked_by_short=Зависит от issues.dependency.remove_header=Удалить зависимость issues.dependency.issue_remove_text=Это приведет к удалению зависимости от этой задачи. Продолжить? -issues.dependency.pr_remove_text=Это приведет к удалению зависимости от этого запроса на слияние. Продолжить? +issues.dependency.pr_remove_text=Это приведёт к удалению зависимости от этого запроса на слияние. Продолжить? issues.dependency.setting=Включение зависимостей для проблем и запросов на слияние issues.dependency.add_error_same_issue=Вы не можете заставить задачу зависеть от самой себя. issues.dependency.add_error_dep_issue_not_exist=Зависимая задача не существует. @@ -1072,6 +1178,9 @@ issues.review.show_outdated=Показать устаревшие issues.review.hide_outdated=Скрыть устаревшие issues.review.show_resolved=Показать разрешенные issues.review.hide_resolved=Скрыть разрешенные +issues.review.resolve_conversation=Покинуть диалог +issues.review.un_resolve_conversation=Незавершённый разговор +issues.review.resolved_by=пометить этот разговор как разрешённый issues.assignee.error=Не все назначения были добавлены из-за непредвиденной ошибки. pulls.desc=Включить запросы на слияние и проверки кода. @@ -1090,7 +1199,7 @@ pulls.merged_title_desc=слито %[1]d коммит(ов) из %[2]s%s на %s %s` pulls.tab_conversation=Обсуждение pulls.tab_commits=Коммиты -pulls.tab_files=Измененные файлы +pulls.tab_files=Изменённые файлы pulls.reopen_to_merge=Пожалуйста, переоткройте этот Pull Request для выполнения слияния. pulls.cant_reopen_deleted_branch=Этот запрос на слияние не может быть открыт заново, потому что ветка была удалена. pulls.merged=Слито @@ -1108,6 +1217,7 @@ pulls.required_status_check_administrator=Как администратор, в pulls.blocked_by_approvals=Этому Pull Request'у не хватает одобрений. Получено %d из %d одобрений. pulls.blocked_by_rejection=Официальным проверяющим были запрошены изменения для этого запроса на слияние. pulls.blocked_by_outdated_branch=Этот Pull Request заблокирован, потому что он устарел. +pulls.blocked_by_changed_protected_files_n=Этот Pull Request заблокирован, потому что он изменяет защищенные файлы: pulls.can_auto_merge_desc=Этот Pull Request может быть объединён автоматически. pulls.cannot_auto_merge_desc=Этот запрос на слияние не может быть объединён автоматически. pulls.cannot_auto_merge_helper=Пожалуйста, совершите слияние вручную для урегулирования конфликтов. @@ -1120,7 +1230,7 @@ pulls.reject_count_n=%d запросов на изменение pulls.waiting_count_1=%d ожидает проверки pulls.waiting_count_n=%d ожидающих отзывов -pulls.no_merge_desc=Pull Request не может быть принят, так как запрещены все методы организации рабочего потока. +pulls.no_merge_desc=Pull Request не может быть принят, так как отключены все настройки слияния. pulls.no_merge_helper=Включите опции слияния в настройках репозитория или совершите слияние Pull Request'а вручную. pulls.no_merge_wip=Данный Pull Request не может быть принят, поскольку он помечен как находящийся в разработке. pulls.no_merge_not_ready=Этот запрос не готов к слиянию, обратите внимания на ревью и проверки. @@ -1131,25 +1241,30 @@ pulls.rebase_merge_commit_pull_request=Выполнить rebase и принят pulls.squash_merge_pull_request=Объединить и принять PR pulls.require_signed_wont_sign=Данная ветка ожидает подписанные коммиты, однако слияние не будет подписано pulls.invalid_merge_option=Этот параметр слияния нельзя использовать для этого Pull Request'а. -pulls.merge_conflict=Слияние не удалось: Произошел конфликт во время слияния: %[1]s
%[2]s
Совет: попробуйте другую стратегию -pulls.rebase_conflict=Слияние не удалось: Произошел конфликт во время ребейза коммита: %[1]s
%[2]s
%[3]s
Совет: попробуйте другую стратегию +; %[2]s
%[3]s
pulls.unrelated_histories=Слияние не удалось: У источника и цели слияния нет общей истории. Совет: попробуйте другую стратегию -pulls.merge_out_of_date=Ошибка слияния: при создании слияния база данных была обновлена. Подсказка: попробуйте еще раз. -pulls.push_rejected=Слияние не удалось: отправка была отклонена со следующим сообщением:
%s
Просмотрите githooks для этого репозитория +pulls.merge_out_of_date=Ошибка слияния: при создании слияния база данных была обновлена. Подсказка: попробуйте ещё раз. pulls.push_rejected_no_message=Слияние не удалось: отправка была отклонена, но сообщение не было удалено.
Просмотрите githooks для этого репозитория pulls.open_unmerged_pull_exists=`Вы не можете снова открыть, поскольку уже существует запрос на слияние (#%d) из того же репозитория с той же информацией о слиянии и ожидающий слияния.` pulls.status_checking=Выполняются некоторые проверки pulls.status_checks_success=Все проверки выполнены успешно +pulls.status_checks_warning=Некоторые проверки сообщили о предупреждениях +pulls.status_checks_failure=Некоторые проверки не удались +pulls.status_checks_error=Некоторые проверки сообщили об ошибках +pulls.status_checks_requested=Требуется +pulls.status_checks_details=Информация pulls.update_branch=Обновить ветку pulls.update_branch_success=Обновление ветки выполнено успешно pulls.update_not_allowed=У вас недостаточно прав для обновления ветки pulls.outdated_with_base_branch=Эта ветка отстает от базовой ветки pulls.closed_at=`закрыл этот запрос на слияние %[2]s` +pulls.reopened_at=`переоткрыл этот запрос на слияние %[2]s` milestones.new=Новый этап -milestones.open_tab=%d открыты -milestones.close_tab=%d закрыты +milestones.open_tab=%d открыто(ы) +milestones.close_tab=%d закрыто(ы) milestones.closed=Закрыт %s +milestones.update_ago=Обновлено %s назад milestones.no_due_date=Срок не указан milestones.open=Открыть milestones.close=Закрыть @@ -1166,10 +1281,10 @@ milestones.edit=Редактировать этап milestones.edit_subheader=Используйте лучшее описание контрольной точки, во избежание непонимания со стороны других людей. milestones.cancel=Отмена milestones.modify=Обновить этап -milestones.edit_success=Этап '%s' был обновлен. +milestones.edit_success=Этап '%s' был обновлён. milestones.deletion=Удалить этап milestones.deletion_desc=Удаление этапа приведет к его удалению из всех связанных задач. Продолжить? -milestones.deletion_success=Контрольная точка успешно удалена. +milestones.deletion_success=Этап успешно удалён. milestones.filter_sort.closest_due_date=Ближайшее по дате milestones.filter_sort.furthest_due_date=Дальнее по дате milestones.filter_sort.least_complete=Менее полное @@ -1189,6 +1304,7 @@ signing.wont_sign.basesigned=Слияние не будет подписано, signing.wont_sign.headsigned=Слияние не будет подписано, так как главный коммит не подписан signing.wont_sign.commitssigned=Слияние не будет подписано, так как все связанные коммиты не подписаны signing.wont_sign.approved=Слияние не будет подписано, так как PR не одобрен +signing.wont_sign.not_signed_in=Вы не авторизовались ext_wiki=Внешняя вики ext_wiki.desc=Ссылка на внешнюю вики. @@ -1203,12 +1319,12 @@ wiki.filter_page=Фильтр страницы wiki.new_page=Страница wiki.default_commit_message=Описание изменения вики-страницы (необязательно). wiki.save_page=Сохранить страницу -wiki.last_commit_info=%s редактировал эту страницу %s +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 страницу +wiki.back_to_wiki=Вернуться на вики страницу wiki.delete_page_button=Удалить страницу wiki.delete_page_notice_1=Удаление вики-страницы '%s' не может быть отменено. Продолжить? wiki.page_already_exists=Вики-страница с таким именем уже существует. @@ -1295,14 +1411,14 @@ settings.collaboration.write=Запись settings.collaboration.read=Просмотр settings.collaboration.owner=Владелец settings.collaboration.undefined=Не определено -settings.hooks=Автоматическое обновление -settings.githooks=Git хуки +settings.hooks=Веб-хуки +settings.githooks=Git Hook'и settings.basic_settings=Основные параметры settings.mirror_settings=Настройки зеркалирования settings.sync_mirror=Синхронизировать settings.mirror_sync_in_progress=Синхронизируются репозитории-зеркала. Подождите минуту и обновите страницу. settings.email_notifications.enable=Включить почтовые уведомления -settings.email_notifications.onmention=Посылать email только при упоминании +settings.email_notifications.onmention=Посылать письмо на эл. почту только при упоминании settings.email_notifications.disable=Отключить почтовые уведомления settings.email_notifications.submit=Установить настройки электронной почты settings.site=Сайт @@ -1334,6 +1450,7 @@ settings.pulls.allow_merge_commits=Разрешить коммиты слиян settings.pulls.allow_rebase_merge=Разрешить rebase-слияние settings.pulls.allow_rebase_merge_commit=Разрешить rebase с явным коммитом слияния (--no-ff) settings.pulls.allow_squash_commits=Разрешить объединять коммиты перед слиянием (squash) +settings.projects_desc=Включить проекты репозитория settings.admin_settings=Настройки администратора settings.admin_enable_health_check=Выполнять проверки целостности этого репозитория (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Закрыть задачу с помощью коммита, сделанного в ветке не по умолчанию @@ -1344,11 +1461,29 @@ settings.convert_desc=Это зеркало можно преобразоват settings.convert_notices_1=Эта операция преобразует это зеркало в обычный репозиторий, и она не может быть отменена. settings.convert_confirm=Подтвердите преобразование settings.convert_succeed=Репозиторий успешно преобразован в обычный. +settings.convert_fork=Преобразовать в обычный репозиторий +settings.convert_fork_desc=Вы можете преобразовать этот форк в обычный репозиторий. Это не может быть отменено. +settings.convert_fork_notices_1=Эта операция преобразует этот форк в обычный репозиторий, и не может быть отменена. +settings.convert_fork_confirm=Преобразовать Репозиторий +settings.convert_fork_succeed=Форк был преобразован в обычный репозиторий. settings.transfer=Передать права собственности settings.transfer_desc=Передать репозиторий другому пользователю или организации где у вас есть права администратора. settings.transfer_notices_1=- Вы можете потерять доступ, если новый владелец является отдельным пользователем. settings.transfer_notices_2=- Вы сохраните доступ, если новым владельцем станет организация, владельцем которой вы являетесь. settings.transfer_form_title=Введите сопутствующую информацию для подтверждения операции: +settings.signing_settings=Настройки подписи верификации +settings.trust_model=Модель доверия подписи +settings.trust_model.default=Модель доверия по умолчанию +settings.trust_model.default.desc=Использовать стандартную модель доверия репозитория для этой установки. +settings.trust_model.collaborator=Соавтор +settings.trust_model.collaborator.long=Соавтор: Подписи доверия от соавторов +settings.trust_model.collaborator.desc=Допустимые подписи соавторов этого репозитория будут помечены как "доверенные" - (если они соответствуют коммиту или нет). В противном случае, правильные подписи будут помечены как "ненадёжные", если подпись соответствует коммиту и "не совпадает", если нет. +settings.trust_model.committer=Коммитер +settings.trust_model.committer.long=Коммитер: Доверять подписям, соответствующим коммитерам (Это совпадает с GitHub и заставит подписать коммиты Gitea в качестве коммитера) +settings.trust_model.committer.desc=Допустимые подписи будут помечены "доверенными" только если они соответствуют коммитеру, в противном случае они будут помечены "недоверенными". Это заставит Gitea быть коммитером подписанных коммитов вместе с фактическим коммитером, обозначенным как Co-Authored-By: и Co-Committed-By: прикреплён в этом коммите. Ключ Gitea по умолчанию должен совпадать с пользователем в базе данных. +settings.trust_model.collaboratorcommitter=Соавтор+Коммитер +settings.trust_model.collaboratorcommitter.long=Соавтор+Коммитер: Доверять подписи соавторам, которые соответствуют коммитеру +settings.trust_model.collaboratorcommitter.desc=Допустимые подписи соавторов этого репозитория будут помечены "доверенными", если они соответствуют коммиту. В противном случае, правильные подписи будут помечены как "недоверенными", если подпись соответствует коммиту и не совпадает. Это заставит Gitea быть отмеченным в качестве ответственного за подписание коммитеров с фактическим коммитером, обозначенным как Co-Authored-By: и Co-Committed-By: прикреплённым для выполнения этого коммита. По умолчанию ключ Gitea должен совпадать с пользователем в базе данных. settings.wiki_delete=Стереть данные Вики settings.wiki_delete_desc=Будьте внимательны! Как только вы удалите Вики — пути назад не будет. settings.wiki_delete_notices_1=- Это навсегда удалит и отключит Вики для %s. @@ -1359,7 +1494,7 @@ settings.delete_desc=Будьте внимательны! Как только в settings.delete_notices_1=- Эта операция НЕ МОЖЕТ быть отменена. settings.delete_notices_2=- Эта операция навсегда удалит всё из репозитория %s, включая данные Git, связанные с ним задачи, комментарии и права доступа для сотрудников. settings.delete_notices_fork_1=- Все форки станут независимыми репозиториями после удаления. -settings.deletion_success=Репозиторий удален. +settings.deletion_success=Репозиторий удалён. settings.update_settings_success=Настройки репозитория обновлены. settings.transfer_owner=Новый владелец settings.make_transfer=Выполнить передачу @@ -1384,27 +1519,27 @@ settings.add_team_success=Команда теперь имеет доступ к settings.search_team=Поиск команды… settings.change_team_permission_tip=Разрешение команды установлено на странице настройки команды и не может быть изменено для каждого репозитория settings.delete_team_tip=Эта команда имеет доступ ко всем репозиториям и не может быть удалена -settings.remove_team_success=Доступ команды к репозиторию был удален. -settings.add_webhook=Добавить Webhook -settings.add_webhook.invalid_channel_name=Имя канала не может быть пустым или состоять только из символа #. -settings.hooks_desc=Webhooks позволяют внешним службам получать уведомления при возникновении определенных событий на Gitea. При возникновении указанных событий мы отправим запрос POST на каждый заданный вами URL. Узнать больше можно в нашем руководстве по webhooks. -settings.webhook_deletion=Удалить Webhook +settings.remove_team_success=Доступ команды к репозиторию был удалён. +settings.add_webhook=Добавить Вебхук +settings.add_webhook.invalid_channel_name=Название канала вебхука не может быть пустым или состоять только из символа #. +settings.hooks_desc=Вебхуки позволяют внешним службам получать уведомления при возникновении определенных событий на Gitea. При возникновении указанных событий мы отправим запрос POST на каждый заданный вами URL. Узнать больше можно в нашем руководстве по вебхукам. +settings.webhook_deletion=Удалить вебхук settings.webhook_deletion_desc=Удаление этого веб-хука приведет к удалению всей связанной с ним информации, включая историю. Хотите продолжить? -settings.webhook_deletion_success=Webhook был удалён. +settings.webhook_deletion_success=Вебхук был удалён. settings.webhook.test_delivery=Проверить доставку settings.webhook.test_delivery_desc=Отправить тестовое событие для тестирования настройки веб-хука. -settings.webhook.test_delivery_success=Тест веб-хука была добавлен в очередь доставки. Это может занять несколько секунд, прежде чем он отобразится в истории доставки. +settings.webhook.test_delivery_success=Тест веб-хука был добавлен в очередь доставки. Это может занять несколько секунд, прежде чем он отобразится в истории доставки. settings.webhook.request=Запрос settings.webhook.response=Ответ settings.webhook.headers=Заголовки settings.webhook.payload=Содержимое settings.webhook.body=Тело ответа -settings.githooks_desc=Git-хуки предоставляются Git самим по себе, вы можете изменять файлы поддерживаемых хуков из списка ниже чтобы выполнять внешние операции. -settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведет к отключению хука. +settings.githooks_desc=Git hook'и предоставляются Git самим по себе, вы можете изменять файлы поддерживаемых hook'ов из списка ниже чтобы выполнять внешние операции. +settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука. settings.githook_name=Название Hook'a -settings.githook_content=Перехватить содержание +settings.githook_content=Содержание hook'а settings.update_githook=Обновить Hook -settings.add_webhook_desc=Gitea будет оправлять POST запросы на указанный URL адрес, с информацией о происходящих событиях. Подробности на странице инструкции по использованию webhooks. +settings.add_webhook_desc=Gitea будет оправлять POST запросы на указанный URL адрес, с информацией о происходящих событиях. Подробности на странице инструкции по использованию вебхуков. settings.payload_url=URL обработчика settings.http_method=Метод HTTP settings.content_type=Тип содержимого @@ -1414,7 +1549,7 @@ settings.slack_icon_url=URL иконки settings.discord_username=Имя пользователя settings.discord_icon_url=URL иконки settings.slack_color=Цвет -settings.event_desc=На какие события этот webhook должен срабатывать? +settings.event_desc=На какие события этот веб-хук должен срабатывать? settings.event_push_only=Просто push событие settings.event_send_everything=Все события settings.event_choose=Позвольте мне выбрать то, что нужно. @@ -1441,18 +1576,18 @@ settings.event_issue_label_desc=Метки задач обновлены или settings.event_issue_milestone=Этап задачи завершен settings.event_issue_milestone_desc=Этап или этап выполнения задания. settings.event_issue_comment=Комментарии в задаче -settings.event_issue_comment_desc=Комментарий создан, изменен или удален. +settings.event_issue_comment_desc=Комментарий создан, изменён или удалён. settings.event_header_pull_request=События запроса на слияние settings.event_pull_request=Pull Request settings.event_pull_request_desc=Запрос на слияние открыт, закрыт, переоткрыт или отредактирован. settings.event_pull_request_assign=Запроса на слияние назначен settings.event_pull_request_assign_desc=Запрос на получение назначен или не назначен. settings.event_pull_request_label=Pull Request отмечен -settings.event_pull_request_label_desc=Метки Pull Request обновлены или очищены. +settings.event_pull_request_label_desc=Метки Pull Request'а обновлены или очищены. settings.event_pull_request_milestone=Этап Pull Request завершен settings.event_pull_request_milestone_desc=Этап Pull request или промежуточный шаг. settings.event_pull_request_comment=Комментарий Pull Request -settings.event_pull_request_comment_desc=Pull request создан, отредактирован или удален. +settings.event_pull_request_comment_desc=Pull request создан, отредактирован или удалён. settings.event_pull_request_review=Pull Request рассмотрен settings.event_pull_request_review_desc=Запрос на слияние утвержден, отклонён или оставлен комментарий. settings.event_pull_request_sync=Синхронизация Pull Request @@ -1460,13 +1595,13 @@ settings.event_pull_request_sync_desc=Запрос на слияние синх settings.branch_filter=Фильтр веток settings.branch_filter_desc=Белый список ветвей для событий Push, создания ветвей и удаления ветвей, указанных в виде глобуса. Если пусто или *, сообщается о событиях для всех филиалов. Смотрите github.com/gobwas/glob документацию по синтаксису. Примеры: master, {master,release*}. settings.active=Активный -settings.active_helper=Информация о происходящих событиях будет отправляться на URL-адрес этого webhook'а. -settings.add_hook_success=Webhook был добавлен. -settings.update_webhook=Обновление Webhook -settings.update_hook_success=Webhook был обновлен. -settings.delete_webhook=Удалить Webhook +settings.active_helper=Информация о происходящих событиях будет отправляться на URL-адрес этого вебхука. +settings.add_hook_success=Вебхук был добавлен. +settings.update_webhook=Обновление вебхука +settings.update_hook_success=Вебхук был обновлён. +settings.delete_webhook=Удалить вебхук settings.recent_deliveries=Недавние рассылки -settings.hook_type=Тип перехватчика +settings.hook_type=Тип hook'а settings.add_slack_hook_desc=Добавить интеграцию с Slack в ваш репозиторий. settings.slack_token=Slack токен settings.slack_domain=Домен @@ -1474,9 +1609,9 @@ settings.slack_channel=Канал settings.add_discord_hook_desc=Добавить уведомления о событиях через Discord. settings.add_dingtalk_hook_desc=Добавить интеграцию с Dingtalk в ваш репозиторий. settings.add_telegram_hook_desc=Добавить интеграцию с Telegram в ваш репозиторий. -settings.add_matrix_hook_desc=Интеграция Matrix в ваш репозиторий. +settings.add_matrix_hook_desc=Добавить интеграцию Matrix в ваш репозиторий. settings.add_msteams_hook_desc=Добавить интеграцию с Microsoft Teams в ваш репозиторий. -settings.add_feishu_hook_desc=Интегрировать Feishu в ваш репозиторий. +settings.add_feishu_hook_desc=Добавить интеграцию Feishu в ваш репозиторий. settings.deploy_keys=Ключи развертывания settings.add_deploy_key=Добавить ключ развертывания settings.deploy_key_desc=Ключи развёртывания доступны только для чтения. Это не то же самое что и SSH-ключи аккаунта. @@ -1498,13 +1633,14 @@ settings.protected_branch_can_push_yes=Вы можете выполнять push settings.protected_branch_can_push_no=Вы не можете выполнять push settings.branch_protection=Защита ветки %s settings.protect_this_branch=Защитить эту ветку -settings.protect_this_branch_desc=Предотвращает удаление, ограничивает толкание и слияние Git в ветку. +settings.protect_this_branch_desc=Предотвращает удаление, ограничивает Push и слияние Git в ветку. settings.protect_disable_push=Отключить Push settings.protect_disable_push_desc=Отправка не будет разрешена в эту ветку. settings.protect_enable_push=Включить Push settings.protect_enable_push_desc=Любой, у кого есть доступ на запись, будет разрешен push в эту ветку (но не принудительно push). settings.protect_whitelist_committers=Ограниченный белый список Push settings.protect_whitelist_committers_desc=Только пользователям или командам из белого списка будут разрешены push в эту ветку (но не принудительно push). +settings.protect_whitelist_deploy_keys=Белый список развёртываемых ключей с доступом на запись в push. settings.protect_whitelist_users=Пользователи, которые могут делать push в эту ветку: settings.protect_whitelist_search_users=Поиск пользователей… settings.protect_whitelist_teams=Команды, члены которых могут делать push в эту ветку: @@ -1514,6 +1650,7 @@ settings.protect_merge_whitelist_committers_desc=Вы можете добавл settings.protect_merge_whitelist_users=Пользователи с правом на принятие Pull Request'ов в эту ветку: settings.protect_merge_whitelist_teams=Команды, члены которых обладают правом на принятие Pull Request'ов в эту ветку: 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=Разрешить объединение Pull Request'а только с достаточным количеством положительных отзывов. @@ -1524,6 +1661,9 @@ settings.protect_approvals_whitelist_teams=Команды в белом спис settings.dismiss_stale_approvals=Отклонить устаревшие разрешения settings.dismiss_stale_approvals_desc=Когда новые коммиты, изменяющие содержимое Pull Request'а, отправляются в ветку, старые разрешения будут отклонены. settings.require_signed_commits=Требовать подписанные коммиты +settings.require_signed_commits_desc=Отклонить push'ы в эту ветку, если они не подписаны или не проверены. +settings.protect_protected_file_patterns=Защищённые шаблоны файлов (разделённые через '\;'): +settings.protect_protected_file_patterns_desc=Защищенные файлы, которые не могут быть изменены напрямую, даже если пользователь имеет право добавлять, редактировать или удалять файлы в этой ветке. Несколько шаблонов могут быть разделены точкой с запятой ('\;'). Смотрите github.com/gobwas/glob документацию для синтаксиса шаблонов. Например: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Включить защиту settings.delete_protected_branch=Отключить защиту settings.update_protect_branch_success=Настройки защиты ветки '%s' были успешно изменены. @@ -1533,6 +1673,7 @@ settings.protected_branch_deletion_desc=Любой пользователь с settings.block_rejected_reviews=Блокировка слияния по отклоненным отзывам settings.block_rejected_reviews_desc=Слияние будет не возможным, если будут запрошены официальными рецензентами изменения, даже если имеется достаточное количество одобрений. settings.block_outdated_branch=Блокировать слияние, если pull request устарел +settings.block_outdated_branch_desc=Слияние будет невозможно, если головная ветвь находится позади базовой ветви. settings.default_branch_desc=Главная ветка является "базовой" для вашего репозитория, на которую по умолчанию направлены все Pull Request'ы и которая является лицом вашего репозитория. Первое, что увидит посетитель — это содержимое главной ветки. Выберите её из уже существующих: settings.choose_branch=Выберите ветку… settings.no_protected_branch=Нет защищённых веток. @@ -1546,7 +1687,7 @@ settings.matrix.access_token=Токен доступа settings.matrix.message_type=Тип сообщения settings.archive.button=Архивировать репозиторий settings.archive.header=Архивировать этот репозиторий -settings.archive.text=Архивация репозитория переведет его в режим read-only. Он будет скрыт из панели управления, создание задач, запросов на слияние, или создание коммитов будут запрещены. +settings.archive.text=Архивация репозитория переведёт его в режим read-only. Он будет скрыт из панели управления, создания задач, pull-request'ов на слияние, или создание коммитов будут запрещены. settings.archive.success=Репозиторий был успешно архивирован. settings.archive.error=Ошибка при попытке архивировать репозиторий. Смотрите логи для получения подробностей. settings.archive.error_ismirror=Вы не можете поместить зеркалируемый репозиторий в архив. @@ -1556,7 +1697,7 @@ settings.unarchive.header=Разархивировать этот репозит settings.unarchive.text=Разархивация восстанавливает возможность совершать push в репозиторий, создавать новые коммиты, задачи и запросы на слияние. settings.unarchive.success=Репозиторий был успешно разархивирован. settings.unarchive.error=Ошибка при попытке разархивировать репозиторий. Смотрите логи для получения подробностей. -settings.update_avatar_success=Аватар репозитория обновлен. +settings.update_avatar_success=Аватар репозитория обновлён. settings.lfs=LFS settings.lfs_filelist=Файлы LFS хранятся в этом репозитории settings.lfs_no_lfs_files=Нет файлов LFS в этом репозитории @@ -1599,7 +1740,7 @@ diff.whitespace_show_everything=Показать все изменения diff.whitespace_ignore_all_whitespace=Игнорировать пробелы при сравнении строк diff.whitespace_ignore_amount_changes=Игнорировать изменения количества пробелов diff.whitespace_ignore_at_eol=Игнорировать изменения в пробельных символах на концах строк -diff.stats_desc= %d измененных файлов: %d добавлений и %d удалений +diff.stats_desc= %d изменённых файлов: %d добавлений и %d удалений diff.bin=Двоичные данные diff.view_file=Просмотреть файл diff.file_before=До @@ -1621,6 +1762,7 @@ diff.review.placeholder=Рецензионный комментарий diff.review.comment=Комментировать diff.review.approve=Утвердить diff.review.reject=Запрос изменений +diff.committed_by=коммит произвёл releases.desc=Релизы позволяют организовать хранение готовых сборок проекта в строгом хронологически верном порядке. release.releases=Релизы @@ -1629,6 +1771,8 @@ release.draft=Черновик release.prerelease=Пре-релиз release.stable=Стабильный release.edit=Редактировать +release.ahead.commits=%d коммиты +release.ahead.target=%s с этого релиза release.source_code=Исходный код release.new_subheader=Публикация релизов поможет хранить чёткую историю развития вашего проекта. release.edit_subheader=Подробный журнал изменений может помочь пользователям понять, что было изменено в очередной версии. @@ -1645,7 +1789,7 @@ release.save_draft=Сохранить черновик release.edit_release=Редактировать релиз release.delete_release=Удалить этот релиз release.deletion=Удаление релиза -release.deletion_desc=Удаление релиза удаляет Git-тэг из репозитория. Содержимое хранилища и история останутся неизменными. Продолжить? +release.deletion_desc=Удаление релиза удаляет Git-тег из репозитория. Содержимое хранилища и история останутся неизменными. Продолжить? release.deletion_success=Релиз был удалён. release.tag_name_already_exist=Релиз с этим именем метки уже существует. release.tag_name_invalid=Имя тега является не допустимым. @@ -1672,6 +1816,7 @@ branch.deleted_by=Удалён %s branch.restore_success=Ветка '%s' восстановлена. branch.restore_failed=Не удалось восстановить ветку '%s'. branch.protected_deletion_failed=Ветка '%s' защищена. Её нельзя удалить. +branch.default_deletion_failed=Ветка '%s' является веткой по умолчанию. Она не может быть удалена. branch.restore=Восстановить ветку '%s' branch.download=Скачать ветку '%s' branch.included_desc=Эта ветка является частью ветки по умолчанию @@ -1718,19 +1863,21 @@ settings.repoadminchangeteam=Администратор репозитория settings.visibility=Видимость settings.visibility.public=Публичный settings.visibility.limited=Ограничено (Видно только для авторизованных пользователей) +settings.visibility.limited_shortname=Ограничить settings.visibility.private=Частный (Видимый только для участников организации) +settings.visibility.private_shortname=Приватизировать settings.update_settings=Обновить настройки settings.update_setting_success=Настройки организации обновлены. settings.change_orgname_prompt=Это изменение изменит ссылки на организацию. -settings.update_avatar_success=Аватар организации обновлен. +settings.update_avatar_success=Аватар организации обновлён. settings.delete=Удалить организацию settings.delete_account=Удалить эту организацию settings.delete_prompt=Это действие БЕЗВОЗВРАТНО удалит эту организацию навсегда. settings.confirm_delete_account=Подтвердить удаление settings.delete_org_title=Удалить организацию settings.delete_org_desc=Эта организация будет безвозвратно удалена. Продолжить? -settings.hooks_desc=Добавьте webhooks, который будет вызываться для всех репозиториев под этой организации. +settings.hooks_desc=Добавьте вебхуки, которые будет вызываться для всех репозиториев под этой организации. settings.labels_desc=Добавьте метки, которые могут быть использованы в задачах для всех репозиториев этой организации. @@ -1797,7 +1944,7 @@ repositories=Репозитории hooks=Стандартные Веб-хуки systemhooks=Системные вебхуки authentication=Авторизация -emails=Email пользователей +emails=Адреса эл. почты пользователей config=Конфигурация notices=Системные уведомления monitor=Мониторинг @@ -1808,17 +1955,40 @@ total=Всего: %d dashboard.statistic=Статистика dashboard.operations=Операции dashboard.system_status=Статус системного монитора -dashboard.statistic_info=В базе данных Gitea записано %d пользователей, %d организаций, %d публичных ключей, %d репозиториев, %d подписок на репозитории, %d добавлений в избранное, %d действий, %d доступов, %d задач, %d комментариев, %d социальных учетных записей, %d подписок на пользователей, %d зеркал, %d релизов, %d источников входа, %d веб-хуков, %d этапов, %d меток, %d задач хуков, %d команд, %d задач по обновлению, %d присоединенных файлов. +dashboard.statistic_info=В базе данных Gitea записано %d пользователей, %d организаций, %d публичных ключей, %d репозиториев, %d подписок на репозитории, %d добавлений в избранное, %d действий, %d доступов, %d задач, %d комментариев, %d социальных учетных записей, %d подписок на пользователей, %d зеркал, %d релизов, %d источников входа, %d вебхуков, %d этапов, %d меток, %d задач hook'ов, %d команд, %d задач по обновлению, %d присоединённых файлов. dashboard.operation_name=Имя операции dashboard.operation_switch=Переключить dashboard.operation_run=Запуск dashboard.clean_unbind_oauth=Очистить список незавершённых авторизаций OAuth dashboard.clean_unbind_oauth_success=Все незавершённые связи OAuth были удалены. +dashboard.task.started=Началось задание: %[1]s +dashboard.task.process=Задача: %[1]s +dashboard.task.cancelled=Задача: %[1]s отменена: %[3]s +dashboard.task.error=Ошибка в Задаче: %[1]s: %[3]s +dashboard.task.finished=Задача: %[1]s, начатая %[2]s завершена +dashboard.task.unknown=Неизвестная задача: %[1]s +dashboard.cron.started=Стартовал Cron: %[1]s +dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Задача Cron: %s отменена: %[3]s +dashboard.cron.error=Ошибка в задаче Cron: %s: %[3]s +dashboard.cron.finished=Крон: %[1]s завершено +dashboard.delete_inactive_accounts=Удалить все неактивированные учётные записи +dashboard.delete_inactive_accounts.started=Удалить все запущенные задачи неактивированных аккаунтов. dashboard.delete_repo_archives=Удаление всех архивов репозиториев +dashboard.delete_repo_archives.started=Удаление всех архивов репозитория началось. dashboard.delete_missing_repos=Удалить все записи о репозиториях с отсутствующими файлами Git +dashboard.delete_missing_repos.started=Задача по удалению всех репозиториев, в которых отсутствуют их git-файлы, начата. dashboard.delete_generated_repository_avatars=Удалить генерированные аватары репозитория +dashboard.update_mirrors=Обновить зеркала +dashboard.repo_health_check=Проверка состояния всех репозиториев +dashboard.check_repo_stats=Проверить всю статистику репозитория +dashboard.archive_cleanup=Удалить старые архивы репозитория +dashboard.deleted_branches_cleanup=Очистка удалённых ветвей +dashboard.update_migration_poster_id=Обновить ID плакатов миграции dashboard.git_gc_repos=Выполнить сборку мусора для всех репозиториев -dashboard.resync_all_hooks=Повторная синхронизация хуков pre-receive, update и post-receive во всех репозиториях. +dashboard.resync_all_sshkeys=Обновить файл '.ssh/authorized_keys' с SSH ключами Gitea. +dashboard.resync_all_sshkeys.desc=(Не требуется для встроенного SSH сервера.) +dashboard.resync_all_hooks=Повторная синхронизация hook'ов pre-receive, update и post-receive во всех репозиториях. dashboard.reinit_missing_repos=Переинициализировать все отсутствующие Git репозитории, для которых существуют записи dashboard.sync_external_users=Синхронизировать данные внешних пользователей dashboard.server_uptime=Время непрерывной работы сервера @@ -1858,6 +2028,7 @@ users.full_name=Полное имя users.activated=Активирован users.admin=Администратор users.restricted=Ограничено +users.2fa=Двухфакторная авторизация users.repos=Репозитории users.created=Создано users.last_login=Последний вход @@ -1869,32 +2040,31 @@ users.auth_source=Источник аутентификации users.local=Локальный users.auth_login_name=Логин для авторизации users.password_helper=Оставьте пустым, чтобы оставить без изменений. -users.update_profile_success=Профиль учетной записи обновлен успешно. +users.update_profile_success=Профиль учётной записи обновлён успешно. users.edit_account=Изменение учетной записи users.max_repo_creation=Максимальное количество репозиториев users.max_repo_creation_desc=(Установите -1 для использования стандартного глобального значения предела) users.is_activated=Эта учетная запись активирована -users.prohibit_login=Этой учетной записи запрещен вход в систему +users.prohibit_login=Этой учетной записи запрещён вход в систему users.is_admin=У этой учетной записи есть права администратора users.is_restricted=Ограничен -users.allow_git_hook=Эта учетная запись имеет разрешение на создание Git-хуков -users.allow_git_hook_tooltip=Git Hooks выполняются от пользователь Gitea операционной системы и будет иметь одинаковый уровень доступа к хосту +users.allow_git_hook=Эта учётная запись имеет разрешение на создание Git hook'ов users.allow_import_local=Пользователь имеет право импортировать локальные репозитории users.allow_create_organization=Эта учетная запись имеет разрешения на создание организаций users.update_profile=Обновить профиль учетной записи users.delete_account=Удалить эту учетную запись users.still_own_repo=На вашем аккаунте все еще остается один или более репозиториев, сначала вам нужно удалить или передать их. users.still_has_org=Эта учетная запись все еще является членом одной или более организаций. Для продолжения, покиньте или удалите организации. -users.deletion_success=Учетная запись успешно удалена. +users.deletion_success=Учётная запись успешно удалена. -emails.email_manage_panel=Управление Email пользователя +emails.email_manage_panel=Управление эл. почтой пользователя emails.primary=Первичный emails.activated=Активирован emails.filter_sort.email=Эл. почта -emails.filter_sort.email_reverse=Email (обратный) +emails.filter_sort.email_reverse=Эл. почта (обратный) emails.filter_sort.name=Имя пользователя emails.filter_sort.name_reverse=Имя пользователя (обратное) -emails.updated=Email обновлен +emails.updated=Email обновлён emails.not_updated=Не удалось обновить запрошенный адрес электронной почты: %v emails.duplicate_active=Этот адрес электронной почты уже активирован для другого пользователя. emails.change_email_header=Обновить свойства электронной почты @@ -1907,6 +2077,8 @@ orgs.members=Участники orgs.new_orga=Новая организация repos.repo_manage_panel=Управление репозиториями +repos.unadopted=Непринятые репозитории +repos.unadopted.no_more=Больше непринятых репозиториев не найдено repos.owner=Владелец repos.name=Название repos.private=Личный @@ -1916,11 +2088,11 @@ repos.forks=Форки repos.issues=Задачи repos.size=Размер -hooks.desc=Webhooks автоматически делает HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Webhooks, определенные здесь, по умолчанию и будут скопированы во все новые репозитории. Подробнее читайте в руководстве по webhooks. +hooks.desc=Вебхуки автоматически делают HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Вебхуки, определённые здесь, по умолчанию и будут скопированы во все новые репозитории. Подробнее читайте в руководстве по вебхукам. hooks.add_webhook=Добавить стандартный Веб-хук hooks.update_webhook=Обновить стандартный Веб-хук -systemhooks.desc=Webhooks автоматически делает HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Определенные вебхуки будут действовать на всех репозиториях системы, поэтому пожалуйста, учитывайте любые последствия для производительности. Подробнее читайте в руководстве по вебхукам. +systemhooks.desc=Вебхуки автоматически делают HTTP-POST запросы на сервер, когда вызываются определённые события Gitea. Определённые вебхуки будут действовать на всех репозиториях системы, поэтому пожалуйста, учитывайте любые последствия для производительности. Подробнее читайте в руководстве по вебхукам. systemhooks.add_webhook=Добавить системный вебхук systemhooks.update_webhook=Обновить системный вебхук @@ -1946,7 +2118,7 @@ auths.attribute_username=Атрибут Username auths.attribute_username_placeholder=Оставьте пустым, чтобы использовать имя пользователя для регистрации. auths.attribute_name=Атрибут First Name auths.attribute_surname=Атрибут Surname -auths.attribute_mail=Атрибут Email +auths.attribute_mail=Атрибут электронной почты auths.attribute_ssh_public_key=Атрибут Открытый SSH ключ auths.attributes_in_bind=Извлекать атрибуты в контексте Bind DN auths.allow_deactivate_all=Разрешить пустой результат поиска для отключения всех пользователей @@ -1956,6 +2128,11 @@ auths.filter=Фильтр пользователя auths.admin_filter=Фильтр администратора auths.restricted_filter=Ограниченный фильтр auths.restricted_filter_helper=Оставьте пустым, чтобы не устанавливать никаких пользователей как ограниченные. Используйте звездочку ('*'), чтобы установить всех пользователей, не соответствующих фильтру администратора. +auths.verify_group_membership=Проверять членство в группе в LDAP +auths.group_search_base=Поисковая база групп DN +auths.valid_groups_filter=Допустимый фильтр групп +auths.group_attribute_list_users=Атрибут группы, содержащий список пользователей +auths.user_attribute_in_group=Атрибут пользователя в группе auths.ms_ad_sa=Атрибуты поиска MS AD auths.smtp_auth=Тип аутентификации SMTP auths.smtphost=Узел SMTP @@ -1976,7 +2153,7 @@ 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_create_users_helper=Разрешить метод аутентификации SSPI для автоматического создания новых учётных записей для пользователей, которые впервые входят в систему auths.sspi_auto_activate_users=Автоматически активировать пользователей auths.sspi_auto_activate_users_helper=Разрешить метод аутентификации SSPI для автоматической активации новых пользователей auths.sspi_strip_domain_names=Удалять доменные имена из имён пользователей @@ -2000,7 +2177,7 @@ auths.tip.openid_connect=Используйте OpenID Connect Discovery URL (%[2]s
на %[3]s из зеркала approve_pull_request=`утвердил(а) %s#%[2]s` reject_pull_request=`предложил(а) изменения для %s#%[2]s` +publish_release=`выпущено "%[4]s" в %[3]s` [tool] ago=%s назад @@ -2296,10 +2477,12 @@ mark_all_as_read=Пометить все как прочитанные default_key=Подписано ключом по умолчанию error.extract_sign=Не удалось извлечь подпись error.generate_hash=Не удается создать хэш коммита -error.no_committer_account=Аккаунт пользователя с таким Email не найден +error.no_committer_account=Аккаунт пользователя с такой электронной почтой не найден error.no_gpg_keys_found=Не найден GPG ключ соответствующий данной подписи error.not_signed_commit=Неподписанный коммит error.failed_retrieval_gpg_keys=Не удалось получить соответствующий GPG ключ пользователя +error.probable_bad_signature=ПРЕДУПРЕЖДЕНИЕ! Хотя в базе данных есть ключ с этим идентификатором! Это коммит SUSPICIOUS (подозрительный). +error.probable_bad_default_signature=ПРЕДУПРЕЖДЕНИЕ! Хотя ключ по умолчанию имеет этот идентификатор, он не проверяет это коммит! Это коммит SUSPICIOUS (подозрительный). [units] error.no_unit_allowed_repo=У вас нет доступа ни к одному разделу этого репозитория. diff --git a/options/locale/locale_sr-SP.ini b/options/locale/locale_sr-SP.ini index dda7eb7923f8..d75177b19e12 100644 --- a/options/locale/locale_sr-SP.ini +++ b/options/locale/locale_sr-SP.ini @@ -173,7 +173,6 @@ key_content=Садржај add_on=Додато last_used=Задње корршћено no_activity=Нема недавних активности - manage_social=Управљање прикључених друштвеним мрежама generate_new_token=Генериши нови токен @@ -211,8 +210,6 @@ forks=Огранци -migrate_type=Тип миграције -migrate_type_helper=Ово спремиште ће бити огледало migrate_repo=Мигрирајте спремиште migrate.permission_denied=Немате права на увезете локално спремиште. migrate.failed=Миграција није успела: %v @@ -260,7 +257,6 @@ editor.create_new_branch=Креирај нову грану з editor.cancel=Откажи editor.branch_already_exists=Грана '%s' већ постоји за ово спремиште. editor.no_changes_to_show=Нема никаквих промена. -editor.fail_to_update_file=Промена над '%s' није успело са грешком: %v editor.unable_to_upload_files=Учитање датотеке '%s' није успело са грешкном: %v editor.upload_files_to_dir=Пошаљи датотеке на '%s' @@ -347,6 +343,7 @@ pulls.merged=Спојено pulls.can_auto_merge_desc=Овај захтев за спајање може бити обављен аутоматски. pulls.merge_pull_request=Обави спајање +; %[2]s
%[3]s
milestones.new=Нова фаза milestones.open_tab=%d отворено @@ -639,7 +636,6 @@ config.session_config=Подешавања сесије config.session_provider=Добављач сесија config.provider_config=Конфигурација на добављачу config.cookie_name=Име датотеке cookie -config.enable_set_cookie=Укључи поставку cookie config.gc_interval_time=Интервал cакупљања смећа config.session_life_time=Дужина живота сесјие config.https_only=Само HTTPS diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 555a4f9d9e0d..5da2b32c547d 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -20,10 +20,13 @@ user_profile_and_more=Profil och Inställningar… signed_in_as=Inloggad som enable_javascript=Denna sida fungerar bättre med Javascript igång. toc=Innehållsförteckning +licenses=Licenser +return_to_gitea=Återgå till Gitea username=Användarnamn email=E-postadress password=Lösenord +access_token=Åtkomsttoken re_type=Upprepa lösenordet captcha=CAPTCHA twofa=Tvåfaktorsautentisering @@ -51,6 +54,8 @@ new_migrate=Ny migrering new_mirror=Ny Spegling new_fork=Ny förgrening av utvecklingskatalog new_org=Ny organisation +new_project=Nytt projekt +new_project_board=Ny projekttavla manage_org=Hantera organisationer admin_panel=Sidadministration account_settings=Kontoinställningar @@ -71,6 +76,7 @@ issues=Problem milestones=Milstolpar cancel=Avbryt +save=Spara add=Lägg till add_all=Lägg till alla remove=Ta bort @@ -166,6 +172,7 @@ openid_signin=Aktivera OpenID-inloggning openid_signin_popup=Aktivera användarinloggning via OpenID. openid_signup=Aktivera självregistrering genom OpenID openid_signup_popup=Aktivera OpenID-baserad självregistrering av användare. +enable_captcha=Aktivera CAPTCHA registrering enable_captcha_popup=Kräv captcha för användarregistrering. require_sign_in_view=Kräv Inloggning För Att Visa Sidor require_sign_in_view_popup=Begränsa åtkomst till inloggande användare. Besökare kommer bara kunna se inloggnings- och registreringssidorna. @@ -205,8 +212,17 @@ my_orgs=Mina organisationer my_mirrors=Mina speglar view_home=Visa %s search_repos=Hitta en utvecklingskatalog… +filter=Övriga Filter +show_archived=Arkiverade +show_both_archived_unarchived=Visar både arkiverade och icke arkiverade +show_only_archived=Visar endast arkiverade +show_only_unarchived=Visa endast icke arkiverade +show_private=Privat +show_both_private_public=Visar både offentliga och privata +show_only_private=Visar endast privata +show_only_public=Visar endast publika issues.in_your_repos=I dina utvecklingskataloger @@ -283,6 +299,8 @@ authorize_title=Ge "%s" tillgång till ditt konto? authorization_failed=Auktorisering misslyckades authorization_failed_desc=Auktoriseringen misslyckades eftersom vi upptäckte en ogiltig begäran. Vänligen kontakta den som är ansvarige för appen som du har försökt auktorisera. sspi_auth_failed=SSPI-autentisering misslyckades +password_pwned=Lösenordet du valde finns på en lista över stulna lösenord som tidigare har exponerats i offentliga dataintrång. Försök igen med ett annat lösenord. +password_pwned_err=Kunde inte slutföra begäran till HaveIBeenPwned [mail] activate_account=Vänligen aktivera ditt konto @@ -337,6 +355,10 @@ lang_select_error=Välj ett språk från listan. username_been_taken=Användarnamnet är redan taget. repo_name_been_taken=Namnet för utvecklingskatalogen är upptaget. +repository_files_already_exist=Filer finns redan för denna utvecklingskatalog. Kontakta systemadministratören. +repository_files_already_exist.adopt=Filer finns redan för denna utvecklingskatalog och kan bara antas. +repository_files_already_exist.delete=Filer finns redan för denna utvecklingskatalog. Du måste ta bort dem. +repository_files_already_exist.adopt_or_delete=Filer finns redan för denna utvecklingskatalog. Antingen anta dem eller ta bort dem. visit_rate_limit=För många förfrågningar på för kort tid till fjärrvärden. 2fa_auth_required=Fjärrbesök kräver tvåfaktorsautentisering. org_name_been_taken=Organisationsnamnet är redan taget. @@ -355,7 +377,7 @@ enterred_invalid_owner_name=Det nya namnet på ägaren är ogiltligt. enterred_invalid_password=Det angivna lösenordet är felaktigt. user_not_exist=Användaren finns inte. team_not_exist=Teamet finns inte. -last_org_owner=Du kan inte ta bort den sista användaren från 'ägare'-teamet. Varje organisation måste ha åtminstone en ägare. +last_org_owner=Du kan inte ta bort den sista användaren från 'owners' teamet. Det måste finnas minst en ägare för en organisation. cannot_add_org_to_team=En organisation kan inte läggas till som teammedlem. invalid_ssh_key=Kunde inte verifiera din SSH-nyckel: %s @@ -376,11 +398,13 @@ repositories=Utvecklingskataloger activity=Offentlig Aktivitet followers=Följare starred=Stjärnmärkta Utvecklingskataloger +projects=Projekt following=Följer follow=Följ unfollow=Sluta följa heatmap.loading=Laddar färgdiagram… user_bio=Biografi +disabled_public_activity=Den här användaren har inaktiverat den publika synligheten av aktiviteten. form.name_reserved=Användarnamnet '%s' är reserverat. form.name_pattern_not_allowed=Mönstret '%s' är otillåtet i ett användarnamn. @@ -405,6 +429,7 @@ uid=AnvändarID u2f=Säkerhetsnycklar public_profile=Offentlig profil +biography_placeholder=Berätta lite om dig själv profile_desc=Din mejladress kommer användas för notifikationer och andra åtgärder. password_username_disabled=Externa användare kan inte ändra sitt användarnamn. Kontakta din webbadministratör för mera information. full_name=Fullständigt namn @@ -419,6 +444,9 @@ continue=Fortsätt cancel=Avbryt language=Språk ui=Tema +privacy=Sekretess +keep_activity_private=Dölj aktiviteten från profilsidan +keep_activity_private_popup=Gör aktiviteten endast synlig för dig och administratörerna lookup_avatar_by_mail=Slå upp avatarer med hjälp utav mejladress federated_avatar_lookup=Förenad uppslagning av avatar @@ -480,8 +508,9 @@ ssh_helper=Behöver du hjälp? Kolla in Github's guide för att gpg_helper=Behöver du hjälp? Ta en titt på Github's guide om GPG. add_new_key=Lägg till SSH-nyckel add_new_gpg_key=Lägg till GPG-nyckel +key_content_ssh_placeholder=Börjar med 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', eller 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Börjar med '-----BEGIN PGP PUBLIC KEY BLOCK-----' ssh_key_been_used=Denna SSH-nyckel har redan lagts till på servern. -ssh_key_name_used=En SSH-nyckel med samma namn är redan associerad med ditt konto. gpg_key_id_used=En publik GPG-nyckel med samma ID existerar redan. gpg_no_key_email_found=Denna GPG-nyckel är inte brukbar med någon utav mejladresserna associerade med ditt konto. subkeys=Undernycklar @@ -509,7 +538,6 @@ token_state_desc=Denna token har används inom dom senaste 7 dagarna show_openid=Synlig på min profil hide_openid=Dold från min profil ssh_disabled=SSH är inaktiverat - manage_social=Hantera länkade sociala konton social_desc=Dessa sociala konton är länkade till ditt Gitea konto. Var god kontrollera att du känns vid samtliga då de kan användas för att logga in på ditt Gitea konto. unbind=Koppla från @@ -522,6 +550,7 @@ new_token_desc=Applikationer som använder en token har full åtkomst till ditt token_name=Tokennamn generate_token=Generera Token generate_token_success=Din nya token har genererats. Kopiera nu då den inte kommer visas igen. +generate_token_name_duplicate=%s finns redan som programnamn. Välj ett annat. delete_token=Radera access_token_deletion=Ta bort åtkomst token access_token_deletion_desc=Borttagning utav en token kommer återkalla åtkomst till ditt konto för de applikationer som använder den. Vill du fortsätta? @@ -654,7 +683,18 @@ pick_reaction=Välj din reaktion reactions_more=och %d flera unit_disabled=Webbplatsens administratör har inaktiverat denna utvecklingskatalog. language_other=Övrigt - +adopt_preexisting_content=Skapa utvecklingskatalog från %s +delete_preexisting_label=Radera +delete_preexisting=Ta bort befintliga filer +delete_preexisting_content=Ta bort filer i %s + +desc.private=Privat +desc.public=Publik +desc.private_template=Privat mall +desc.public_template=Mall +desc.internal=Intern +desc.internal_template=Intern mall +desc.archived=Arkiverade template.items=Mallobjekt template.git_content=Git innehåll (Default branch) @@ -676,14 +716,17 @@ form.name_reserved=Utvecklingskatalogsnamnet '%s' är reserverat. form.name_pattern_not_allowed=Mönstret '%s' är otillåtet i ett utvecklingskatalogsnamn. need_auth=Klona Auktorisering -migrate_type=Migreringstyp -migrate_type_helper=Detta repo kommer att vara en spegling +migrate_options=Migrationsalternativ +migrate_service=Migreringstjänst +migrate_options_mirror_helper=Denna utvecklingskatalog kommer att vara en spegel +migrate_options_mirror_disabled=Din webbplatsadministratör har inaktiverat nya speglar. migrate_items=Migrationsobjekt migrate_items_wiki=Wiki migrate_items_milestones=Milstenar migrate_items_labels=Etiketter migrate_items_issues=Ärenden migrate_items_pullrequests=Pull Requester +migrate_items_merge_requests=Begäran om sammanslagning migrate_items_releases=Releaser migrate_repo=Migrera Repot migrate.clone_address=Migrera Eller Klona Från URL @@ -693,17 +736,23 @@ migrate.permission_denied=Du får inte importera lokala repon. migrate.invalid_local_path=Ogiltig lokal sökväg. Den finns inte, eller är inte en katalog. migrate.failed=Migrering misslyckades: %v migrate.lfs_mirror_unsupported=Spegling av LFS-objekt stöds ej. Använd 'git lfs fetch --all' och 'git lfs push -all' istället. -migrate.migrate_items_options=För att migrera från github, skriv in användarnamn så kommer val för migrering att visas. +migrate.migrate_items_options=Åtkomsttoken krävs för att migrera ytterligare objekt migrated_from=Migrerad från %[2]s migrated_from_fake=Migrerad från %[1]s +migrate.migrate=Migrera från %s migrate.migrating=Migrerar från %s ... migrate.migrating_failed=Migrering från %s misslyckades. +migrate.github.description=Migrera data från Github.com eller Github Enterprise. +migrate.git.description=Migrera eller spegla git-data från Git-tjänster +migrate.gitlab.description=Migrera data från GitLab.com eller fristående gitlab-server. mirror_from=spegling av forked_from=forkad från generated_from=skapad från fork_from_self=Du kan inte forka din egna utvecklingskatalog. fork_guest_user=Logga in för att grena detta förråd. +watch_guest_user=Logga in för att bevaka denna utvecklingskatalog. +star_guest_user=Logga in för att stjärnmarkera denna utvecklingskatalog. copy_link=Kopiera copy_link_success=Länken har kopierats copy_link_error=Tryck på ⌘C eller Ctrl-C för att kopiera @@ -726,11 +775,13 @@ code=Kod code.desc=Se källkod, filer, commits och brancher. branch=Gren tree=Träd +clear_ref=`Rensa aktuell referens` filter_branch_and_tag=Filtrera gren eller tagg branches=Grenar tags=Taggar issues=Ärenden pulls=Pull-förfrågningar +project_board=Projekt labels=Etiketter org_labels_desc=Etiketter på organisationsnivå som kan användas i alla utvecklingskataloger tillhörande denna organisation org_labels_desc_manage=hantera @@ -749,6 +800,8 @@ audio_not_supported_in_browser=Din webbläsare stöder inte taggen 'audio' i HTM stored_lfs=Sparad med Git LFS symbolic_link=Symbolisk länk commit_graph=Commit-Graf +commit_graph.monochrome=Mono +commit_graph.color=Färg blame=Blame normal_view=Normal vy line=rad @@ -798,9 +851,7 @@ editor.file_already_exists=En fil vid namn '%s' finns redan i denna utvecklingsk editor.commit_empty_file_header=Committa en tom fil editor.commit_empty_file_text=Filen du vill committa är tom. Vill du fortsätta? editor.no_changes_to_show=Det finns inga ändringar att visa. -editor.fail_to_update_file=Uppdateringen/skapandet av filen '%s' misslyckades med felet: %v editor.push_rejected_no_message=Ändringarna avvisades av servern utan något meddelande. Kontrollera githookarna. -editor.push_rejected=Ändringarna avvisades av servern med följande meddelande:
%s
Kontrollera githookarna. editor.add_subdir=Lägga till en katalog… editor.unable_to_upload_files=Uppladdning av filen '%s' misslyckades med felet: %v editor.upload_file_is_locked=Filen '%s' är låst av %s. @@ -830,10 +881,26 @@ commits.gpg_key_id=GPG-nyckel ID ext_issues=Externa ärenden ext_issues.desc=Länk till externt ärendehanteringssystem. +projects=Projekt +projects.create=Skapa projekt +projects.title=Titel +projects.new=Nytt projekt +projects.create_success=Projektet '%s' har skapats. +projects.deletion=Ta bort projekt +projects.deletion_success=Projektet har tagits bort. +projects.edit=Redigera projekt +projects.modify=Uppdatera projekt +projects.edit_success=Projektet '%s' har uppdaterats. +projects.template.desc=Projektmall +projects.type.uncategorized=Okatergoriserad +projects.board.new_submit=Skicka +projects.open=Öppna +projects.close=Stäng issues.desc=Organisera felrapporteringar, ärenden och milstolpar. issues.filter_assignees=Filtrera tilldelad person issues.filter_milestones=Filtrera milstolpe +issues.filter_projects=Filtrera projekt issues.filter_labels=Filtrera etikett issues.filter_reviewers=Filtrera granskare issues.new=Nytt Ärende @@ -842,6 +909,12 @@ issues.new.labels=Etiketter issues.new.add_labels_title=Tillämpa etiketter issues.new.no_label=Ingen Etikett issues.new.clear_labels=Rensa etiketter +issues.new.projects=Projekt +issues.new.add_project_title=Ange projekt +issues.new.clear_projects=Rensa projekt +issues.new.no_projects=Inget projekt +issues.new.open_projects=Öppna projekt +issues.new.closed_projects=Stängda projekt issues.new.no_items=Inga objekt issues.new.milestone=Milsten issues.new.add_milestone_title=Ange milstolpe @@ -855,6 +928,9 @@ issues.new.clear_assignees=Rensa tilldelade issues.new.no_assignees=Ingen tilldelad issues.new.no_reviewers=Inga granskare issues.new.add_reviewer_title=Begär granskning +issues.choose.get_started=Kom igång +issues.choose.blank=Standard +issues.choose.blank_about=Skapa ett ärende från standardmall. issues.no_ref=Ingen branch/Tag specificerad issues.create=Skapa Ärende issues.new_label=Ny etikett @@ -869,9 +945,12 @@ issues.label_templates.fail_to_load_file=Laddning av etikettmallen '%s' misslyck issues.add_label_at=lade till etiketten
%s
%s issues.remove_label_at=tog bort etiketten
%s
%s issues.add_milestone_at=`lade till denna till milstolpe %s %s` +issues.add_project_at=`lade till detta till projektet %s %s` issues.change_milestone_at='modifierade milstolpen från %s till %s %s' issues.remove_milestone_at='tog bort denna från milstolpen %s %s' +issues.remove_project_at=`tog bort detta från projektet %s %s` issues.deleted_milestone=`(raderad)` +issues.deleted_project=`(raderad)` issues.self_assign_at=`tilldelade denna till sig själv %s` issues.add_assignee_at=`blev tilldelad denna av %s %s` issues.remove_assignee_at=`tilldelning togs bort av %s %s` @@ -1005,6 +1084,7 @@ issues.add_time=Lägg till tid manuellt issues.add_time_short=Lägg till tid issues.add_time_cancel=Avbryt issues.add_time_history=`la till tillbringad tid %s` +issues.del_time_history=`raderade tillbringad tid %s` issues.add_time_hours=Timmar issues.add_time_minutes=Minuter issues.add_time_sum_to_small=Inge tid har angivits. @@ -1016,6 +1096,8 @@ issues.due_date=Förfallodatum issues.invalid_due_date_format=Datumsformatet för förfallodatum måste följa 'yyyy-MM-dd'. issues.error_modifying_due_date=Det gick inte att ändra förfallodatumet. issues.error_removing_due_date=Det gick inte att ta bort förfallodatumet. +issues.push_commit_1=lade till %d commit %s +issues.push_commits_n=lade till %d committer %s issues.due_date_form=yyyy-MM-dd issues.due_date_form_add=Lägg till förfallodatum issues.due_date_form_edit=Ändra @@ -1062,11 +1144,18 @@ issues.review.left_comment=lämnade en kommentar issues.review.content.empty=Du måste skriva en kommentar som anger de önskade ändringarna. issues.review.reject=begärda ändringar %s issues.review.wait=begärdes för granskning %s +issues.review.add_review_request=begärde granskning från %s %s +issues.review.remove_review_request=tog bort granskningsbegäran för %s %s +issues.review.remove_review_request_self=vägrade att granska %s issues.review.pending=Väntande issues.review.review=Granska issues.review.reviewers=Granskare issues.review.show_outdated=Visa föråldrade issues.review.hide_outdated=Dölj föråldrade +issues.review.show_resolved=Visa löst +issues.review.hide_resolved=Dölj löst +issues.review.resolve_conversation=Lös konversation +issues.review.resolved_by=markerade denna konversation som löst issues.assignee.error=Inte alla tilldelade har lagts till på grund av ett oväntat fel. pulls.desc=Aktivera pull-förfrågningar och kodgranskning. @@ -1097,6 +1186,8 @@ pulls.cannot_merge_work_in_progress=Denna pull-förfrågan är markerad som ett pulls.data_broken=Pull-requesten är trasig pågrund av oexisterande information on forken. pulls.files_conflicted=Den här pull-förfrågan ha ändringar som är i konflikt med mål-branchen. pulls.is_checking=Merge-konfliktkontroll pågår. Försök igen senare. +pulls.required_status_check_failed=Vissa tvingande kontroller lyckades inte. +pulls.required_status_check_missing=Vissa tvingande kontroller saknas. pulls.required_status_check_administrator=Som administratör kan du fortfarande merga den här pull requesten. pulls.blocked_by_approvals=Denna pull request har inte tillräckligt många godkännanden än. %d av %d godkännanden. pulls.can_auto_merge_desc=Denna pull-förfrågan kan sammanfogas automatiskt. @@ -1110,7 +1201,7 @@ pulls.rebase_merge_pull_request=Rebase och sammanfogning pulls.rebase_merge_commit_pull_request=Rebase och Merge (--no-ff) pulls.squash_merge_pull_request=Squasha och sammanfogning pulls.invalid_merge_option=Du kan inte använda detta mergealternativet för denna pull-request. -pulls.push_rejected=Sammanfogningen misslyckades: push-förfrågan avvisades med följande meddelande:
%s
Granska githookarna för denna utvecklingskatalog +; %[2]s
%[3]s
pulls.push_rejected_no_message=Sammanfogningen misslyckades: Push-förfrågan avvisades, men utan något meddelande från fjärrvärden.
Granska githookarna för denna utvecklingskatalog pulls.open_unmerged_pull_exists=`Du kan inte återuppliva denna pull-request då det redan finns en identisk pull-request öppen (#%d).` pulls.update_branch=Uppdatera branch @@ -1298,11 +1389,13 @@ settings.convert_desc=Du kan konvertera denna spegling till en vanlig utveckling settings.convert_notices_1=Denna operation kommer att omvandla speglingen till en vanlig utvecklingskatalog och detta kan inte ångras. settings.convert_confirm=Konvertera utvecklingskatalog settings.convert_succeed=Speglingen har blivit konverterad till en vanlig utvecklingskatalog. +settings.convert_fork=Konvertera till vanlig utvecklingskatalog settings.transfer=Överför Ägarskap settings.transfer_desc=Överför denna utvecklingskatalog till en användare eller organisation för vilken du har administratörsrättigheter till. settings.transfer_notices_1=- Du kommer förlora åtkomst till denna utvecklingskatalog om du för över den till en individuell användare. settings.transfer_notices_2=- Du kommer behålla åtkomst till utvecklingskatalogen om du för över den till en organisation som du antingen äger eller är delägare i. settings.transfer_form_title=Ange utvecklingskatalogens namn för att bekräfta: +settings.trust_model.collaborator=Medarbetare settings.wiki_delete=Ta bort wiki-data settings.wiki_delete_desc=Borttagning av utvecklingskatalogens wiki-data är permanent och kan ej ångras. settings.wiki_delete_notices_1=- Detta kommer permanent ta bort och inaktivera utvecklingskatalogens wiki för %s. @@ -1424,6 +1517,7 @@ settings.protect_disable_push=Inaktivera Push settings.protect_disable_push_desc=Inga push-förfrågningar kommer att tillåtas till denna branch. settings.protect_enable_push=Aktivera Push settings.protect_enable_push_desc=Alla med skrivrättigheter kommer att kunna pusha till denna branch (men inte force-pusha). +settings.protect_whitelist_deploy_keys=Vitlista deploy-nyckar med skrivåtkomst till push. settings.protect_whitelist_users=Vitlistade användare för pushning: settings.protect_whitelist_search_users=Sök användare… settings.protect_whitelist_teams=Vitlistade team för pushning: @@ -1433,11 +1527,13 @@ settings.protect_merge_whitelist_committers_desc=Tillåt endast vitlistade anvä settings.protect_merge_whitelist_users=Vitlistade användare för sammanfogning: settings.protect_merge_whitelist_teams=Vitlistade teams för sammanfogning: settings.protect_check_status_contexts=Aktivera statuskontroller +settings.protect_check_status_contexts_desc=Kräv godkända statuskontroller innan merge. Välj vilka statuskontroller som godkännas innan grenar kan slås samman till en gren som matchar denna regel. När aktiverad, måste committer först pushas till en annan gren, sedan mergas eller pushas direkt till en gren som matchar denna regel efter statuskontroll har har godkännts. Om inga context väljs måste den sista committen vara framgångsrik oavsett context. settings.protect_check_status_contexts_list=Statuskontroller funna under senaste veckan för denna utvecklingskatalog settings.protect_required_approvals=Godkännanden som krävs: settings.protect_approvals_whitelist_users=Vitlistade granskare: settings.protect_approvals_whitelist_teams=Vitlistade team för granskning: settings.require_signed_commits=Kräv signerade commits +settings.require_signed_commits_desc=Avvisa pushar till den här grenen om dom är osignerade eller inte verifierbara. settings.add_protected_branch=Aktivera skydd settings.delete_protected_branch=Inaktivera skydd settings.update_protect_branch_success=Skydd för branch '%s' har blivit uppdaterat. @@ -1517,6 +1613,7 @@ diff.review.placeholder=Granskningskommentar diff.review.comment=Kommentar diff.review.approve=Godkänn diff.review.reject=Begär ändringar +diff.committed_by=committad av releases.desc=Följ projektversioner och nerladdningar. release.releases=Släpp @@ -1525,6 +1622,8 @@ release.draft=Utkast release.prerelease=Försläpp release.stable=Stabil release.edit=redigera +release.ahead.commits=%d committer +release.ahead.target=till %s sedan denna utgåva release.source_code=Källkod release.new_subheader=Releaser organiserar projektversioner. release.edit_subheader=Releaser organiserar projektversioner. @@ -1968,7 +2067,6 @@ config.session_config=Sessionskonfiguration config.session_provider=Sessionsleverantör config.provider_config=Leverantörskonfiguration config.cookie_name=Cookie-namn -config.enable_set_cookie=Aktivera sättning av kaka config.gc_interval_time=Tidsintervall för skräpsamling config.session_life_time=Livstid för session config.https_only=Endast HTTPS diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index e26ca2ff36ca..5631e223769a 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -10,7 +10,7 @@ link_account=Bağlantı hesabı register=Üye Ol website=Web sitesi version=Sürüm -powered_by=%s tarafından desteklenmektedir +powered_by=%s tarafından desteklenen page=Sayfa template=Şablon language=Dil @@ -21,10 +21,12 @@ signed_in_as=Giriş yapan: enable_javascript=Bu web sitesi JavaScript ile daha iyi çalışır. toc=İçindekiler Tablosu licenses=Lisanslar +return_to_gitea=Gitea'ya Dön username=Kullanıcı Adı email=E-posta Adresi password=Parola +access_token=Erişim Kodu re_type=Parolayı yeniden yazın captcha=CAPTCHA twofa=İki Aşamalı Doğrulama @@ -297,6 +299,8 @@ authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi? authorization_failed=Yetkilendirme başarısız oldu authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun. sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu +password_pwned=Seçtiğiniz parola, daha önce herkese açık veri ihlallerinde açığa çıkan bir çalınan parola listesindedir. Lütfen farklı bir parola ile tekrar deneyin. +password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı [mail] activate_account=Lütfen hesabınızı aktifleştirin @@ -351,6 +355,10 @@ 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. +repository_files_already_exist=Bu depo için dosyalar zaten var. Sistem yöneticisine başvurun. +repository_files_already_exist.adopt=Bu depo için dosyalar zaten var ve yalnızca Kabul Edilebilir. +repository_files_already_exist.delete=Bu depo için dosyalar zaten var. Onları silmelisiniz. +repository_files_already_exist.adopt_or_delete=Bu depo için dosyalar zaten var. Ya kabul edin ya da silin. 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. @@ -369,11 +377,12 @@ enterred_invalid_owner_name=Yeni sahip ismi hatalı. enterred_invalid_password=Girdiğiniz parola hatalı. user_not_exist=Böyle bir kullanıcı yok. team_not_exist=Böyle bir takım bulunmuyor. -last_org_owner=Son kullanıcıyı 'sahipler' takımından kaldıramazsınız. Herhangi bir takımda en az bir sahip olması gerekir. +last_org_owner=Son kullanıcıyı 'sahipler' takımından çıkaramazsınız. Bir organizasyonun en az bir sahibi olmalıdır. cannot_add_org_to_team=Organizasyon, takım üyesi olarak eklenemez. invalid_ssh_key=SSH anahtarınız doğrulanamıyor: %s invalid_gpg_key=GPG anahtarınız doğrulanamıyor: %s +invalid_ssh_principal=Geçersiz sorumlu: %s unable_verify_ssh_key=SSH anahtarı doğrulanamıyor; hatalar için lütfen tekrar kontrol edin. auth_failed=Kimlik doğrulaması başarısız oldu: %v @@ -421,6 +430,7 @@ uid=Tekil ID u2f=Güvenlik Anahtarları public_profile=Herkese Açık Profil +biography_placeholder=Bize biraz kendinizden bahsedin profile_desc=E-posta adresiniz bilgilendirmeler ve diğer işlemler için kullanılacaktır. password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz. full_name=Ad Soyad @@ -491,9 +501,11 @@ keep_email_private_popup=E-posta adresiniz diğer kullanıcılardan gizlenir. openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar. manage_ssh_keys=SSH Anahtarlarını Yönet +manage_ssh_principals=SSH Sertifika Sorumlularını Yönet manage_gpg_keys=GPH Anahtarlarını Yönet add_key=Anahtar Ekle ssh_desc=Bu genel SSH anahtarları hesabınızla ilişkilendirildi. İlgili özel anahtarlar, depolarınıza tam erişim sağlar. +principal_desc=Bu SSH sertifika sorumluları, hesabınızla ilişkilidir ve depolarınıza tam erişim sağlar. gpg_desc=Bu açık GPG anahtarları hesabınızla ilişkilendirildi. İşlemelerin doğrulanmasına izin verdiği için özel anahtarlarınızı güvende tutun. ssh_helper=Yardıma ihtiyacınız mı var? Github klavuzundaki Kendi SSH anahtarınızı oluşturun bölümüne göz atın veya SSH'ı kullanırken karşılaşabileceğinizortak problemler'i çözün. gpg_helper=Yardıma ihtiyacınız mı var?Github klavuzundaki GPG hakkında bölümüne göz atınız. @@ -501,23 +513,30 @@ add_new_key=SSH Anahtarı Ekle add_new_gpg_key=GPG Anahtarı Ekle key_content_ssh_placeholder='ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', veya 'ecdsa-sha2-nistp521' ile başlar key_content_gpg_placeholder='-----BEGIN PGP PUBLIC KEY BLOCK-----' ile başlar +add_new_principal=Sorumlu Ekle ssh_key_been_used=Bu SSH anahtarı, sunucuya zaten eklenmiş. -ssh_key_name_used=Aynı isimde bir SSH anahtarı zaten hesabınıza eklenmiş. +ssh_key_name_used=Hesabınızda aynı ada sahip bir SSH anahtarı zaten var. +ssh_principal_been_used=Bu sorumlu sunucuya zaten eklendi. gpg_key_id_used=Aynı kimliğe sahip bir açık GPG anahtarı zaten var. gpg_no_key_email_found=Bu GPG anahtarı, hesabınızla ilişkili hiçbir e-posta adresiyle kullanılamaz. subkeys=Alt anahtarlar key_id=Anahtar Kimliği key_name=Anahtar İsmi key_content=İçerik +principal_content=İçerik add_key_success=SSH anahtarı '%s' eklendi. add_gpg_key_success=GPG anahtarı '%s' eklendi. +add_principal_success=SSH sertifika sorumlusu '%s' eklendi. delete_key=Sil ssh_key_deletion=SSH Anahtarını Sil gpg_key_deletion=GPG Anahtarını Sil +ssh_principal_deletion=SSH Sertifika Sorumlusunu Kaldır ssh_key_deletion_desc=Bir SSH anahtarını kaldırmak, hesabınıza erişimi iptal eder. Devam edilsin mi? gpg_key_deletion_desc=Bir GPG anahtarını kaldırmak, onun tarafından imzalanan işlemelerin doğrulamasını iptal eder. Devam edilsin mi? +ssh_principal_deletion_desc=Bir SSH Sertifika Sorumlusunun kaldırılması, hesabınıza erişimini iptal eder. Devam edilsin mi? ssh_key_deletion_success=SSH anahtarı silindi. gpg_key_deletion_success=GPG anahtarı silindi. +ssh_principal_deletion_success=Sorumlu kaldırıldı. add_on=Eklendiği tarih valid_until=-E kadar geçerli valid_forever=Sürekli geçerlidir @@ -527,10 +546,10 @@ can_read_info=Oku can_write_info=Yaz key_state_desc=Bu anahtar son 7 gün içinde kullanılmıştır token_state_desc=Bu token son 7 gün içinde kullanılmıştır +principal_state_desc=Bu sorumlu son 7 gün içinde kullanıldı show_openid=Profilde göster hide_openid=Profilden gizle ssh_disabled=SSH devre dışı bırakıldı - manage_social=Bağlanmış Sosyal Hesapları Yönet social_desc=Bu sosyal hesaplar Gitea hesabınızla bağlantılı. Hepsini Gitea hesabınıza giriş yapmak için kullanılabildiğinden emin olun. unbind=Bağlantıyı Kaldır @@ -676,6 +695,15 @@ pick_reaction=Reaksiyonunu seç reactions_more=ve %d daha fazla unit_disabled=Site yöneticisi bu depo bölümünü devre dışı bıraktı. language_other=Diğer +adopt_search=Kabul edilmeyen depoları aramak için kullanıcı adını girin... (tümünü bulmak için boş bırakın) +adopt_preexisting_label=Dosyaları Kabul Et +adopt_preexisting=Önceden var olan dosyaları kabul et +adopt_preexisting_content=%s konumundan depo oluştur +adopt_preexisting_success=%s konumundan dosyalar kabul edildi ve depo oluşturuldu +delete_preexisting_label=Sil +delete_preexisting=Önceden var olan dosyaları sil +delete_preexisting_content=%s içindeki dosyaları sil +delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi desc.private=Özel desc.public=Genel @@ -705,15 +733,17 @@ form.name_reserved=Depo ismi '%s' rezerve edildi. form.name_pattern_not_allowed='%s' deseni, depo adı için geçerli değildir. need_auth=Yetkilendirmeyi Klonla -migrate_type=Göç Türü -migrate_type_helper=Bu depo bir yansı olacaktır -migrate_type_helper_disabled=Site yöneticiniz yeni yansıları devre dışı bıraktı. +migrate_options=Göç Seçenekleri +migrate_service=Göç Hizmeti +migrate_options_mirror_helper=Bu depo bir yansı olacaktır +migrate_options_mirror_disabled=Site yöneticiniz yeni yansıları devre dışı bıraktı. migrate_items=Göç Öğeleri migrate_items_wiki=Wiki migrate_items_milestones=Kilometre Taşları migrate_items_labels=Etiketler migrate_items_issues=Konular migrate_items_pullrequests=Değişiklik İstekleri +migrate_items_merge_requests=Birleştirme İstekleri migrate_items_releases=Sürümler migrate_repo=Depoyu Göç Ettir migrate.clone_address=URL'den Taşı / Klonla @@ -723,17 +753,24 @@ migrate.permission_denied=Yerel depoları içeri aktarma izniniz yok. migrate.invalid_local_path=Yerel yol geçersiz. Mevcut değil veya bir dizin değil. migrate.failed=Göç başarısız: %v migrate.lfs_mirror_unsupported=LFS nesnelerini yansılama desteklenmiyor - yerine 'git lfs fetch --all' ve 'git lfs push --all' kullanın. -migrate.migrate_items_options=Github'dan göç yaparken, bir kullanıcı adı girin ve göç seçenekleri görüntülenecektir. +migrate.migrate_items_options=Ek öğeleri taşımak için Erişim Kodu gereklidir migrated_from=%[2]s konumundan göç edildi migrated_from_fake=%[1]s Konumundan Göç Edildi +migrate.migrate=%s Konumundan Göç Et migrate.migrating=%s konumundan taşınıyor ... migrate.migrating_failed=%s konumundan taşıma başarısız oldu. +migrate.github.description=Github.com veya Github Enterprise'dan veri taşıma. +migrate.git.description=Git hizmetlerinden git verilerini taşıma veya yansıtma +migrate.gitlab.description=Verileri GitLab.com'dan veya Kendi Kendine Barındırılan gitlab sunucusundan taşıma. +migrate.gitea.description=Verileri Gitea.com'dan veya Kendi Kendine Barındırılan Gitea sunucusundan taşıma. mirror_from=şunun yansıması forked_from=şundan çatallanmış generated_from=şuradan oluşturuldu fork_from_self=Sahibi olduğunuz bir depoyu çatallayamazsınız. fork_guest_user=Bu depoyu çatallamak için giriş yap. +watch_guest_user=Bu depoyu izlemek için oturum açın. +star_guest_user=Bu depoyu yıldızlamak için oturum açın. copy_link=Kopyala copy_link_success=Bağlantı kopyalandı copy_link_error=Kopyalamak için ⌘C veya Ctrl-C kullanın @@ -756,8 +793,9 @@ code=Kod code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş. branch=Dal tree=Ağaç +clear_ref='Geçerli referansı temizle' filter_branch_and_tag=Dal veya biçim imini filtrele -branches=Dallar +branches=Dal tags=Biçim İmleri issues=Konular pulls=Değişiklik İstekleri @@ -829,11 +867,13 @@ editor.file_deleting_no_longer_exists=Silinen '%s' dosyası bu depoda artık yer editor.file_changed_while_editing=Düzenlemeye başladığınızdan beri dosya içeriği değişti. Görmek için burayı tıklayın veya üzerine yazmak için değişiklikleri yine de işleyin. editor.file_already_exists=Bu depoda '%s' isimli bir dosya zaten mevcut. editor.commit_empty_file_header=Boş bir dosya işle -editor.commit_empty_file_text=İşleme yaptığınız dosya boş. Devam edilsin mi? +editor.commit_empty_file_text=İşlemek üzere olduğunuz dosya boş. Devam edilsin mi? editor.no_changes_to_show=Gösterilecek değişiklik yok. -editor.fail_to_update_file=Şu hata ile '%s' dosyasını güncelleme/oluşturma başarısız oldu: %v +editor.fail_to_update_file='%s' dosyası güncellenemedi/oluşturulamadı. +editor.fail_to_update_file_summary=Hata Mesajı: editor.push_rejected_no_message=Değişiklik, bir ileti olmadan sunucu tarafından reddedildi. Githooks'u kontrol edin. -editor.push_rejected=Değişiklik sunucu tarafından aşağıdaki mesajla reddedildi:
%s
Lütfen githooks'u kontrol edin. +editor.push_rejected=Değişiklik sunucu tarafından reddedildi. Lütfen git istemcilerini kontrol edin. +editor.push_rejected_summary=Tam Red Mesajı: editor.add_subdir=Bir dizin ekle… editor.unable_to_upload_files=Şu hata ile dosyalar '%s' 'a yüklenemedi: %v editor.upload_file_is_locked='%s' dosyası %s tarafından kilitlendi. @@ -844,10 +884,10 @@ editor.user_no_push_to_branch=Kullanıcı dala gönderemez editor.require_signed_commit=Dal imzalı bir işleme gerektirir commits.desc=Kaynak kodu değişiklik geçmişine göz atın. -commits.commits=İşlemeler +commits.commits=İşleme 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.search.tooltip=Anahtar kelimeleri "yazar:", "işleyici:", "sonra:" veya "önce:", örneğin; "eski haline yazan: Alice önce: 2019-04-01" ile önekleyebilirsiniz. commits.find=Ara commits.search_all=Tüm Dallar commits.author=Yazar @@ -857,7 +897,7 @@ commits.older=Daha Eski commits.newer=Daha yeni commits.signed_by=İmzalayan commits.signed_by_untrusted_user=Güvenilmeyen kullanıcı tarafından imzalandı -commits.signed_by_untrusted_user_unmatched=İşlemeci ile eşleşmeyen güvenilmeyen kullanıcı tarafından imzalanmış +commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilmeyen kullanıcı tarafından imzalanmış commits.gpg_key_id=GPG Anahtar Kimliği ext_issues=Dışsal Konular @@ -924,6 +964,9 @@ issues.new.clear_assignees=Atamaları Temizle issues.new.no_assignees=Atanan Kişi Yok issues.new.no_reviewers=Değerlendirici yok issues.new.add_reviewer_title=İnceleme iste +issues.choose.get_started=Başla +issues.choose.blank=Varsayılan +issues.choose.blank_about=Varsayılan şablondan bir konu oluşturun. issues.no_ref=Bölüm/Etiket Belirtilmedi issues.create=Konu Oluştur issues.new_label=Yeni Etiket @@ -938,18 +981,18 @@ issues.label_templates.fail_to_load_file=Etiket şablon dosyası yüklemesi baş issues.add_label_at=
%s
%s etiketini ekledi issues.remove_label_at=
%s
%s etiketini kaldırdı issues.add_milestone_at=`%[2]s %[1]s kilometre taşına ekledi` -issues.add_project_at=`bunu %s %s projesine ekledi` +issues.add_project_at=`bunu %s projesine %s ekledi` issues.change_milestone_at=`%s kilometre taşını %s iken %s olarak değiştirdi` issues.change_project_at=`%s %s olan projeyi %s olarak değiştirdi issues.remove_milestone_at=`%[2]s %[1]s kilometre taşından kaldırdı` -issues.remove_project_at=`bunu %s %s projesinden kaldırdı` +issues.remove_project_at=`bunu %s projesinden %s kaldırdı` issues.deleted_milestone=`(silindi)` issues.deleted_project=`(silindi)` issues.self_assign_at=`%s kendini atadı` issues.add_assignee_at=`%[2]s %[1]s tarafından atandı` issues.remove_assignee_at=`ataması %[2]s %[1]s tarafından kaldırıldı` issues.remove_self_assignment=`atamalarını kaldırdı %s` -issues.change_title_at=`%s başlığı %s iken %s olarak değiştirdi` +issues.change_title_at=`başlığı %s iken %s olarak %s değiştirdi` issues.delete_branch_at=`%s dalı silindi %s` issues.open_tab=%d açık issues.close_tab=%d kapanmış @@ -1023,6 +1066,7 @@ issues.poster=Poster issues.collaborator=Katkıcı issues.owner=Sahibi issues.re_request_review=İncelemeyi yeniden iste +issues.is_stale=Bu incelemeden bu yana bu istekte değişiklikler oldu issues.remove_request_review=İnceleme isteğini kaldır issues.remove_request_review_block=İnceleme isteği kaldırılamadı issues.sign_in_require_desc=Bu konuşmaya katılmak için oturum aç. @@ -1147,6 +1191,7 @@ issues.review.remove_review_request_self=%s incelemeyi reddetti issues.review.pending=Beklemede issues.review.review=Gözden Geçir issues.review.reviewers=Gözden Geçirenler +issues.review.outdated=Eskimiş issues.review.show_outdated=Eskiyi göster issues.review.hide_outdated=Eskiyi gizle issues.review.show_resolved=Çözülenleri göster @@ -1171,7 +1216,7 @@ pulls.title_desc=%[2]s içindeki %[1]d işlemeyi , не існує або Ви не маєте права на її перегляд. [error] +occurred=Сталася помилка +report_message=Якщо ви впевнені, що це помилка Gitea, будь ласка, спробуйте відшукати відповідну проблему на GitHub та за відсутності створіть нову. [startpage] app_desc=Зручний власний сервіс хостингу репозиторіїв Git install=Легко встановити +install_desc=Просто запустіть виконуваний файл для вашої платформи, розміщуйте в Docker або встановіть пакунок. platform=Платформонезалежність platform_desc=Gitea виконується на платформі, для якої можливо скомпілювати Go: Windows, macOS, Linux, ARM, та інших. Оберіть ту, яка вам до вподоби! lightweight=Невибагливість @@ -162,6 +170,7 @@ openid_signin=Увімкнути реєстрацію за допомогою Op openid_signin_popup=Увімкнути вхід за допомогою OpenID. openid_signup=Увімкнути самостійну реєстрацію за допомогою OpenID openid_signup_popup=Увімкнути самореєстрацію користувачів на основі OpenID. +enable_captcha=Увімкнути CAPTCHA при реєстрації enable_captcha_popup=Вимагати перевірку CAPTCHA при самостійній реєстрації користувача. require_sign_in_view=Вимагати авторизації для перегляду сторінок require_sign_in_view_popup=Обмеження доступу до сторінки для користувачів, які виконали вхід. Відвідувачі побачать тільки сторінки входу і реєстрації. @@ -201,8 +210,17 @@ my_orgs=Мої організації my_mirrors=Мої дзеркала view_home=Переглянути %s search_repos=Шукати репозиторій… +filter=Інші фільтри +show_archived=Архівовані +show_both_archived_unarchived=Показано архівовані і не архівовані +show_only_archived=Показано тільки архівовані +show_only_unarchived=Показано тільки не архівовані +show_private=Приватні +show_both_private_public=Показано публічні та приватні +show_only_private=Показано тільки приватні +show_only_public=Показано тільки публічні issues.in_your_repos=В ваших репозиторіях @@ -351,7 +369,6 @@ enterred_invalid_owner_name=Ім'я нового власника не є дій enterred_invalid_password=Введений вами пароль некоректний. user_not_exist=Даний користувач не існує. team_not_exist=Команда не існує. -last_org_owner=Ви не можете вилучити останнього користувача з команди 'власники'. У кожній команді має бути хоча б один власник. cannot_add_org_to_team=Організацію неможливо додати як учасника команди. invalid_ssh_key=Неможливо перевірити ваш SSH ключ: %s @@ -372,11 +389,13 @@ repositories=Репозиторії activity=Публічна активність followers=Читачі starred=Обрані Репозиторії +projects=Проекти following=Читає follow=Підписатися unfollow=Відписатися heatmap.loading=Завантаження карти активності… user_bio=Біографія +disabled_public_activity=Цей користувач вимкнув публічний показ діяльності. form.name_reserved=Ім'я користувача "%s" зарезервовано. form.name_pattern_not_allowed=Шаблон '%s' не дозволено в імені користувача. @@ -415,6 +434,9 @@ continue=Продовжити cancel=Відмінити language=Мова ui=Тема +privacy=Приватність +keep_activity_private=Приховати діяльність на сторінці профілю +keep_activity_private_popup=Показувати вашу активність лише Вам та адміністраторам lookup_avatar_by_mail=Знайти Аватар за адресою електронної пошти federated_avatar_lookup=Знайти зовнішній аватар @@ -476,8 +498,9 @@ ssh_helper=Потрібна допомога? Дивіться gpg_helper= Потрібна допомога? Перегляньте посібник GitHub про GPG . add_new_key=Додати SSH ключ add_new_gpg_key=Додати GPG ключ +key_content_ssh_placeholder=Починається з 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384' або 'ecdsa-sha2-nistp521' +key_content_gpg_placeholder=Починається з '-----BEGIN PGP PUBLIC KEY BLOCK-----' ssh_key_been_used=Цей SSH ключ вже був додано до сервера. -ssh_key_name_used=Ключ SSH з таким самим ім'ям вже додано до вашого облікового запису. gpg_key_id_used=Публічний ключ GPG з таким самим ідентифікатором вже існує. gpg_no_key_email_found=Цей ключ GPG непридатний для використання з будь-якою електронною адресою, що пов'язана з вашим обліковим записом. subkeys=Підключі @@ -505,7 +528,6 @@ token_state_desc=Цей токен використовувався в оста show_openid=Показати у профілю hide_openid=Не показувати у профілі ssh_disabled=SSH вимкнено - manage_social=Керувати зв'язаними обліковими записами соціальних мереж social_desc=Ці адреси соціальних мереж пов'язані з вашим обліковим записом Gitea. Переконайтеся, що ви їх впізнаєте, оскільки вони можуть бути використані для входу в обліковий запис Gitea. unbind=Від'єднати @@ -518,6 +540,7 @@ new_token_desc=Додатки, що використовують токен, м token_name=Ім'я токену generate_token=Згенерувати токен generate_token_success=Ваш новий токен був створений. Скопіюйте його зараз, оскільки він не буде показаний знову. +generate_token_name_duplicate=Назва програми %s вже використовується. Будь ласка, використайте нову. delete_token=Видалити access_token_deletion=Видалити токен доступу access_token_deletion_desc=Видалення токена скасовує доступ до вашого облікового запису для програм, що використовують його. Продовжити? @@ -651,6 +674,13 @@ reactions_more=додати %d більше unit_disabled=Адміністратор сайту вимкнув цей розділ репозиторію. language_other=Інші +desc.private=Приватний +desc.public=Публічний +desc.private_template=Приватний шаблон +desc.public_template=Шаблон +desc.internal=Внутрішній +desc.internal_template=Внутрішній шаблон +desc.archived=Архівний template.items=Елементи шаблону template.git_content=Вміст Git (типова гілка) @@ -672,8 +702,6 @@ 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=Етапи @@ -689,7 +717,6 @@ 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... @@ -727,7 +754,10 @@ branches=Гілки tags=Теги issues=Проблеми pulls=Запити на злиття +project_board=Проєкти labels=Мітки +org_labels_desc=Мітки рівня організації можуть використовуватися в усіх репозиторіях цієї організації +org_labels_desc_manage=керувати milestones=Етап commits=Коміти @@ -743,6 +773,8 @@ audio_not_supported_in_browser=Ваш браузер не підтримує т stored_lfs=Збережено з Git LFS symbolic_link=Символічне посилання commit_graph=Графік комітів +commit_graph.monochrome=Монохром +commit_graph.color=Колір blame=Звинувачення normal_view=Звичайний вигляд line=рядок @@ -790,11 +822,8 @@ 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.push_rejected_no_message=Зміна була відхилена сервером без повідомлення. Будь ласка, перевірте git-хуки. -editor.push_rejected=Зміна була відхилена сервером з повідомленням:
%s
Будь ласка, перевірте git-хуки. editor.add_subdir=Додати каталог… editor.unable_to_upload_files=Не вдалося завантажити файли до '%s' через помилку: %v editor.upload_file_is_locked=Файл '%s' заблоковано %s. @@ -824,21 +853,67 @@ commits.gpg_key_id=Ідентифікатор GPG ключа ext_issues=Зов. Проблеми ext_issues.desc=Посилання на зовнішню систему відстеження проблем. +projects=Проєкти +projects.desc=Керуйте проблемами та запитами злиття на дошках проєкту. +projects.create=Створити проєкт +projects.title=Назва +projects.new=Новий проєкт +projects.new_subheader=Координуйте, відстежуйте та оновлюйте інформацію про виконувану роботу в одному місці, аби проєкти залишалися прозорими та за розкладом. +projects.create_success=Проєкт '%s' створено. +projects.deletion=Видалити проєкт +projects.deletion_desc=Видалення проєкту видаляє його з усіх пов'язаних проблем. Продовжити? +projects.deletion_success=Проєкт видалено. +projects.edit=Редагувати проєкти +projects.edit_subheader=Проєкти призначені для організації проблем та відстеження поступу. +projects.modify=Оновити проєкт +projects.edit_success=Проєкт '%s' оновлено. +projects.type.none=Відсутній +projects.type.basic_kanban=Спрощений канбан +projects.type.bug_triage=Сортування помилок +projects.template.desc=Шаблон проєкту +projects.template.desc_helper=Оберіть шаблон проєкту, аби почати +projects.type.uncategorized=Без категорії +projects.board.edit=Редагувати дошку +projects.board.edit_title=Нова назва дошки +projects.board.new_title=Назва нової дошки +projects.board.new_submit=Створити +projects.board.new=Нова дошка +projects.board.delete=Видалити дошку +projects.board.deletion_desc=Видалення дошки проєкту перенесе всі пов'язані проблеми в дошку 'Без категорії'. Продовжити? +projects.open=Відкрити +projects.close=Закрити issues.desc=Організація звітів про помилки, завдань та етапів. +issues.filter_assignees=Фільтр виконавців +issues.filter_milestones=Фільтр етапів +issues.filter_projects=Фільтр проєктів +issues.filter_labels=Фільтр міток +issues.filter_reviewers=Фільтр рецензентів issues.new=Нова проблема issues.new.title_empty=Заголовок не може бути пустим issues.new.labels=Мітки +issues.new.add_labels_title=Застосувати мітки issues.new.no_label=Без мітки issues.new.clear_labels=Очистити мітки +issues.new.projects=Проєкти +issues.new.add_project_title=Призначити проєкт +issues.new.clear_projects=Скинути проєкти +issues.new.no_projects=Проєкт відсутній +issues.new.open_projects=Відкриті проєкти +issues.new.closed_projects=Закриті проєкти +issues.new.no_items=Немає елементів issues.new.milestone=Етап +issues.new.add_milestone_title=Призначити етап issues.new.no_milestone=Етап відсутній issues.new.clear_milestone=Очистити етап issues.new.open_milestone=Активні етапи issues.new.closed_milestone=Закриті етапи issues.new.assignees=Виконавеці +issues.new.add_assignees_title=Призначити користувачів issues.new.clear_assignees=Прибрати виконавеців issues.new.no_assignees=Немає виконавеця +issues.new.no_reviewers=Немає рецензентів +issues.new.add_reviewer_title=Попросити рецензію issues.no_ref=Не вказана гілка або тег issues.create=Створити проблему issues.new_label=Нова мітка @@ -850,10 +925,16 @@ 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.add_project_at=`додав до проєкту %s %s` issues.change_milestone_at=`змінено цільової етап з %s на %s %s` +issues.change_project_at=`змінив проєкт з %s на %s %s` issues.remove_milestone_at=`видалено з етапу%s %s` +issues.remove_project_at=`видалив з проєкту %s %s` issues.deleted_milestone=`(видалено)` +issues.deleted_project=`(видалено)` issues.self_assign_at=`самостійно призначений %s` issues.add_assignee_at=`був призначений %s %s` issues.remove_assignee_at=`був знятий з призначення %s %s` @@ -897,7 +978,9 @@ 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=закрито %[3]s %[1]s issues.opened_by_fake=%[1]s відкрито %[2]s +issues.closed_by_fake=закрито %[2]s %[1]s issues.previous=Попередній issues.next=Далі issues.open_title=Відкрито @@ -911,10 +994,13 @@ issues.context.edit=Редагувати issues.context.delete=Видалити issues.no_content=Тут ще немає жодного змісту. issues.close_issue=Закрити +issues.pull_merged_at=`злив коміт %[2]s в %[3]s %[4]s` issues.close_comment_issue=Прокоментувати і закрити issues.reopen_issue=Відкрити знову issues.reopen_comment_issue=Прокоментувати та відкрити знову 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` @@ -926,6 +1012,9 @@ issues.ref_from=`із %[1]s` issues.poster=Автор issues.collaborator=Співавтор issues.owner=Власник +issues.re_request_review=Повторно попросити рецензію +issues.remove_request_review=Видалити запит рецензування +issues.remove_request_review_block=Неможливо видалити запит рецензування issues.sign_in_require_desc=Підпишіться щоб приєднатися до обговорення. issues.edit=Редагувати issues.cancel=Відмінити @@ -993,6 +1082,9 @@ issues.due_date=Дата завершення issues.invalid_due_date_format=Дата закінчення має бути в форматі 'ррр-мм-дд'. issues.error_modifying_due_date=Не вдалося змінити дату завершення. issues.error_removing_due_date=Не вдалося видалити дату завершення. +issues.push_commit_1=додав %d коміт %s +issues.push_commits_n=додав %d коміти(-ів) %s +issues.force_push_codes=`виконав force-push %[1]s з %[2]s на %[4]s %[6]s` issues.due_date_form=рррр-мм-дд issues.due_date_form_add=Додати дату завершення issues.due_date_form_edit=Редагувати @@ -1011,6 +1103,8 @@ issues.dependency.add=Додати залежність… issues.dependency.cancel=Відмінити issues.dependency.remove=Видалити issues.dependency.remove_info=Видалити цю залежність +issues.dependency.added_dependency=`додав нову залежність %s` +issues.dependency.removed_dependency=`видалив залежність %s` issues.dependency.issue_closing_blockedby=Закриття цього запиту на злиття заблокує наступні проблеми issues.dependency.pr_closing_blockedby=Закриття цієї проблеми заблокує наступні проблеми issues.dependency.issue_close_blocks=Ця проблема блокує закриття залежних проблем @@ -1033,13 +1127,23 @@ issues.review.self.approval=Ви не можете схвалити власни issues.review.self.rejection=Ви не можете надіслати запит на зміну на власний пулл-реквест. issues.review.approve=зміни затверджено %s issues.review.comment=рецензовано %s +issues.review.left_comment=додав коментар issues.review.content.empty=Запрошуючи зміни, ви зобов'язані залишити коментар з поясненнями своїх побажань відносно Pull Request'а. issues.review.reject=зробив запит змін %s +issues.review.wait=попросив рецензію %s +issues.review.add_review_request=попросив рецензію від %s %s +issues.review.remove_review_request=видалив запит на рецензію до %s %s +issues.review.remove_review_request_self=відмовився рецензувати %s issues.review.pending=Очікування issues.review.review=Рецензії issues.review.reviewers=Рецензенти issues.review.show_outdated=Показати застарілі issues.review.hide_outdated=Приховати застарілі +issues.review.show_resolved=Показати вирішене +issues.review.hide_resolved=Приховати вирішене +issues.review.resolve_conversation=Завершити обговорення +issues.review.un_resolve_conversation=Поновити обговорення +issues.review.resolved_by=позначив обговорення завершеним issues.assignee.error=Додано не всіх виконавців через непередбачену помилку. pulls.desc=Увімкнути запити на злиття та огляд коду. @@ -1071,9 +1175,11 @@ pulls.data_broken=Зміст цього запиту було порушено pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою. pulls.is_checking=Триває перевірка конфліктів, будь ласка обновіть сторінку дещо пізніше. pulls.required_status_check_failed=Деякі необхідні перевірки виконані з помилками. +pulls.required_status_check_missing=Декілька з необхідних перевірок відсутні. pulls.required_status_check_administrator=Як адміністратор ви все одно можете об'єднати цей запит на злиття. pulls.blocked_by_approvals=Цей pull-запит ще не має достатньо схвалень. %d від %d схвалень надано. pulls.blocked_by_rejection=Цей запит на злиття має запит змін від офіційного рецензента. +pulls.blocked_by_outdated_branch=Цей запит на злиття заблоковано, оскільки він застарів. pulls.can_auto_merge_desc=Цей запит можна об'єднати автоматично. pulls.cannot_auto_merge_desc=Цей запит на злиття не може бути злитий автоматично через конфлікти. pulls.cannot_auto_merge_helper=Злийте вручну для вирішення конфліктів. @@ -1083,6 +1189,8 @@ pulls.approve_count_1=%d схвалення pulls.approve_count_n=%d схвалень pulls.reject_count_1=%d запит на зміну pulls.reject_count_n=%d запити на зміну +pulls.waiting_count_1=очікується %d рецензія +pulls.waiting_count_n=очікується %d рецензії(й) pulls.no_merge_desc=Цей запити на злиття неможливо злити, оскільки всі параметри об'єднання репозиторія вимкнено. pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну. @@ -1095,19 +1203,22 @@ pulls.rebase_merge_commit_pull_request=Rebase та злитя (--no-ff) pulls.squash_merge_pull_request=Об'єднати (Squash) і злити pulls.require_signed_wont_sign=Гілка вимагає підписаних комітів, але це злиття не буде підписано pulls.invalid_merge_option=Цей параметр злиття не можна використовувати для цього Pull Request'а. -pulls.merge_conflict=Помилка злиття: при злитті виник конфлікт: %[1]s
%[2]s
Підказка: спробуйте іншу стратегію -pulls.rebase_conflict=Помилка злиття: виник конфлікт при виконанні rebase коміту: %[1]s
%[2]s
%[3]s
Підказка: спробуйте іншу стратегію +; %[2]s
%[3]s
pulls.unrelated_histories=Помилка злиття: head та base злиття не мають спільної історії. Підказка: спробуйте іншу стратегію pulls.merge_out_of_date=Помилка злиття: base було оновлено, поки відбувалося злиття. Підказка: спробуйте знову. -pulls.push_rejected=Не вдалося виконати злиття: push було відхилено з наступним повідомленням:
%s
Перегляньте git-хуки для цього репозиторію pulls.push_rejected_no_message=Не вдалося виконати злиття: push було відхилено без повідомлення.
Перегляньте git-хуки для цього репозиторію pulls.open_unmerged_pull_exists=`Ви не можете знову відкрити, оскільки вже існує запит на злиття (%d) з того ж репозиторія з тією ж інформацією про злиття і в очікуванні.` pulls.status_checking=Деякі перевірки знаходяться на розгляді pulls.status_checks_success=Всі перевірки були успішними +pulls.status_checks_warning=Декілька перевірок завершилися з попередженнями +pulls.status_checks_failure=Декілька перевірок не були успішними +pulls.status_checks_error=Декілька перевірок завершилися з помилками pulls.update_branch=Оновити гілку pulls.update_branch_success=Оновлення гілки пройшло успішно pulls.update_not_allowed=Ви не можете оновити гілку pulls.outdated_with_base_branch=Ця гілка застаріла відносно базової гілки +pulls.closed_at=`закрив цей запит на злиття %[2]s` +pulls.reopened_at=`повторно відкрив цей запит на злиття %[2]s` milestones.new=Новий етап milestones.open_tab=%d відкрито @@ -1297,6 +1408,7 @@ 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.projects_desc=Увімкнути проєкти у репозиторії settings.admin_settings=Налаштування адміністратора settings.admin_enable_health_check=Включити перевірки працездатності репозиторію (git fsck) settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити проблему за допомогою коміта, зробленого не головній гілці @@ -1307,6 +1419,11 @@ settings.convert_desc=Ви можете сконвертувати це дзер settings.convert_notices_1=Ця операція перетворить дзеркало у звичайний репозиторій і не може бути скасована. settings.convert_confirm=Перетворити репозиторій settings.convert_succeed=Репозиторій успішно перетворений в звичайний. +settings.convert_fork=Перетворити на звичайний репозиторій +settings.convert_fork_desc=Ви можете перетворити цей форк на звичайний репозиторій. Цю дію неможливо скасувати. +settings.convert_fork_notices_1=Ця операція перетворить форк на звичайний репозиторій та не може бути скасованою. +settings.convert_fork_confirm=Перетворити репозиторій +settings.convert_fork_succeed=Цей форк успішно перетворено на звичайний репозиторій. settings.transfer=Передати новому власнику settings.transfer_desc=Передати репозиторій користувачеві або організації, де ви маєте права адміністратора. settings.transfer_notices_1=- Ви втратите доступ до репозиторія, якщо ви переведете його окремому користувачеві. @@ -1340,8 +1457,13 @@ settings.search_user_placeholder=Пошук користувача… settings.org_not_allowed_to_be_collaborator=Організації не можуть бути додані як співавтори. settings.change_team_access_not_allowed=Зміна доступу команди до репозитарію обмежена власником організації settings.team_not_in_organization=Команда та репозитарій мають привязки до різних організацій +settings.teams=Команди +settings.add_team=Додати Команду settings.add_team_duplicate=Команда вже має привязку до репозитарію settings.add_team_success=Команда отримала доступ до репозиторію. +settings.search_team=Знайти команду… +settings.change_team_permission_tip=Дозволи команди встановлюються на сторінці налаштувань команди та не можуть бути заданими для кожного з репозиторіїв окремо +settings.delete_team_tip=Ця команда має доступ до всіх репозиторіїв та не може бути видалена settings.remove_team_success=Доступ команди до репозиторію видалений. settings.add_webhook=Додати веб-хук settings.add_webhook.invalid_channel_name=Назва каналу Webhook не може бути порожньою і не може містити лише символ #. @@ -1432,6 +1554,7 @@ settings.slack_channel=Канал settings.add_discord_hook_desc=Інтеграція Discord у ваш репозиторії. settings.add_dingtalk_hook_desc=Інтеграція Dingtalk у ваш репозиторії. settings.add_telegram_hook_desc=Інтегруйте Telegram у своє сховище. +settings.add_matrix_hook_desc=Інтегрувати Matrix з вашим репозиторієм. settings.add_msteams_hook_desc=Інтегруйте Microsoft Teams у своє сховище. settings.add_feishu_hook_desc=Інтеграція Feishu у ваш репозиторії. settings.deploy_keys=Ключі для розгортування @@ -1462,6 +1585,7 @@ 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 в цю гілку: @@ -1471,6 +1595,7 @@ 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=Дозволити об'єднання запитів на злиття лише із достатньою кількістю позитивних рецензій. @@ -1481,6 +1606,9 @@ settings.protect_approvals_whitelist_teams=Білий список команд settings.dismiss_stale_approvals=Відхилити застарілі погодження settings.dismiss_stale_approvals_desc=Коли нові коміти що змінюють вміст пулл-запиту відправляються в гілку, старі погодження будуть відхилені. settings.require_signed_commits=Потрібно підписані коміти +settings.require_signed_commits_desc=Відхиляти push до цієї гілки, якщо вони не підписані або підпис неможливо перевірити. +settings.protect_protected_file_patterns=Шаблони захищених файлів (розділені крапками з комою '\;'): +settings.protect_protected_file_patterns_desc=Захищені файли, які заборонено змінювати напряму, навіть якщо користувач має дозвіл додавати, редагувати чи видаляти файли у цій гілці. Декілька шаблонів можуть бути розділеними за допомогою крапки з комою ('\;'). github.com/gobwas/glob надає документацію щодо синтаксису шаблонів. Приклади: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Увімкнути захист settings.delete_protected_branch=Вимкнути захист settings.update_protect_branch_success=Налаштування захисту гілки '%s' були успішно змінені. @@ -1489,6 +1617,8 @@ settings.protected_branch_deletion=Відключити захист гілки settings.protected_branch_deletion_desc=Будь-який користувач з дозволами на запис зможе виконувати push в цю гілку. Ви впевнені? settings.block_rejected_reviews=Блокувати злиття при відкидаючих рецензіях settings.block_rejected_reviews_desc=Злиття буде недоступним, якщо є запит змін від офіційних рецензентів, навіть за наявності достатньої кількості схвалень. +settings.block_outdated_branch=Блокувати злиття, якщо запит на злиття застарів +settings.block_outdated_branch_desc=Злиття буде неможливим, коли головна гілка позаду основної. settings.default_branch_desc=Головна гілка є 'базовою' для вашого репозиторія, на яку за замовчуванням спрямовані всі запити на злиття і яка є обличчям вашого репозиторія. Перше, що побачить відвідувач - це зміст головної гілки. Виберіть її з уже існуючих: settings.choose_branch=Оберіть гілку… settings.no_protected_branch=Немає захищених гілок. @@ -1577,6 +1707,7 @@ diff.review.placeholder=Рецензійований коментарій diff.review.comment=Коментар diff.review.approve=Затвердити diff.review.reject=Запит змін +diff.committed_by=зафіксовано releases.desc=Відслідковувати версії проекту (релізи) та завантаження. release.releases=Релізи @@ -1585,6 +1716,8 @@ release.draft=Чернетка release.prerelease=Пре-реліз release.stable=Стабільний release.edit=редагувати +release.ahead.commits=%d коміт(ів) +release.ahead.target=до %s з моменту цього випуску release.source_code=Код release.new_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту. release.edit_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту. @@ -1628,6 +1761,7 @@ branch.deleted_by=Видалено %s branch.restore_success=Гілку "%s" відновлено. branch.restore_failed=Не вдалося відновити гілку '%s'. branch.protected_deletion_failed=Гілка '%s' захищена. Її не можна видалити. +branch.default_deletion_failed=Гілка '%s' є основною. Її неможливо видалити. branch.restore=Відновити гілку '%s' branch.download=Завантажити гілку '%s' branch.included_desc=Ця гілка є частиною типової гілки @@ -1688,6 +1822,7 @@ settings.delete_org_title=Видалити організацію settings.delete_org_desc=Ця організація буде безповоротно видалена. Продовжити? settings.hooks_desc=Додайте webhooks, який буде викликатися для всіх репозиторіїв якими володіє ця організація. +settings.labels_desc=Додайте мітки, які можуть використовуватися для всіх репозиторіїв цієї органцізації. members.membership_visibility=Видимість учасника: members.public=Показувати @@ -1769,10 +1904,33 @@ dashboard.operation_switch=Перемкнути dashboard.operation_run=Запустити dashboard.clean_unbind_oauth=Очистити список незавершених авторизацій OAuth dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки OAuth були видалені. +dashboard.task.started=Запущено завдання: %[1]s +dashboard.task.process=Завдання: %[1]s +dashboard.task.cancelled=Завдання: %[1]s скасовано: %[3]s +dashboard.task.error=Помилка у завданні: %[1]:%[3]s +dashboard.task.finished=Завершилося завдання, яке запустив %[2]s: %[1]s +dashboard.task.unknown=Невідоме завдання: %[1]s +dashboard.cron.started=Запущено Cron: %[1]s +dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %s скасовано: %[3]s +dashboard.cron.error=Помилка в Cron: %s: %[3]s +dashboard.cron.finished=Cron: %[1]s завершено +dashboard.delete_inactive_accounts=Видалити всі неактивовані облікові записи +dashboard.delete_inactive_accounts.started=Запущено завдання видалення всі неактивованих облікових записів. dashboard.delete_repo_archives=Видалити всі архіви репозиторіїв +dashboard.delete_repo_archives.started=Запущено завдання видалення всіх архівів репозиторіїв. dashboard.delete_missing_repos=Видалити всі записи про репозиторії з відсутніми файлами Git +dashboard.delete_missing_repos.started=Запущено завдання видалення всіх репозиторіїв, в яких відсутні файли Git. dashboard.delete_generated_repository_avatars=Видалити репозиторій з згенерованими аватарами +dashboard.update_mirrors=Оновити дзеркала +dashboard.repo_health_check=Перевірка стану всіх репозиторіїв +dashboard.check_repo_stats=Перевірити статистику всіх репозиторіїв +dashboard.archive_cleanup=Видалити старі архіви репозиторіїв +dashboard.deleted_branches_cleanup=Прибрати видалені гілки +dashboard.update_migration_poster_id=Оновити мігровані ID авторів dashboard.git_gc_repos=Виконати очистку сміття для всіх репозиторіїв +dashboard.resync_all_sshkeys=Оновити файл '.ssh/authorized_keys' з SSH ключами Gitea. +dashboard.resync_all_sshkeys.desc=(Не потрібне при використанні вбудованого сервера SSH.) dashboard.resync_all_hooks=Пересинхронізувати перед-прийнятні, оновлюючі та пост-прийнятні хуки в усіх репозиторіях. dashboard.reinit_missing_repos=Переініціалізувати усі репозитрії git-файли яких втрачено dashboard.sync_external_users=Синхронізувати дані зовнішніх користувачів @@ -1813,6 +1971,7 @@ users.full_name=Повне ім'я users.activated=Активовано users.admin=Адміністратор users.restricted=Обмежено +users.2fa=2FA users.repos=Репозиторії users.created=Створено users.last_login=Останній вхід @@ -2049,6 +2208,8 @@ config.mailer_user=Користувач config.mailer_use_sendmail=Використовувати Sendmail config.mailer_sendmail_path=Шлях до Sendmail config.mailer_sendmail_args=Додаткові аргументи до Sendmail +config.mailer_sendmail_timeout=Тайм-аут Sendmail +config.test_email_placeholder=Адреса електронної пошти (наприклад, test@example.com) config.send_test_mail=Відправити тестового листа config.test_mail_failed=Не вдалося відправити тестовий лист на «%s»: %v config.test_mail_sent=Тестового листа було відправлено до '%s'. @@ -2066,7 +2227,6 @@ config.session_config=Конфігурація сесії config.session_provider=Провайдер сесії config.provider_config=Конфігурація постачальника config.cookie_name=Ім'я файлу cookie -config.enable_set_cookie=Увімкнути встановлення cookie config.gc_interval_time=Інтервал запуску збирача сміття (GC) config.session_life_time=Час життя сесії config.https_only=Тільки HTTPS @@ -2178,6 +2338,7 @@ notices.delete_selected=Видалити обране notices.delete_all=Видалити усі cповіщення notices.type=Тип notices.type_1=Репозиторій +notices.type_2=Завдання notices.desc=Опис notices.op=Оп. notices.delete_success=Сповіщення системи були видалені. @@ -2199,6 +2360,7 @@ transfer_repo=перенесено репозиторій %s у %[2]s в %[3]s delete_tag=видалено мітку %[2]s з %[3]s delete_branch=видалено гілку %[2]s з %[3]s +compare_branch=Порівняти compare_commits=Порівняти %d комітів compare_commits_general=Порівняти коміти mirror_sync_push=синхронізовано коміти %[3]s в %[4]s із дзеркала @@ -2206,6 +2368,7 @@ mirror_sync_create=синхронізовано нове посилання %[2]s
на %[3]s із дзеркала approve_pull_request=`схвалив %s#%[2]s` reject_pull_request=`запропонував зміни до %s#%[2]s` +publish_release=`опублікував випуск "%[4]s" з %[3]s` [tool] ago=%s тому diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 7592043a09c3..0fe7fa967a55 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -21,10 +21,12 @@ signed_in_as=已登录用户 enable_javascript=使用 JavaScript能使本网站更好的工作。 toc=目录 licenses=许可证 +return_to_gitea=返回 Gitea username=用户名 email=电子邮件地址 password=密码 +access_token=访问令牌(Access Token) re_type=重新输入密码 captcha=验证码 twofa=两步验证 @@ -142,7 +144,7 @@ ssh_port=SSH 服务端口 ssh_port_helper=SSH 服务器的端口号,为空则禁用它。 http_port=HTTP 服务端口 http_port_helper=Giteas web 服务器将侦听的端口号。 -app_url=Gitea 基本 URL。 +app_url=基础URL app_url_helper=用于 HTTP (S) 克隆和电子邮件通知的基本地址。 log_root_path=日志路径 log_root_path_helper=日志文件将写入此目录。 @@ -297,6 +299,8 @@ authorize_title=授权 %s 访问您的帐户? authorization_failed=授权失败 authorization_failed_desc=授权失败,这是一个无效的请求。请联系尝试授权应用的管理员。 sspi_auth_failed=SSPI 认证失败 +password_pwned=此密码出现在 被盗密码 列表上并且曾经被公开。 请使用另一个密码再试一次。 +password_pwned_err=无法完成对 HaveIBeenPwned 的请求 [mail] activate_account=请激活您的帐户 @@ -351,6 +355,10 @@ lang_select_error=从列表中选出语言 username_been_taken=用户名已被使用。 repo_name_been_taken=仓库名称已被使用。 +repository_files_already_exist=此仓库已存在文件。请联系系统管理员。 +repository_files_already_exist.adopt=此仓库已存在文件,只能被收录。 +repository_files_already_exist.delete=此仓库已存在文件,必须先删除他们。 +repository_files_already_exist.adopt_or_delete=此仓库已存在文件,要么删除他们,要么收录他们。 visit_rate_limit=远程访问达到速度限制。 2fa_auth_required=远程访问需要双重验证。 org_name_been_taken=组织名称已被使用。 @@ -369,11 +377,12 @@ enterred_invalid_owner_name=新的所有者名称无效。 enterred_invalid_password=输入的密码不正确 user_not_exist=该用户名不存在 team_not_exist=团队不存在 -last_org_owner=您不能从 "所有者" 团队中删除最后一个用户。在任何给定的团队中必须至少有一个所有者。 +last_org_owner=您不能从 "所有者" 团队中删除最后一个用户。组织中必须至少有一个所有者。 cannot_add_org_to_team=组织不能被加入到团队中。 invalid_ssh_key=无法验证您的 SSH 密钥: %s invalid_gpg_key=无法验证您的 GPG 密钥: %s +invalid_ssh_principal=无效的规则: %s unable_verify_ssh_key=无法验证SSH密钥,再次检查是否有误。 auth_failed=授权验证失败:%v @@ -421,6 +430,7 @@ uid=用户 ID u2f=安全密钥 public_profile=公开信息 +biography_placeholder=关于你自己 profile_desc=您的电子邮件地址将用于通知和其他操作。 password_username_disabled=不允许非本地用户更改他们的用户名。更多详情请联系您的系统管理员。 full_name=自定义名称 @@ -491,9 +501,11 @@ keep_email_private_popup=您的电子邮件地址将对其他用户隐藏。 openid_desc=OpenID 让你可以将认证转发到外部服务。 manage_ssh_keys=管理 SSH 密钥 +manage_ssh_principals=管理SSH证书规则 manage_gpg_keys=管理 GPG 密钥 add_key=增加密钥 ssh_desc=这些 SSH 公钥已经关联到你的账号。相应的私钥拥有完全操作你的仓库的权限。 +principal_desc=这些SSH证书规则已关联到你的账号将允许完全访问你的所有仓库。 gpg_desc=这些 GPG 公钥已经关联到你的账号。请妥善保管你的私钥因为他们将被用于认证提交。 ssh_helper=需要帮助? 请查看有关 如何生成 SSH 密钥常见 SSH 问题 寻找答案。 gpg_helper=需要帮助吗?看一看 GitHub 关于GPG 的指导。 @@ -501,23 +513,30 @@ add_new_key=增加 SSH 密钥 add_new_gpg_key=添加的 GPG 密钥 key_content_ssh_placeholder=以 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384' 或 'ecdsa-sha2-nistp521' 开头 key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 开头 +add_new_principal=添加规则 ssh_key_been_used=此 SSH 密钥已添加到服务器。 ssh_key_name_used=使用相同名称的SSH公钥已经存在! +ssh_principal_been_used=此规则已经加入到了服务器。 gpg_key_id_used=使用相同名称的GPG公钥已经存在! gpg_no_key_email_found=此 GPG 公钥没有使用任何你的电子邮箱地址。 subkeys=子项 key_id=键ID key_name=密钥名称 key_content=密钥内容 +principal_content=内容 add_key_success=您的 SSH 密钥 '%s' 添加成功。 add_gpg_key_success=您的 GPG 密钥 '%s' 添加成功。 +add_principal_success=SSH证书规则 '%s' 已经被加过了。 delete_key=删除 ssh_key_deletion=删除 SSH 密钥 gpg_key_deletion=删除 GPG 密钥 +ssh_principal_deletion=删除 SSH 证书规则 ssh_key_deletion_desc=删除 SSH 公钥将取消对应的私钥对您的 Gitea 帐户的访问权限。继续? gpg_key_deletion_desc=删除 GPG 公钥将无法认知使用对应私钥签名的提交,继续? +ssh_principal_deletion_desc=删除此 SSH 证书规则将取消它对您的账户的访问权限。继续? ssh_key_deletion_success=GPG 密钥已被删除。 gpg_key_deletion_success=GPG 密钥已被删除。 +ssh_principal_deletion_success=此规则删除成功。 add_on=增加于 valid_until=有效期至 valid_forever=永久有效 @@ -527,10 +546,10 @@ can_read_info=读取 can_write_info=写入 key_state_desc=7 天内使用过该密钥 token_state_desc=7 天内使用过该密钥 +principal_state_desc=7 天内使用过该规则 show_openid=在个人信息上显示 hide_openid=在个人信息上隐藏 ssh_disabled=SSH 被禁用 - manage_social=管理关联社交帐户 social_desc=这些外部账号已经绑定到你的Gitea账号。请确认这些账号,因为这些账号可以用来登录系统。 unbind=取消链接 @@ -676,6 +695,15 @@ pick_reaction=选择你的表情 reactions_more=再加载 %d unit_disabled=站点管理员已禁用此仓库单元。 language_other=其它 +adopt_search=输入用户名以搜索未被收录的仓库... (留空以查找全部) +adopt_preexisting_label=收录文件 +adopt_preexisting=收录已存在的文件 +adopt_preexisting_content=从 %s 创建仓库 +adopt_preexisting_success=从 %s 收录文件并创建仓库成功 +delete_preexisting_label=刪除 +delete_preexisting=删除已存在的文件 +delete_preexisting_content=删除 %s 中的文件 +delete_preexisting_success=删除 %s 中未收录的文件 desc.private=私有库 desc.public=公开 @@ -705,15 +733,17 @@ form.name_reserved=仓库名称 '%s' 是被保留的。 form.name_pattern_not_allowed=仓库名称中不允许使用模式 "%s"。 need_auth=需要授权验证 -migrate_type=迁移类型 -migrate_type_helper=该仓库将是一个 镜像 -migrate_type_helper_disabled=您的站点管理员已禁用创建新镜像。 +migrate_options=迁移选项 +migrate_service=迁移服务 +migrate_options_mirror_helper=该仓库将是一个 镜像 +migrate_options_mirror_disabled=您的站点管理员已禁用创建新镜像。 migrate_items=迁移项目 migrate_items_wiki=百科 migrate_items_milestones=里程碑 migrate_items_labels=标签 migrate_items_issues=工单 migrate_items_pullrequests=合并请求 +migrate_items_merge_requests=合并请求 migrate_items_releases=版本发布 migrate_repo=迁移仓库 migrate.clone_address=从 URL 迁移/克隆 @@ -723,17 +753,24 @@ 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 迁移并且输入了用户名时,迁移选项将会显示。 +migrate.migrate_items_options=需要访问令牌来迁移额外的内容 migrated_from=从 %[2]s 迁移 migrated_from_fake=从 %[1]s 迁移成功 +migrate.migrate=从 %s 迁移 migrate.migrating=正在从 %s 迁移... migrate.migrating_failed=从 %s 迁移失败。 +migrate.github.description=从 Github.com 或者 Github Enterprise 迁移数据 +migrate.git.description=从 Git 迁移数据 +migrate.gitlab.description=从 GitLab.com 或者 自部署 GitLab 迁移数据 +migrate.gitea.description=从 Gitea.com 或 自托管 Gitea 服务器迁移数据。 mirror_from=镜像自地址 forked_from=派生自 generated_from=生成自 fork_from_self=无法派生已经拥有的仓库! fork_guest_user=登录并 派生 这个仓库。 +watch_guest_user=请登录后再关注此仓库 +star_guest_user=请登录后再点赞此仓库 copy_link=复制链接 copy_link_success=已复制链接 copy_link_error=请按下 ⌘-C 或 Ctrl-C 复制 @@ -756,6 +793,7 @@ code=代码 code.desc=查看源码、文件、提交和分支。 branch=分支 tree=目录树 +clear_ref=`清除当前引用` filter_branch_and_tag=过滤分支或标签 branches=分支列表 tags=标签列表 @@ -831,9 +869,7 @@ 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.push_rejected_no_message=此更改被服务器拒绝并且没有反馈消息。请检查 githooks。 -editor.push_rejected=此更改被服务器拒绝,消息如下:
%s
请检查 githooks。 editor.add_subdir=添加目录 editor.unable_to_upload_files=上传文件至 '%s' 时发生错误:%v editor.upload_file_is_locked=文件%s被 %s 锁定。 @@ -924,6 +960,9 @@ issues.new.clear_assignees=取消指派成员 issues.new.no_assignees=未指派成员 issues.new.no_reviewers=无审核者 issues.new.add_reviewer_title=请求审核 +issues.choose.get_started=开始 +issues.choose.blank=默认模板 +issues.choose.blank_about=从默认模板创建一个工单。 issues.no_ref=分支/标记未指定 issues.create=创建工单 issues.new_label=创建标签 @@ -988,7 +1027,7 @@ issues.action_assignee_no_select=未指派 issues.opened_by=由 %[3]s 于 %[1]s创建 pulls.merged_by=由 %[3]s 于 %[1]s 合并 pulls.merged_by_fake=由 %[2]s 于 %[1]s 合并 -issues.closed_by=按 %[3]s 关闭%[1]s +issues.closed_by=%[3]s 关闭于 %[1]s issues.opened_by_fake=由 %[2]s 于 %[1]s创建 issues.closed_by_fake=通过 %[2]s 关闭 %[1]s issues.previous=上一页 @@ -1023,6 +1062,7 @@ issues.poster=发布者 issues.collaborator=协作者 issues.owner=所有者 issues.re_request_review=再次请求审核 +issues.is_stale=此评审之后代码有更新 issues.remove_request_review=移除审核请求 issues.remove_request_review_block=无法移除审核请求 issues.sign_in_require_desc=登录 并参与到对话中。 @@ -1094,7 +1134,7 @@ issues.error_modifying_due_date=未能修改到期时间。 issues.error_removing_due_date=未能删除到期时间。 issues.push_commit_1=已于 %[2]s 推送了 %[1]d 提交 issues.push_commits_n=已于 %[2]s 推送了 %[1]d 提交 -issues.force_push_codes="强制推送%[1]s %[2]s%[4]s %[6]s" +issues.force_push_codes=`%[6]s 强制从 %[2]s 推送 %[1]s 到 %[4]s` issues.due_date_form=yyyy年mm月dd日 issues.due_date_form_add=添加到期时间 issues.due_date_form_edit=编辑 @@ -1190,6 +1230,8 @@ pulls.required_status_check_administrator=作为管理员,您仍可合并此 pulls.blocked_by_approvals=此合并请求没有通过审批。已获取审批数%d个,共需要审批数%d个。 pulls.blocked_by_rejection=此合并请求有官方审核员请求的更改。 pulls.blocked_by_outdated_branch=此合并请求因过期而被阻止。 +pulls.blocked_by_changed_protected_files_1=此合并请求被阻止因为修改了被保护的文件: +pulls.blocked_by_changed_protected_files_n=此合并请求被阻止因为修改了被保护的文件: pulls.can_auto_merge_desc=该合并请求可以进行自动合并操作。 pulls.cannot_auto_merge_desc=该合并请求存在冲突,无法进行自动合并操作。 pulls.cannot_auto_merge_helper=手动合并解决此冲突 @@ -1213,11 +1255,9 @@ pulls.rebase_merge_commit_pull_request=变基合并 (--no-ff) pulls.squash_merge_pull_request=压缩提交并合并 pulls.require_signed_wont_sign=分支需要签名的提交,但这个合并将不会被签名 pulls.invalid_merge_option=你可以在此合并请求中使用合并选项。 -pulls.merge_conflict=合并失败:合并时发生冲突:%[1]s
[2]
提示:尝试不同的合并策略 -pulls.rebase_conflict=合并失败:Rebase合并时发生冲突:%[1]s
%[2]s
%[3]s
提示:尝试不同的合并策略 +; %[2]s
%[3]s
pulls.unrelated_histories=合并失败:两个分支没有共同历史。提示:尝试不同的策略 pulls.merge_out_of_date=合并失败:在生成合并时,主分支已更新。提示:再试一次。 -pulls.push_rejected=合并失败:这个推送被拒绝并收到以下消息:
%s
检查这个仓库的 githooks pulls.push_rejected_no_message=合并失败:这个推送被拒绝但没有远程消息。
检查这个仓库的 githooks pulls.open_unmerged_pull_exists=`您不能执行重新打开操作, 因为已经存在相同的合并请求 (#%d)。` pulls.status_checking=一些检测仍在等待运行 @@ -1225,6 +1265,8 @@ pulls.status_checks_success=所有检测均成功 pulls.status_checks_warning=一些检查报告了警告 pulls.status_checks_failure=一些检查失败了 pulls.status_checks_error=一些检查报告了错误 +pulls.status_checks_requested=必须 +pulls.status_checks_details=详情 pulls.update_branch=更新分支 pulls.update_branch_success=分支更新成功 pulls.update_not_allowed=您无权更新分支 @@ -1236,6 +1278,7 @@ milestones.new=新的里程碑 milestones.open_tab=%d 开启中 milestones.close_tab=%d 已关闭 milestones.closed=于 %s关闭 +milestones.update_ago=更新于 %s 前 milestones.no_due_date=暂无截止日期 milestones.open=开启中 milestones.close=关闭 @@ -1275,6 +1318,7 @@ signing.wont_sign.basesigned=合并将不会被签名,因为父提交没有签 signing.wont_sign.headsigned=合并将不会被签名,因为最新提交没有签名 signing.wont_sign.commitssigned=合并将不会被签名,因为所有相关的提交都没有签名 signing.wont_sign.approved=合并将不会被签名,因为合并请求未被批准 +signing.wont_sign.not_signed_in=您还没有登录。 ext_wiki=外部百科 ext_wiki.desc=链接到外部 wiki。 @@ -1441,6 +1485,19 @@ settings.transfer_desc=您可以将仓库转移至您拥有管理员权限的帐 settings.transfer_notices_1=-如果将其传输给单个用户, 您将失去对存储库的访问权限。 settings.transfer_notices_2=-如果将其转移到您 (共同) 拥有的组织,您可以继续访问该仓库。 settings.transfer_form_title=输入仓库名称以做确认: +settings.signing_settings=签名验证设置 +settings.trust_model=签名信任模型 +settings.trust_model.default=默认信任模型 +settings.trust_model.default.desc=为此安装使用默认仓库信任模型。 +settings.trust_model.collaborator=协作者 +settings.trust_model.collaborator.long=协作者:信任协作者的签名 +settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为“可信” - 不管他们是否是提交者。否则,如果签名匹配了提交者,有效的签名将被标记为“不可信”。 +settings.trust_model.committer=提交者 +settings.trust_model.committer.long=提交者: 信任与提交者匹配的签名 (匹配GitHub 并强制Gitea签名的提交者将Gitea作为提交者) +settings.trust_model.committer.desc=此仓库中匹配为提交者的有效签名将仅被标记为“可信”。否则,将会标记为未匹配。这将强制 Gitea 在签名提交上将实际提交者加上 Co-Authored-By: 和 Co-Committed-By: 。默认的Gitea密钥必须匹配Gitea用户。 +settings.trust_model.collaboratorcommitter=协作者+提交者 +settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名 +settings.trust_model.collaboratorcommitter.desc=如果匹配为提交者,此仓库中协作者的有效签名将被标记为“可信”。否则,如果签名匹配了提交者或者未匹配,有效的签名将被标记为“不可信”。这将强制 Gitea 在签名提交上将实际提交者加上 Co-Authored-By: 和 Co-Committed-By: 。默认的Gitea密钥必须匹配Gitea用户。 settings.wiki_delete=删除百科数据 settings.wiki_delete_desc=删除仓库百科数据是永久性的,无法撤消。 settings.wiki_delete_notices_1=- 这将永久删除和禁用 %s 的百科。 @@ -1720,6 +1777,7 @@ diff.review.comment=评论 diff.review.approve=通过 diff.review.reject=请求变更 diff.committed_by=提交者 +diff.protected=受保护的 releases.desc=跟踪项目版本和下载。 release.releases=版本发布 @@ -1820,7 +1878,9 @@ settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问 settings.visibility=可见性 settings.visibility.public=公开 settings.visibility.limited=受限 (仅对登录用户可见) +settings.visibility.limited_shortname=受限 settings.visibility.private=私有 (仅对组织成员可见) +settings.visibility.private_shortname=私有 settings.update_settings=更新组织设置 settings.update_setting_success=组织设置已更新。 @@ -1943,6 +2003,8 @@ dashboard.update_migration_poster_id=更新迁移的发表者ID dashboard.git_gc_repos=对仓库进行垃圾回收 dashboard.resync_all_sshkeys=使用 Gitea SSH 密钥更新'.ssh/authorized_keys' 文件。 dashboard.resync_all_sshkeys.desc=(内置的 SSH 服务器不需要。) +dashboard.resync_all_sshprincipals=使用 Gitea SSH 规则更新 '.ssh/authorized_principals' 文件。 +dashboard.resync_all_sshprincipals.desc=(内置的 SSH 服务器不需要。) dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子 dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录 dashboard.sync_external_users=同步外部用户数据 @@ -2004,7 +2066,7 @@ users.prohibit_login=禁用登录 users.is_admin=是管理员 users.is_restricted=受限 users.allow_git_hook=允许创建 Git 钩子 -users.allow_git_hook_tooltip=Git 钩子作为运行 Gitea 的操作系统用户执行,并且将拥有相同级别的主机访问权限 +users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行,将会拥有同样的主机访问权限。因此,拥有此特殊的Git 钩子权限将能够访问合修改所有的 Gitea 仓库或者Gitea的数据库。同时也能获得Gitea的管理员权限。 users.allow_import_local=允许导入本地仓库 users.allow_create_organization=允许创建组织 users.update_profile=更新帐户 @@ -2033,6 +2095,8 @@ orgs.members=成员 orgs.new_orga=创建新的组织 repos.repo_manage_panel=仓库管理 +repos.unadopted=未收录仓库 +repos.unadopted.no_more=找不到更多未被收录的仓库 repos.owner=所有者 repos.name=名称 repos.private=私有库 @@ -2082,6 +2146,11 @@ auths.filter=用户过滤规则 auths.admin_filter=管理员过滤规则 auths.restricted_filter=受限的过滤器 auths.restricted_filter_helper=留空则不将任何用户设置为受限。使用星号('*') 将所有不匹配管理过滤器的用户设置为受限用户。 +auths.verify_group_membership=在 LDAP 中验证群组成员 +auths.group_search_base=群组搜索基础 DN +auths.valid_groups_filter=有效的群组过滤器 +auths.group_attribute_list_users=包含用户列表的群组属性 +auths.user_attribute_in_group=群组中列出的用户属性 auths.ms_ad_sa=MS AD 搜索属性 auths.smtp_auth=SMTP 认证类型 auths.smtphost=SMTP 主机地址 @@ -2240,7 +2309,6 @@ config.session_config=Session 配置 config.session_provider=Session 提供者 config.provider_config=提供者配置 config.cookie_name=Cookie 名称 -config.enable_set_cookie=启用设置 Cookie config.gc_interval_time=GC 周期 config.session_life_time=Session 生命周期 config.https_only=仅限 HTTPS @@ -2382,7 +2450,7 @@ mirror_sync_create=从镜像同步了新的引用 %[2]s mirror_sync_delete=从镜像同步并从 %[3]s 删除了引用 %[2]s approve_pull_request=`同意了 %s#%[2]s` reject_pull_request=`建议变更 %s#%[2]s` -publish_release=`发布了 "%[4]" %[3]s` +publish_release=`发布了 "%[4]s" %[3]s` [tool] ago=%s前 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 0fdf3f2f779c..7851b6f25ecf 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -215,7 +215,6 @@ key_state_desc=該金鑰在 7 天內被使用過 token_state_desc=此 token 在過去七天內曾經被使用過 show_openid=在設定檔顯示 hide_openid=從設定檔隱藏 - manage_social=管理關聯社交帳戶 generate_new_token=生成新的令牌 @@ -262,8 +261,6 @@ forks=複製儲存庫 form.reach_limit_of_creation=您已經達到了儲存庫 %d 的上限。 form.name_reserved=儲存庫名稱 '%s' 是預留的。 -migrate_type=遷移類型 -migrate_type_helper=該儲存庫將是一個 鏡像 migrate_repo=遷移儲存庫 migrate.permission_denied=您並沒有導入本地儲存庫的權限。 migrate.failed=遷移失敗:%v @@ -314,7 +311,6 @@ editor.create_new_branch=建立 新的分支 為此提交和開 editor.cancel=取消 editor.branch_already_exists='%s' 已存在於此存儲庫。 editor.no_changes_to_show=沒有可以顯示的變更。 -editor.fail_to_update_file=上傳/建立檔案 '%s' 失敗, 錯誤訊息: %v editor.unable_to_upload_files=上傳檔案失敗到 '%s', 錯誤訊息: %v editor.upload_files_to_dir=上傳檔案到 '%s' @@ -422,6 +418,7 @@ pulls.merged=已合併 pulls.can_auto_merge_desc=這個拉請求可以自動合併。 pulls.merge_pull_request=合併請求 +; %[2]s
%[3]s
milestones.new=新的里程碑 milestones.open_tab=%d 開啟中 @@ -767,7 +764,6 @@ config.session_config=Session 設定 config.session_provider=Session 提供者 config.provider_config=提供者設定 config.cookie_name=Cookie 名稱 -config.enable_set_cookie=啟用設定 Cookie config.gc_interval_time=垃圾收集周期 config.session_life_time=Session 生命周期 config.https_only=僅限 HTTPS diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index a929759b79d5..ec864d1c1801 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1,5 +1,5 @@ home=首頁 -dashboard=控制面版 +dashboard=資訊主頁 explore=探索 help=說明 sign_in=登入 @@ -10,27 +10,40 @@ link_account=連結帳戶 register=註冊 website=網站 version=版本 +powered_by=技術提供: %s page=頁面 template=樣板 language=語言 -notifications=訊息 +notifications=通知 create_new=建立... -user_profile_and_more=設定檔和設置... -signed_in_as=已登入用戶 +user_profile_and_more=個人資料和設定... +signed_in_as=已登入 enable_javascript=本網站在啟用 JavaScript 的情況下可以運作的更好。 +toc=目錄 +licenses=授權條款 +return_to_gitea=返回 Gitea -username=用戶名稱 -email=電子郵件地址 +username=帳號 +email=電子信箱 password=密碼 +access_token=Access Token re_type=再次輸入密碼 captcha=驗證碼 twofa=兩步驟驗證 -twofa_scratch=兩步驟驗證備用碼 +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=請確認使用正確,加密的 (https://) URL。 +u2f_error_3=伺服器無法執行您的請求。 +u2f_error_4=此請求不允許使用這個安全金鑰。請確保該金鑰尚未註冊。 +u2f_error_5=在成功讀取金鑰之前已逾時,請重新載入以重試。 u2f_reload=重新載入 repository=儲存庫 @@ -39,49 +52,81 @@ mirror=鏡像 new_repo=新增儲存庫 new_migrate=遷移外部儲存庫 new_mirror=新鏡像 -new_fork=Fork 新的儲存庫 +new_fork=新增儲存庫 fork new_org=新增組織 +new_project=新增專案 +new_project_board=新增專案看板 manage_org=管理組織 admin_panel=網站管理 -account_settings=帳號設定 +account_settings=帳戶設定 settings=設定 -your_profile=個人訊息 -your_starred=標記星號 +your_profile=個人資料 +your_starred=已加星號 your_settings=設定 all=所有 sources=來源 mirrors=鏡像 -collaborative=協同者 -forks=複製列表 +collaborative=協作 +forks=Fork -activities=活動 +activities=動態 pull_requests=合併請求 issues=問題 +milestones=里程碑 cancel=取消 +save=儲存 +add=增加 +add_all=全部增加 +remove=移除 +remove_all=全部移除 +write=撰寫 +preview=預覽 +loading=載入中… +error404=您正嘗試訪問的頁面 不存在您尚未被授權 查看該頁面。 [error] +occurred=發生錯誤 +report_message=如果你確定這是一個 Gitea 的 bug,請去 GitHub 搜尋相關的問題,如果有需要你也可以開一個新的問題 [startpage] +install=安裝容易 +install_desc=簡單地用執行檔來架設平台,或是使用 Docker,你也可以從套件管理員安裝。 +platform=跨平台 +platform_desc=Gitea 可以在所有能編譯 Go 語言的平台上執行: Windows, macOS, Linux, ARM 等等。挑一個您喜歡的吧! +lightweight=輕量級 +lightweight_desc=一片便宜的 Raspberry Pi 就可以滿足 Gitea 的最低需求。節省您的機器資源! +license=開放原始碼 +license_desc=取得 code.gitea.io/gitea !成為一名貢獻者和我們一起讓 Gitea 更好,快點加入我們吧! [install] install=安裝頁面 -title=初始設定 -docker_helper=如果您正在使用 Docker 容器運行 Gitea,請務必先仔細閱讀 官方文件後再對本頁面進行填寫。 +title=初始組態 +docker_helper=如果您正在使用 Docker 容器執行 Gitea,請務必先仔細閱讀 官方文件後再對本頁面進行填寫。 +requite_db_desc=Gitea 必須搭配 MySQL、PostgreSQL、MSSQL 或 SQLite3 資料庫使用。 db_title=資料庫設定 db_type=資料庫類型 host=主機 -user=使用者名稱 +user=帳號 password=密碼 db_name=資料庫名稱 -db_schema=架構模式 +db_helper=MySQL 使用者注意:請使用 InnoDB 儲存引擎,如果您使用 utf8mb4,您必須使用 InnoDB 5.6 或更新版本。 +db_schema=Schema +db_schema_helper=留空則使用資料庫預設值("public")。 ssl_mode=SSL -path=資料庫文件路徑 +charset=編碼 +path=資料庫檔案路徑 +sqlite_helper=SQLite3 或 TiDB 資料庫的檔案路徑。
如果將 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_pattern_not_allowed=管理員帳號無效,該帳號是保留名稱 +err_admin_name_is_invalid=無效的管理員帳號 general_title=一般設定 app_name=網站標題 @@ -91,15 +136,15 @@ repo_path_helper=所有遠端 Git 儲存庫會儲存到此目錄。 lfs_path=Git LFS 根目錄 lfs_path_helper=以 Git LFS 儲存檔案時會被儲存在此目錄中。請留空以停用 LFS 功能。 run_user=以使用者名稱執行 -run_user_helper=輸入 Gitea 運行的作業系統使用者名稱。請注意, 此使用者必須具有對存儲庫根目錄的存取權限。 +run_user_helper=輸入 Gitea 執行的作業系統使用者名稱。請注意,此使用者必須擁有存儲庫根目錄的存取權限。 domain=SSH 伺服器域名 -domain_helper=用於 SSH 複製的域名或主機位置。 +domain_helper=用於 SSH Clone 的域名或主機位置。 ssh_port=SSH 伺服器埠 ssh_port_helper=SSH 伺服器使用的埠號,留空以停用此設定。 http_port=Gitea HTTP 埠 http_port_helper=Giteas web 伺服器將偵聽的埠號。 -app_url=Gitea 基本 URL。 -app_url_helper=用於 HTTP(S) 複製和電子郵件通知的基本地址。 +app_url=Gitea 基本 URL +app_url_helper=用於 HTTP(S) Clone 和電子郵件通知的基本網址。 log_root_path=日誌路徑 log_root_path_helper=日誌檔將寫入此目錄。 @@ -107,55 +152,58 @@ optional_title=可選設定 email_title=電子郵件設定 smtp_host=SMTP 主機 smtp_from=電子郵件寄件者 -smtp_from_helper=Gitea 將會使用的電子郵件地址,輸入一個普通的電子郵件地址或使用 "名稱" 格式。 +smtp_from_helper=Gitea 將會使用的電子信箱,輸入一個普通的電子信箱或使用 "名稱" 格式。 mailer_user=SMTP 帳號 mailer_password=SMTP 密碼 register_confirm=要求註冊時確認電子郵件 mail_notify=啟用郵件通知 -server_service_title=伺服器和其他服務設定 +server_service_title=伺服器和第三方服務設定 offline_mode=啟用本地模式 offline_mode_popup=停用其他服務並在本地提供所有資源。 -disable_gravatar=禁用 Gravatar 大頭貼 -disable_gravatar_popup=禁用 Gravatar 和其他大頭貼服務。除非使用者在本地上傳大頭貼, 否則將使用預設的大頭貼。 -federated_avatar_lookup=開啟聯合大頭貼 -federated_avatar_lookup_popup=開啟聯合頭像查詢並使用基於開放源碼的 libravatar 服務 +disable_gravatar=停用 Gravatar +disable_gravatar_popup=停用 Gravatar 和其他大頭貼服務。除非使用者在本地上傳大頭貼,否則將使用預設的大頭貼。 +federated_avatar_lookup=啟用 Federated Avatars +federated_avatar_lookup_popup=使用 Libravatar 以啟用 Federated Avatar 查詢服務 disable_registration=關閉註冊功能 -disable_registration_popup=關閉註冊功能,只有管理員可以新增帳號。 +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=管理員使用者名稱 +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=立即安裝 +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 +run_user_not_match=「以...執行」的使用者名稱不是目前的使用者名稱:%s -> %s save_config_failed=儲存設定失敗:%v invalid_admin_setting=管理員帳戶設定不正確:%v install_success=歡迎!非常感謝您選擇 Gitea,祝你一切順利。 invalid_log_root_path=日誌根目錄設定不正確: %v -default_keep_email_private=預設隱藏電子郵件地址 -default_keep_email_private_popup=預設隱藏新使用者的電子郵件地址。 +default_keep_email_private=預設隱藏電子信箱 +default_keep_email_private_popup=預設隱藏新使用者的電子信箱。 default_allow_create_organization=預設允許建立組織 default_allow_create_organization_popup=預設允許新使用者建立組織 default_enable_timetracking=預設啟用時間追蹤 default_enable_timetracking_popup=預設情況下啟用新存儲庫的時間跟蹤。 -no_reply_address=隱藏電子郵件域名 +no_reply_address=隱藏電子信箱域名 +no_reply_address_helper=作為隱藏電子信箱使用者的域名。例如,如果隱藏的電子信箱域名設定為「noreply.example.org」,帳號「joe」將以「joe@noreply.example.org」的身份登錄到 Git 中。 [home] -uname_holder=使用者名稱或電子郵件地址 +uname_holder=帳號或電子信箱 password_holder=密碼 -switch_dashboard_context=切換控制面版用戶 +switch_dashboard_context=切換資訊主頁帳戶 my_repos=儲存庫 show_more_repos=顯示更多儲存庫... collaborative_repos=參與協作的儲存庫 @@ -163,10 +211,19 @@ my_orgs=我的組織 my_mirrors=我的鏡像 view_home=訪問 %s search_repos=搜尋儲存庫... +filter=其他篩選條件 +show_archived=已封存 +show_both_archived_unarchived=顯示已封存和未封存 +show_only_archived=只顯示已封存 +show_only_unarchived=只顯示未封存 +show_private=私有 +show_both_private_public=顯示公開和私有 +show_only_private=只顯示私有 +show_only_public=只顯示公開 -issues.in_your_repos=屬於該用戶儲存庫的 +issues.in_your_repos=在您的儲存庫中 [explore] repos=儲存庫 @@ -177,45 +234,76 @@ code=程式碼 repo_no_results=沒有找到符合的儲存庫。 user_no_results=沒有找到符合的使用者。 org_no_results=沒有找到符合的組織。 +code_no_results=找不到符合您關鍵字的原始碼。 code_search_results=搜尋結果:'%s' +code_last_indexed_at=最後索引 %s [auth] -create_new_account=註冊帳號 -register_helper_msg=已經註冊?立即登錄! -social_register_helper_msg=已有帳號?立即連結! +create_new_account=註冊帳戶 +register_helper_msg=已經有帳戶了?立即登入! +social_register_helper_msg=已經有帳戶了?立即連結! disable_register_prompt=註冊功能已停用。 請聯繫您的網站管理員。 -remember_me=記住登錄 +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=啟用您的帳戶 -prohibit_login=禁止登錄 -prohibit_login_desc=您的帳號被禁止登入,請聯繫網站管理員 +account_activated=帳戶已啟用 +prohibit_login=禁止登入 +prohibit_login_desc=您的帳戶被禁止登入,請聯絡網站管理員 resent_limit_prompt=抱歉,您請求發送驗證電子郵件太過頻繁,請等待 3 分鐘後再試一次。 has_unconfirmed_mail=%s 您好,您有一封發送至( %s) 但未被確認的郵件。如果您未收到啟用郵件,或需要重新發送,請單擊下方的按鈕。 resend_mail=單擊此處重新發送確認郵件 -email_not_associate=此電子郵件地址未與任何帳戶連結 +email_not_associate=此電子信箱未與任何帳戶連結 +send_reset_mail=發送帳戶救援信 +reset_password=帳戶救援 invalid_code=您的確認代碼無效或已過期。 -non_local_account=非本機帳號無法透過 Gitea 的網頁介面更改密碼 +reset_password_helper=帳戶救援 +reset_password_wrong_user=你已經使用 %s 的帳戶登入,但帳戶救援連結是給 %s 的 +password_too_short=密碼長度不能少於 %d 個字! +non_local_account=非本地帳戶無法透過 Gitea 的網頁介面更改密碼。 verify=驗證 -scratch_code=備用碼 -use_scratch_code=使用備用碼 -twofa_scratch_used=你已經使用了你的備用碼。你將會被轉到兩步驟驗證設定頁面以便移除你已註冊設備或重新產生新的備用碼。 -twofa_passcode_incorrect=你的驗證碼不正確。如果您遺失設備,請使用您的備用碼登入。 -twofa_scratch_token_incorrect=您的備用碼不正確 +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。 +disable_forgot_password_mail=已停用帳戶救援功能。請與網站管理員聯絡。 +email_domain_blacklisted=您無法使用您的電子信箱註冊帳號。 +authorize_application=授權應用程式 +authorize_redirect_notice=如果您授權此應用程式,您將會被重新導向至 %s。 +authorize_application_created_by=此應用程式是由 %s 建立的。 +authorize_application_description=如果您允許,它將能夠讀取和修改您的所有帳戶資訊,包括私有儲存庫和組織。 +authorize_title=授權「%s」存取您的帳戶? +authorization_failed=授權失效 +sspi_auth_failed=SSPI 認證失敗 +password_pwned=您選擇的密碼已被列於被盜密碼清單中,該清單因公共資料外洩而暴露。請試試其它密碼。 +password_pwned_err=無法完成對 HaveIBeenPwned 的請求。 [mail] activate_account=請啟用您的帳戶 activate_email=請驗證您的郵箱地址 +reset_password=救援您的帳戶 register_success=註冊成功 register_notify=歡迎來到 Gitea @@ -225,7 +313,7 @@ no=取消操作 modify=更新 [form] -UserName=用戶名 +UserName=帳號 RepoName=儲存庫名稱 Email=郵箱地址 Password=密碼 @@ -244,6 +332,8 @@ CommitChoice=提交選擇 TreeName=檔案路徑 Content=內容 +SSPISeparatorReplacement=分隔符 +SSPIDefaultLanguage=預設語言 require_error=不能為空。 alpha_dash_error=`應該只包含英文字母、數字、破折號 ("-")、和底線 ("_") 字元。` @@ -257,22 +347,36 @@ include_error=必須包含子字符串 '%s'。 unknown_error=未知錯誤: captcha_incorrect=驗證碼不正確。 password_not_match=密碼錯誤。 +lang_select_error=從清單中選擇一個語言。 -username_been_taken=使用者名稱已被使用 +username_been_taken=帳號已被使用 repo_name_been_taken=儲存庫名稱已被使用。 +repository_files_already_exist=此儲存庫的檔案已存在,請聯絡系統管理有。 +repository_files_already_exist.adopt=此儲存庫的檔案已存在,並且只能被接管。 +repository_files_already_exist.delete=此儲存庫的檔案已存在,您必須刪除它們。 +repository_files_already_exist.adopt_or_delete=此儲存庫的檔案已存在,您可以接管或刪除它們。 org_name_been_taken=組織名稱已被使用。 team_name_been_taken=團隊名稱已被使用。 +team_no_units_error=請至少選擇一個儲存庫區域。 email_been_used=此電子信箱已被使用 openid_been_used=OpenID 位址 '%s' 已被使用。 -username_password_incorrect=使用者名稱或密碼不正確 +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=你不能從「Owners」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。 cannot_add_org_to_team=組織不能被新增為團隊成員。 invalid_ssh_key=無法驗證您的 SSH 密鑰:%s invalid_gpg_key=無法驗證您的 GPG 密鑰:%s +invalid_ssh_principal=無效的主體: %s unable_verify_ssh_key=無法驗證 SSH 密鑰; 請再次檢查是否有錯誤。 auth_failed=授權認證失敗:%v @@ -286,23 +390,29 @@ target_branch_not_exist=目標分支不存在 change_avatar=更改大頭貼... join_on=加入於 repositories=儲存庫列表 -activity=公開活動 -followers=關註者 -starred=已收藏 -following=關註中 -follow=關注 -unfollow=取消關注 - -form.name_reserved=用戶名 '%s' 是被保留的。 +activity=公開動態 +followers=追蹤者 +starred=已加星號 +projects=專案 +following=追蹤中 +follow=追蹤 +unfollow=取消追蹤 +heatmap.loading=正在載入熱點圖... +user_bio=個人簡介 +disabled_public_activity=這個使用者已對外隱藏動態 + +form.name_reserved=帳號「%s」是被保留的。 +form.name_pattern_not_allowed=帳號不可包含字元「%s」。 +form.name_chars_not_allowed=使用者名稱 '%s' 包含無效字元。 [settings] -profile=個人訊息 -account=帳號 +profile=個人資料 +account=帳戶 password=修改密碼 security=安全性 -avatar=頭像 +avatar=大頭貼 ssh_gpg_keys=SSH / GPG 金鑰 -social=社交帳號綁定 +social=社群帳戶 applications=應用程式 orgs=管理組織 repos=儲存庫 @@ -313,208 +423,377 @@ organization=組織 uid=用戶 ID u2f=安全密鑰 -public_profile=公開訊息 +public_profile=公開的個人資料 +biography_placeholder=告訴我們一些關於你的事 +profile_desc=您的電子信箱將被用於通知提醒和其他操作。 +password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。 full_name=自定義名稱 website=個人網站 location=所在地區 -update_profile=更新訊息 -update_profile_success=您的個人資料已被更新 -change_username=您的使用者名稱已更改。 -change_username_prompt=注意:使用者名更改也會更改您的帳戶的 URL。 +update_theme=更新佈景主題 +update_profile=更新個人資料 +update_profile_success=已更新您的個人資料。 +change_username=您的帳號已更改。 +change_username_prompt=注意:帳號更改也會更改您的帳戶的 URL。 continue=繼續操作 cancel=取消操作 language=語言 +ui=佈景主題 +privacy=隱私 +keep_activity_private=在個人資料頁面隱藏動態 +keep_activity_private_popup=讓動態只有你和管理員看得到 -lookup_avatar_by_mail=以電子郵件查找大頭貼 +lookup_avatar_by_mail=以電子信箱查詢大頭貼 federated_avatar_lookup=Federated Avatar 查詢 -enable_custom_avatar=啟動自定義頭像 -choose_new_avatar=選擇新的頭像 +enable_custom_avatar=使用自訂大頭貼 +choose_new_avatar=選擇新的大頭貼 update_avatar=更新大頭貼 -delete_current_avatar=刪除當前頭像 -uploaded_avatar_not_a_image=上傳的文件不是圖片 +delete_current_avatar=刪除目前的大頭貼 +uploaded_avatar_not_a_image=上傳的檔案不是圖片 +uploaded_avatar_is_too_big=上傳的檔案大小超過了最大限制 update_avatar_success=您的大頭貼已更新 change_password=更新密碼 -old_password=當前密碼 +old_password=目前的密碼 new_password=新的密碼 retype_new_password=重新輸入新的密碼 password_incorrect=輸入的密碼不正確! -change_password_success=您的密碼已更新。 從現在起使用您的新密碼登錄。 +change_password_success=您的密碼已更新。 從現在起使用您的新密碼登入。 password_change_disabled=非本機帳號無法透過 Gitea 的網頁介面更改密碼 -emails=電子郵件地址 -manage_emails=管理電子郵件地址 +emails=電子信箱 +manage_emails=管理電子信箱 +manage_themes=選擇預設佈景主題 manage_openid=管理 OpenID 位址 -email_desc=您的主要邮箱地址将被用于通知提醒和其它操作。 +email_desc=您的主要電子信箱將被用於通知提醒和其他操作。 +theme_desc=這將是您在整個網站上的預設佈景主題。 primary=主要 +activated=已啟用 +requires_activation=需要啟動 primary_email=設為主要 +activate_email=寄出啟用信 +activations_pending=等待啟用中 delete_email=移除 -email_deletion=移除電子郵件地址 -email_deletion_desc=電子郵件地址和相關資訊將從您的帳戶中刪除,此電子郵件地址所提交的 Git 將保持不變,繼續執行? -email_deletion_success=該電子郵件地址已被刪除 +email_deletion=移除電子信箱 +email_deletion_desc=電子信箱和相關資訊將從您的帳戶中刪除,由此電子信箱所提交的 Git 將保持不變,是否繼續? +email_deletion_success=該電子信箱已被刪除 +theme_update_success=已更新佈景主題。 +theme_update_error=選取的佈景主題不存在。 openid_deletion=移除 OpenID 位址 openid_deletion_desc=從您的帳戶刪除此 OpenID 位址將會無法使用它進行登入。你確定要繼續嗎? openid_deletion_success=該 OpenID 已被刪除 -add_new_email=新增電子郵件地址 +add_new_email=新增電子信箱 add_new_openid=新增 OpenID URI -add_email=新增電子郵件地址 +add_email=新增電子信箱 add_openid=新增 OpenID URI add_email_confirmation_sent=一封新的確認郵件已發送至 '%s',請檢查您的收件匣並在 %s 內確認您的電郵地址。 -add_email_success=該電子郵件地址已添加。 +add_email_success=已加入新的電子信箱。 +email_preference_set_success=已套用郵件偏好設定 add_openid_success=該 OpenID 已添加。 -keep_email_private=隱藏電子郵件地址 -keep_email_private_popup=您的電子郵件地址將對其他使用者隱藏。 +keep_email_private=隱藏電子信箱 +keep_email_private_popup=您的電子信箱將對其他使用者隱藏。 +openid_desc=OpenID 讓你可以授權認證給外部服務。 manage_ssh_keys=管理 SSH 金鑰 +manage_ssh_principals=管理 SSH 認證主體 manage_gpg_keys=管理 GPG 金鑰 add_key=增加金鑰 -ssh_helper=需要協助? 查詢GitHub的文件以 您自有SSH金鑰 or solve common problems you may encounter using SSH. +ssh_desc=這些 SSH 公鑰已關聯至你的帳戶。持有相對應的私鑰將擁有完全控制你的儲存庫的權限。 +principal_desc=這些 SSH 認證主體已關聯到您的帳戶並擁有完全存取您的儲存庫的權限。 +gpg_desc=這些 GPG 公鑰已經關聯到你的帳戶。請妥善保管你的私鑰因為他們將被用於認證提交。 +ssh_helper=需要協助嗎?建議可看看 GitHub 的文件以建立您的 SSH 金鑰或解決您使用 SSH 時碰到的常見問題。 gpg_helper=需要協助嗎?建議可看看 GitHub 的 about GPG 文件。 add_new_key=增加 SSH 金鑰 add_new_gpg_key=新增 GPG 金鑰 +key_content_ssh_placeholder=以 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521' 開頭 +key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 開頭 +add_new_principal=新增主體 +ssh_key_been_used=此 SSH 金鑰已添加到伺服器。 +ssh_key_name_used=已有相同名稱的 SSH 金鑰存在於您的帳戶。 +ssh_principal_been_used=此伺服器已有名為「%s」的主體。 +gpg_key_id_used=已存在具有相同 ID 的 GPG 金鑰。 +gpg_no_key_email_found=此 GPG 金鑰不適用於您的任何電子信箱。 subkeys=次金鑰 key_id=金鑰 ID key_name=金鑰名稱 -key_content=金鑰內容 +key_content=內容 +principal_content=內容 add_key_success=SSH 金鑰 "%s" 已被添加。 add_gpg_key_success=GPG 金鑰 "%s" 已被添加。 +add_principal_success=已新增 SSH 認證主體「%s」。 delete_key=移除 ssh_key_deletion=移除 SSH 金鑰 gpg_key_deletion=移除 GPG 金鑰 +ssh_principal_deletion=移除 SSH 認證主體 +ssh_key_deletion_desc=刪除 SSH 金鑰將撤銷其對您帳戶的存取權限。是否繼續? +gpg_key_deletion_desc=刪除 GPG 金鑰將取消驗證由其簽署的提交。是否繼續? +ssh_principal_deletion_desc=移除 SSH 認證主體將撤銷其對您帳戶的存取權限。是否繼續? ssh_key_deletion_success=SSH 金鑰已被移除。 gpg_key_deletion_success=GPG 金鑰已被移除。 +ssh_principal_deletion_success=已移除主體。 add_on=增加於 valid_until=有效期至 valid_forever=永遠有效 last_used=上次使用在 -no_activity=沒有最近活動 +no_activity=沒有近期動態 can_read_info=讀取 can_write_info=寫入 -key_state_desc=該金鑰在 7 天內被使用過 -token_state_desc=此 token 在過去七天內曾經被使用過 -show_openid=在設定檔顯示 -hide_openid=從設定檔隱藏 +key_state_desc=此金鑰在過去 7 天內曾被使用 +token_state_desc=此 Token 在過去 7 天內曾被使用 +principal_state_desc=此主體在過去 7 天內曾被使用 +show_openid=在個人資料顯示 +hide_openid=從個人資料隱藏 ssh_disabled=已停用 SSH - -manage_social=管理關聯社交帳戶 +manage_social=管理關聯的社群帳戶 +social_desc=這些社群帳戶已經連結到你的 Gitea 帳戶。請確保您認得這些帳戶,因為這些帳戶能用來登入寧的 Gitea 帳戶。 unbind=解除連結 - -manage_access_token=管理訪問權杖 -generate_new_token=生成新的令牌 -token_name=令牌名稱 -generate_token=生成令牌 -delete_token=删除令牌 -access_token_deletion=刪除訪問權杖 - - - -twofa_is_enrolled=您的帳號已經啟用兩步驟驗證。 -twofa_not_enrolled=您的帳號目前尚未啟用兩步驟驗證。 +unbind_success=已從您的 Gitea 帳戶解除連結社群帳戶。 + +manage_access_token=管理 Access Token +generate_new_token=產生新的 Token +tokens_desc=這些 Token 透過 Gitea API 獲得存取你帳戶的權限。 +new_token_desc=使用 Token 的應用程式擁有完全存取您帳戶的權限。 +token_name=Token 名稱 +generate_token=產生 Token +generate_token_success=已經產生新的 Token。請立刻複製它,因為他將不會再次顯示。 +generate_token_name_duplicate=應用程式名稱 %s 已被使用,請換一個試試。 +delete_token=刪除 +access_token_deletion=刪除 Access Token +access_token_deletion_desc=刪除 Token 後,使用此 Token 的應用程式將無法再存取您的帳戶。是否繼續? +delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。 + +manage_oauth2_applications=管理 OAuth2 應用程式 +edit_oauth2_application=編輯 OAuth2 應用程式 +oauth2_applications_desc=OAuth2 應用程式讓您的第三方應用程式安全地驗證此 Gitea 中的使用者。 +remove_oauth2_application=刪除 OAuth2 應用程式 +remove_oauth2_application_desc=刪除 OAuth2 應用程式將會撤銷所有已簽署的 access token 存取權。繼續嗎? +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=Web (例如 Node.JS, Tomacat, Go) +oauth2_type_native=原生應用程式 (Mobile, Desktop, Browser) +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 應用程式讓您的第三方應用程式可以存取此 Gitea 上的帳戶。 +oauth2_application_remove_description=刪除 OAuth2 應用會拒絕它存取此 Gitea 上已授權的帳戶。繼續嗎? + +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=您的帳戶已經啟用兩步驟驗證。 +twofa_not_enrolled=您的帳戶目前尚未啟用兩步驟驗證。 twofa_disable=停用兩步驟驗證 -twofa_scratch_token_regenerate=重新產生備用碼 +twofa_scratch_token_regenerate=重新產生備用驗證碼 +twofa_scratch_token_regenerated=您的備用驗證碼是 %s。請將它保存到一個安全的地方。 twofa_enroll=啟用兩步驟驗證 twofa_disable_note=如有需要,您可以停用兩步驟驗證。 -twofa_disable_desc=關閉兩步驟驗證會使您的帳號安全性降低,繼續執行? -regenerate_scratch_token_desc=如果您遺失了臨時令牌或已經使用它登錄,您可以在此處重置。 +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=這些外部帳號與您的 Gitea 帳號相關聯。 -account_links_not_available=目前沒有連結到您的 Gitea 帳號的外部帳號 -remove_account_link=刪除連結的帳號 -remove_account_link_success=已取消連結帳號。 +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=刪除當前帳戶 -delete_prompt=此動作將永久刪除您的帳號,而且無法復原。 +delete_account=刪除您的帳戶 +delete_prompt=此動作將永久刪除您的使用者帳戶,而且無法復原。 confirm_delete_account=確認刪除帳戶 -delete_account_title=刪除使用者帳號 -delete_account_desc=您是否確定要永久刪除此帳號? +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=可見度 -fork_repo=複製儲存庫 -fork_from=複製自 +repo_size=儲存庫大小 +template=範本 +template_select=選擇範本 +template_helper=將儲存庫設為範本 +template_description=儲存庫範本讓使用者可新增相同目錄結構、檔案以及設定的儲存庫。 +visibility=瀏覽權限 +visibility_description=只有組織擁有者或有權限的組織成員才能看到。 +visibility_helper=將儲存庫設為私有 +visibility_helper_forced=您的網站管理員強制新的存儲庫必需設定為私有。 +visibility_fork_helper=(修改本值將會影響所有 fork 儲存庫) +clone_helper=需要有關 Clone 的協助嗎?查看幫助 。 +fork_repo=Fork 儲存庫 +fork_from=Fork 自 +fork_visibility_helper=無法更改 fork 儲存庫的瀏覽權限。 +use_template=使用此範本 +generate_repo=產生儲存庫 +generate_from=產生自 repo_desc=儲存庫描述 repo_lang=儲存庫語言 repo_gitignore_helper=選擇 .gitignore 範本 -license=授權許可 -license_helper=請選擇授權許可文件 -readme=讀我 +issue_labels=問題標籤 +issue_labels_helper=選擇一個問題標籤集 +license=授權條款 +license_helper=請選擇授權條款檔案 +readme=讀我檔案 readme_helper=選擇讀我檔案範本。 -auto_init=初始化儲存庫(建立 .gitignore、授權許可文件和讀我檔案) +auto_init=初始化儲存庫(建立 .gitignore、授權條款和讀我檔案) create_repo=建立儲存庫 -default_branch=默認分支 +default_branch=預設分支 mirror_prune=裁減 -mirror_interval_invalid=鏡像周期無效 -mirror_address=由 URL 複製 +mirror_interval=鏡像間隔(有效時間單位為 'h'、'm'、's')。設為 0 以停用自動同步。 +mirror_interval_invalid=鏡像週期無效 +mirror_address=從 URL Clone +mirror_address_desc=在 Clone 授權資訊中填入必要的資料。 +mirror_address_url_invalid=提供的網址無效。請檢查您輸入的網址是否正確。 +mirror_address_protocol_invalid=提供的網址無效。只能從 http(s):// 或是 git:// 位址鏡像儲存庫。 mirror_last_synced=上次同步 watchers=關注者 -stargazers=稱讚者 -forks=複製儲存庫 +stargazers=占星術師 +forks=Fork pick_reaction=選擇你的表情反應 reactions_more=再多添加 %d個 - - - +unit_disabled=網站管理員已經停用這個儲存庫區域。 +language_other=其它 +adopt_search=輸入帳號以搜尋未接管的儲存庫... (留白以查詢全部) +adopt_preexisting_label=接管檔案 +adopt_preexisting=接管既有的檔案 +adopt_preexisting_content=從 %s 建立儲存庫 +adopt_preexisting_success=從 %s 接管檔案並建立儲存庫 +delete_preexisting_label=刪除 +delete_preexisting=刪除既有的檔案 +delete_preexisting_content=刪除 %s 中的檔案 +delete_preexisting_success=刪除 %s 中未接管的檔案 + +desc.private=私有 +desc.public=公開 +desc.private_template=私有範本 +desc.public_template=範本 +desc.internal=組織內部用 +desc.internal_template=組織內部範本 +desc.archived=已封存 + +template.items=範本項目 +template.git_content=Git 內容(預設分支) +template.git_hooks=Git Hook +template.webhooks=Webhook +template.topics=主題 +template.avatar=大頭貼 +template.issue_labels=問題標籤 +template.one_item=至少須選擇一個範本項目 +template.invalid=必須選擇一個儲存庫範本 + +archive.title=此存儲庫已封存。您可以查看檔案及 Clone 此存儲庫,但不能推送、建立問題及發出合併請求。 +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=該儲存庫將是一個鏡像 +need_auth=Clone 授權資訊 +migrate_options=遷移選項 +migrate_service=遷移服務 +migrate_options_mirror_helper=將此儲存庫設定為鏡像儲存庫 +migrate_options_mirror_disabled=您的網站管理員已經停用新增鏡像儲存庫的功能。 +migrate_items=遷移項目 +migrate_items_wiki=Wiki +migrate_items_milestones=里程碑 +migrate_items_labels=標籤 +migrate_items_issues=問題 +migrate_items_pullrequests=合併請求 +migrate_items_merge_requests=合併請求 +migrate_items_releases=版本發佈 migrate_repo=遷移儲存庫 +migrate.clone_address=從 URL 遷移 / Clone +migrate.clone_address_desc=現有存儲庫的 HTTP(S) 或 Git Clone URL migrate.clone_local_path=或者是本地端伺服器路徑 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=遷移其他項目需要 Access Token。 +migrated_from=已從 %[2]s 遷移 +migrated_from_fake=已從 %[1]s 遷移 +migrate.migrate=從 %s 遷移 +migrate.migrating=正在從 %s 遷移... +migrate.migrating_failed=從 %s 遷移失敗 +migrate.github.description=從 Github.com 或 Github Enterprise 遷移資料。 +migrate.git.description=從 Git 服務遷移或鏡像資料。 +migrate.gitlab.description=從 GitLab.com 或自託管的 Gitlab 伺服器遷移資料。 +migrate.gitea.description=從 Gitea.com 或自託管的 Gitea 伺服器遷移資料。 mirror_from=镜像来自 -forked_from=複製自 -fork_from_self=您無法複製您的儲存庫! +forked_from=fork 自 +generated_from=產生自 +fork_from_self=您無法 fork 已經擁有的儲存庫。 +fork_guest_user=登入並 fork 這個儲存庫。 +watch_guest_user=登入以查看此儲存庫。 +star_guest_user=登入以為此儲存庫加上星號。 copy_link=複製連結 copy_link_success=已複製連結 copy_link_error=請按下 ⌘-C 或 Ctrl-C 複製 copied=複製成功 unwatch=取消關注 watch=關注 -unstar=取消收藏 -star=收藏 -fork=複製 -download_archive=下载此儲存庫 +unstar=移除星號 +star=加上星號 +fork=Fork +download_archive=下載此儲存庫 no_desc=暫無描述 quick_guide=快速幫助 -clone_this_repo=複製此儲存庫 +clone_this_repo=Clone 此儲存庫 create_new_repo_command=從命令列建立新儲存庫。 push_exist_repo=從命令行推送已經建立的儲存庫 +empty_message=此儲存庫未包含任何內容。 code=程式碼 +code.desc=存取原始碼、檔案、提交和分支。 branch=分支 tree=目錄樹 filter_branch_and_tag=過濾分支或標籤 -branches=分支列表 +branches=分支 tags=標籤 issues=問題 pulls=合併請求 +project_board=專案 labels=標籤 +org_labels_desc=組織層級標籤可用於此組織下的所有存儲庫。 +org_labels_desc_manage=管理 milestones=里程碑 commits=提交歷史 @@ -526,38 +805,78 @@ file_view_raw=查看原始文件 file_permalink=永久連結 file_too_large=檔案太大,無法顯示。 video_not_supported_in_browser=您的瀏覽器不支援使用 HTML5 播放影片。 +audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 'audio' 標籤 stored_lfs=儲存到到 Git LFS +symbolic_link=符號連結 commit_graph=提交線圖 +commit_graph.monochrome=單色 +commit_graph.color=彩色 +blame=Blame +normal_view=標準檢視 +line=行 +lines=行 editor.new_file=新增文件 editor.upload_file=上傳文件 editor.edit_file=編輯文件 editor.preview_changes=預覽更改 +editor.cannot_edit_lfs_files=無法在 web 介面中編輯 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=如果你想要對這個檔案進行或提出修改,請先 fork 這個儲存庫。 editor.delete_this_file=刪除檔案 editor.file_delete_success=文件 %s 已刪除。 editor.name_your_file=命名您的檔案... +editor.filename_help=輸入名稱和斜線 ('/') 以新增目錄。在文字框的開始輸入倒退鍵以移除目錄。 editor.or=或 editor.cancel_lower=取消 -editor.commit_changes=提交更改嗎? +editor.commit_signed_changes=提交簽署過的變更 +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=為此提交建立新分支並提出合併請求。 +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.fail_to_update_file=更新/建立檔案「%s」失敗。 +editor.fail_to_update_file_summary=錯誤訊息: +editor.push_rejected_no_message=該變更被伺服器拒絕但未提供其它資訊。請檢查 Githook。 +editor.push_rejected=該變更被伺服器拒絕但。請檢查 Githook。 +editor.add_subdir=加入目錄 editor.unable_to_upload_files=上傳檔案失敗到 '%s', 錯誤訊息: %v +editor.upload_file_is_locked=檔案「%s」已被 %s 鎖定 editor.upload_files_to_dir=上傳檔案到 '%s' +editor.cannot_commit_to_protected_branch=無法提交到受保護的分支「%s」。 +editor.no_commit_to_branch=無法直接提交到分支因為: +editor.user_no_push_to_branch=使用者無法推送到分支 +editor.require_signed_commit=分支僅接受經簽署的提交 +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=作者 @@ -566,48 +885,114 @@ commits.date=提交日期 commits.older=更舊的提交 commits.newer=更新的提交 commits.signed_by=簽署人 +commits.signed_by_untrusted_user=由不信任的使用者簽署 +commits.signed_by_untrusted_user_unmatched=由未受信任的使用者簽署,他與提交者不相符 commits.gpg_key_id=GPG 金鑰 ID ext_issues=外部問題 - - -issues.new=建立問題 +ext_issues.desc=連結到外部問題追蹤器。 + +projects=專案 +projects.desc=在專案看板中管理問題和 pull。 +projects.create=建立專案 +projects.title=標題 +projects.new=新增專案 +projects.new_subheader=在同一個地方協調、追蹤和更新您的工作,使專案保持透明並按計畫進行。 +projects.create_success=已建立專案 '%s'。 +projects.deletion=刪除專案 +projects.deletion_desc=刪除專案會從所有相關的問題移除它。是否繼續? +projects.deletion_success=專案已被刪除。 +projects.edit=編輯專案 +projects.edit_subheader=專案可用來組織問題和追蹤進度。 +projects.modify=更新專案 +projects.edit_success=專案 '%s' 已被更新。 +projects.type.none=無 +projects.type.basic_kanban=基本看板 +projects.type.bug_triage=Bug 檢傷分類 +projects.template.desc=專案範本 +projects.template.desc_helper=選擇一個專案範本以開始 +projects.type.uncategorized=未分類 +projects.board.edit=編輯看板 +projects.board.edit_title=新看板名稱 +projects.board.new_title=新看板名稱 +projects.board.new_submit=送出 +projects.board.new=新增看板 +projects.board.delete=刪除看板 +projects.board.deletion_desc=刪除專案看板會將相關的問題移動到 '未分類'。是否繼續? +projects.open=開啟 +projects.close=關閉 + +issues.desc=管理錯誤報告、任務和里程碑。 +issues.filter_assignees=篩選成員 +issues.filter_milestones=篩選里程碑 +issues.filter_projects=篩選專案 +issues.filter_labels=篩選標籤 +issues.filter_reviewers=篩選審查者 +issues.new=新增問題 +issues.new.title_empty=標題不可為空 issues.new.labels=標籤 +issues.new.add_labels_title=套用標籤 issues.new.no_label=未選擇標籤 issues.new.clear_labels=清除已選取標籤 +issues.new.projects=專案 +issues.new.add_project_title=設定專案 +issues.new.clear_projects=清除已選取專案 +issues.new.no_projects=未選擇專案 +issues.new.open_projects=開放中的專案 +issues.new.closed_projects=已關閉的專案 +issues.new.no_items=沒有項目 issues.new.milestone=里程碑 +issues.new.add_milestone_title=設定里程碑 issues.new.no_milestone=未選擇里程碑 issues.new.clear_milestone=清除已選取里程碑 -issues.new.open_milestone=開啟中的里程碑 +issues.new.open_milestone=開放中的里程碑 issues.new.closed_milestone=已關閉的里程碑 -issues.new.assignees=指派成員 -issues.new.clear_assignees=取消指派成員 +issues.new.assignees=成員 +issues.new.add_assignees_title=指派成員 +issues.new.clear_assignees=清除成員 +issues.new.no_assignees=沒有成員 +issues.new.no_reviewers=沒有審核者 +issues.new.add_reviewer_title=請求審核 +issues.choose.get_started=開始 +issues.choose.blank=預設 +issues.choose.blank_about=從預設範本建立問題。 issues.no_ref=未指定分支或標籤 issues.create=建立問題 -issues.new_label=建立標籤 +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.label_templates.fail_to_load_file=載入標籤範本檔案 '%s' 失敗: %v -issues.add_milestone_at=`新增至%s 里程碑 %s` +issues.add_label_at=加入
%s
標籤 %s +issues.remove_label_at=移除
%s
標籤 %s +issues.add_milestone_at=`新增到 %s 里程碑 %s` +issues.add_project_at=`將此加入到 %s 專案 %s` issues.change_milestone_at=`%[3]s 修改了里程碑 %[1]s%[2]s` -issues.remove_milestone_at=`從里程碑 %[2]s 刪除 %[1]s` -issues.deleted_milestone=`(已刪除)` -issues.self_assign_at=將 %s 指派給自己 -issues.add_assignee_at=`被%s %s指派` +issues.change_project_at=`將專案從 %s 修改為 %s %s` +issues.remove_milestone_at=`從 %s 里程碑移除 %s` +issues.remove_project_at=`將此從 %s 專案中移除 %s` +issues.deleted_milestone=`(已刪除)` +issues.deleted_project=`(已刪除)` +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.open_tab=%d 個開放中 issues.close_tab=%d 個已關閉 -issues.filter_label=標籤篩選 +issues.filter_label=標籤 +issues.filter_label_exclude=`使用 alt + click/enter 來排除標籤`。 issues.filter_label_no_select=所有標籤 -issues.filter_milestone=里程碑篩選 +issues.filter_milestone=里程碑 issues.filter_milestone_no_select=所有里程碑 -issues.filter_assignee=指派人篩選 -issues.filter_assginee_no_select=所有指派成員 -issues.filter_type=類型篩選 +issues.filter_assignee=成員 +issues.filter_assginee_no_select=所有成員 +issues.filter_type=類型 issues.filter_type.all_issues=所有問題 issues.filter_type.assigned_to_you=指派給您的 issues.filter_type.created_by_you=由您建立的 @@ -617,38 +1002,62 @@ issues.filter_sort.latest=最新建立 issues.filter_sort.oldest=最早建立 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.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=最多 fork +issues.filter_sort.fewestforks=最少 fork +issues.action_open=開放 issues.action_close=關閉 issues.action_label=標籤 issues.action_milestone=里程碑 issues.action_milestone_no_select=無里程碑 -issues.action_assignee=負責人 -issues.action_assignee_no_select=無負責人 +issues.action_assignee=成員 +issues.action_assignee_no_select=沒有成員 issues.opened_by=由 %[3]s 於 %[1]s建立 +pulls.merged_by=由 %[3]s 於 %[1]s合併 +pulls.merged_by_fake=由 %[2]s 建立,%[1]s合併 +issues.closed_by=由 %[3]s 建立,%[1]s關閉 issues.opened_by_fake=由 %[2]s 於 %[1]s建立 +issues.closed_by_fake=由 %[2]s 建立,%[1]s關閉 issues.previous=上一頁 issues.next=下一頁 -issues.open_title=開啟中 +issues.open_title=開放中 issues.closed_title=已關閉 -issues.num_comments=%d 條評論 -issues.commented_at=` 評論 %s` -issues.delete_comment_confirm=您確定要刪除該條評論嗎? +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=評論並關閉 -issues.reopen_issue=重新開啟 -issues.reopen_comment_issue=重新開啟並評論 -issues.create_comment=評論 -issues.commit_ref_at=`在代碼提交 %[2]s 中引用了該問題` +issues.pull_merged_at=`合併了提交 %[2]s%[3]s %[4]s` +issues.close_comment_issue=留言並關閉 +issues.reopen_issue=重新開放 +issues.reopen_comment_issue=留言並重新開放 +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=所有者 +issues.collaborator=協作者 +issues.owner=擁有者 +issues.re_request_review=再次請求審核 +issues.is_stale=經過此審核以後,此合併請求有被修改 +issues.remove_request_review=移除審核請求 +issues.remove_request_review_block=無法移除審核請求 issues.sign_in_require_desc= 登入 才能加入這對話。 issues.edit=編輯 issues.cancel=取消 @@ -657,24 +1066,46 @@ issues.label_title=標籤名稱 issues.label_description=標籤描述 issues.label_color=標籤顏色 issues.label_count=%d 個標籤 -issues.label_open_issues=%d 個開啓的問題 +issues.label_open_issues=%d 個開放中的問題 issues.label_edit=編輯 issues.label_delete=刪除 issues.label_modify=編輯標籤 issues.label_deletion=刪除標籤 issues.label_deletion_desc=刪除標籤會將其從所有問題中刪除,繼續? issues.label_deletion_success=標籤已刪除。 -issues.label.filter_sort.alphabetically=按字母顺序排序 +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.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` @@ -693,51 +1124,155 @@ issues.due_date=截止日期 issues.invalid_due_date_format=截止日期的格式錯誤,必須是 "yyyy-mm-dd" 的形式。 issues.error_modifying_due_date=無法修改截止日期。 issues.error_removing_due_date=無法移除截止日期。 +issues.push_commit_1=加入了 %d 個提交 %s +issues.push_commits_n=加入了 %d 個提交 %s 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_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=`加入了新的先決條件 %s` +issues.dependency.removed_dependency=`移除了先決條件 %s` +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.blocks_short=阻擋 +issues.dependency.blocked_by_short=先決於 +issues.dependency.remove_header=移除先決條件 +issues.dependency.issue_remove_text=即將從此問題移除先決條件。是否繼續? +issues.dependency.pr_remove_text=即將從此合併請求移除先決條件。是否繼續? +issues.dependency.setting=啟用問題及合併請求的先決條件 +issues.dependency.add_error_same_issue=您無法將問題設定為自己的先決條件。 +issues.dependency.add_error_dep_issue_not_exist=先決條件問題不存在。 +issues.dependency.add_error_dep_not_exist=先決條件不存在。 +issues.dependency.add_error_dep_exists=先決條件已存在。 +issues.dependency.add_error_cannot_create_circular=您無法建立讓兩個問題互相阻擋的先決條件。 +issues.dependency.add_error_dep_not_same_repo=這兩個問題必須在同一個存儲庫中。 +issues.review.self.approval=您不能核可自己的合併請求。 +issues.review.self.rejection=您不能對自己的合併請求提出請求變更。 +issues.review.approve=核可了這些變更 %s +issues.review.comment=已審核 %s +issues.review.left_comment=留下了回應 +issues.review.content.empty=您必須留下訊息指出需要修正的地方。 +issues.review.reject=已請求變更 %s +issues.review.review=審核 +issues.review.reviewers=審核者 +issues.review.outdated=過時的 +issues.review.show_outdated=顯示過時的 +issues.review.hide_outdated=隱藏過時的 +issues.review.show_resolved=顯示已解決 +issues.review.hide_resolved=隱藏已解決 +issues.assignee.error=因為未預期的錯誤,未能成功指派所有成員。 + +pulls.desc=啟用合併請求和程式碼審核。 pulls.new=建立合併請求 pulls.compare_changes=建立合併請求 +pulls.compare_changes_desc=選擇合併的目標分支和來源分支。 pulls.compare_base=合併到 +pulls.compare_compare=拉取自 pulls.filter_branch=過濾分支 pulls.no_results=未找到結果 +pulls.nothing_to_compare=這些分支的內容相同,無需建立合併請求。 +pulls.has_pull_request=`已有相同的合併請求: %[2]s#%[3]d` pulls.create=建立合併請求 -pulls.merged_title_desc=於 %[4]s 將 %[1]d 次代碼提交從 %[2]s合併至 %[3]s +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.reopen_to_merge=請重新開放此合併請求以進行合併作業。 +pulls.cant_reopen_deleted_branch=無法重新開放此合併請求,因為該分支已刪除。 pulls.merged=已合併 +pulls.merged_as=此合併請求已被合併為 %[2]s。 +pulls.is_closed=合併請求已被關閉。 pulls.has_merged=合併請求已合併。 -pulls.can_auto_merge_desc=這個拉請求可以自動合併。 - -pulls.merge_pull_request=合併請求 -pulls.rebase_merge_pull_request=Rebase 合併 -pulls.squash_merge_pull_request=Squash 合併 - -milestones.new=新的里程碑 -milestones.open_tab=%d 開啟中 -milestones.close_tab=%d 已關閉 +pulls.title_wip_desc=`標題用 %s 開頭以避免意外地合併此合併請求。` +pulls.cannot_merge_work_in_progress=此合併請求被標記為仍在作業中。準備好要合併時請移除 %s 前綴。 +pulls.data_broken=此合併請求已損毀,因為遺失 Fork 資訊。 +pulls.files_conflicted=此合併請求有變更和目標分支衝突。 +pulls.is_checking=正在進行合併衝突檢查,請稍後再試。 +pulls.required_status_check_administrator=身為系統管理員,您依然可以進行合併。 +pulls.blocked_by_approvals=此合併請求尚未獲得足夠的核可。需要 %d 個核可,已經獲得 %d 個。 +pulls.blocked_by_rejection=官方審核人員要求修改此合併請求。 +pulls.blocked_by_outdated_branch=此合併請求因逾期而被阻擋。 +pulls.blocked_by_changed_protected_files_1=此合併請求被阻擋,因為修改到被保護的檔案: +pulls.blocked_by_changed_protected_files_n=此合併請求被阻擋,因為修改到被保護的檔案: +pulls.can_auto_merge_desc=這個合併請求可以自動合併。 +pulls.cannot_auto_merge_desc=此合併請求無法自動合併,因為有衝突。 +pulls.cannot_auto_merge_helper=手動合併以解決此衝突。 +pulls.num_conflicting_files_1=%d 個衝突的檔案 +pulls.num_conflicting_files_n=%d 個衝突的檔案 +pulls.approve_count_1=%d 個核可 +pulls.approve_count_n=%d 個核可 +pulls.reject_count_1=%d 變更請求 +pulls.reject_count_n=%d 變更請求 +pulls.waiting_count_1=%d 等待審核 +pulls.waiting_count_n=%d 等待審核 + +pulls.no_merge_desc=無法進行合併,因為所有儲存庫的合併選項已被停用。 +pulls.no_merge_helper=在儲存庫設定啟用合併選項或手動合併該合併請求。 +pulls.no_merge_wip=無法進行合併,因為它被標記為還在進行中。 +pulls.no_merge_not_ready=此合併請求還沒準備好被合併,請檢查審核狀態和狀態檢查。 +pulls.no_merge_access=您未被授權合併此合併請求。 +pulls.merge_pull_request=合併 +pulls.rebase_merge_pull_request=Rebase 後合併 +pulls.rebase_merge_commit_pull_request=Rebase 後合併 (--no-ff) +pulls.squash_merge_pull_request=Squash 後合併 +pulls.require_signed_wont_sign=該分支需要經簽署的提交,但此合併將不會被簽署。 +pulls.invalid_merge_option=您無法對此合併請求使用這個合併選項。 +pulls.merge_conflict_summary=錯誤訊息 +pulls.rebase_conflict_summary=錯誤訊息 +; %[2]s
%[3]s
+pulls.push_rejected_no_message=合併失敗:此推送被拒絕但未提供其它資訊。
請檢查此儲存庫的 Githook。 +pulls.status_checks_details=詳情 +pulls.update_branch=更新分支 +pulls.update_branch_success=分支更新成功 +pulls.update_not_allowed=您無權更新分支 +pulls.outdated_with_base_branch=相對於基底分支,此分支已過時 +pulls.closed_at=`關閉了這個合併請求 %[2]s` +pulls.reopened_at=`重新開放了這個合併請求 %[2]s` + +milestones.new=新增里程碑 +milestones.open_tab=%d 個開放中 +milestones.close_tab=%d 個已關閉 milestones.closed=於 %s關閉 +milestones.update_ago=%s前更新 milestones.no_due_date=暫無截止日期 milestones.open=開啟 milestones.close=關閉 +milestones.new_subheader=里程碑可用來組織問題和追蹤進度。 +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.edit_subheader=里程碑可用來組織問題和追蹤進度。 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=到期日由遠到近 @@ -746,6 +1281,17 @@ milestones.filter_sort.most_complete=完成度由高到低 milestones.filter_sort.most_issues=問題由多到少 milestones.filter_sort.least_issues=問題由少到多 +signing.will_sign=將使用金鑰「%s」簽署此提交。 +signing.wont_sign.error=檢查能否簽署提交時發生錯誤 +signing.wont_sign.nokey=沒有金鑰可用來簽署此提交 +signing.wont_sign.pubkey=將不會簽署此提交,因為您尚未連結任何公鑰到您的帳戶 +signing.wont_sign.twofa=您必須啟用兩步驟驗證才能使用提交簽署 +signing.wont_sign.parentsigned=將不會簽署此提交,因為父提交未經簽署 +signing.wont_sign.basesigned=將不會簽署此合併,因為基底提交未經簽署 +signing.wont_sign.headsigned=將不會簽署此合併,因為 HEAD 提交未經簽署 +signing.wont_sign.commitssigned=將不會簽署此合併,因為所有關聯的提交都未經簽署 +signing.wont_sign.approved=合併請求未被核可,所以不會簽署此合併。 +signing.wont_sign.not_signed_in=你還沒有登入 ext_wiki=外部 Wiki ext_wiki.desc=連結外部 Wiki。 @@ -763,118 +1309,224 @@ 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 頁面修訂記錄 +wiki.back_to_wiki=回到 Wiki 頁面 wiki.delete_page_button=刪除頁面 +wiki.delete_page_notice_1=刪除 Wiki 頁面「%s」將不可還原。是否繼續? wiki.page_already_exists=相同名稱的 Wiki 頁面已經存在。 wiki.reserved_page=Wiki 頁面名稱 "%s" 是被保留的。 wiki.pages=所有頁面 wiki.last_updated=最後更新於 %s -activity=活動 +activity=動態 activity.period.filter_label=期間: activity.period.daily=1 天 activity.period.halfweekly=3 天 -activity.period.weekly=1 星期 +activity.period.weekly=1 週 activity.period.monthly=1 個月 +activity.period.quarterly=3 個月 +activity.period.semiyearly=6 個月 +activity.period.yearly=1 年 activity.overview=概覽 -activity.merged_prs_count_1=合併請求 -activity.merged_prs_count_n=合併請求 -activity.opened_prs_count_1=建立合併請求 -activity.title.user_1=%d 使用者 -activity.title.user_n=%d 使用者 -activity.title.prs_1=%d 合併請求 -activity.title.prs_n=%d 合併請求 +activity.active_prs_count_1=%d 個合併請求 +activity.active_prs_count_n=%d 個合併請求 +activity.merged_prs_count_1=合併 +activity.merged_prs_count_n=合併 +activity.opened_prs_count_1=提出合併請求 +activity.opened_prs_count_n=提出合併請求 +activity.title.user_1=%d 位使用者 +activity.title.user_n=%d 位使用者 +activity.title.prs_1=%d 個合併請求 +activity.title.prs_n=%d 個合併請求 +activity.title.prs_merged_by=%[2]s合併了 %[1]s +activity.title.prs_opened_by=%[2]s提出了 %[1]s activity.merged_prs_label=已合併 activity.opened_prs_label=提案 activity.active_issues_count_1=%d 個問題 activity.active_issues_count_n=%d 個問題 -activity.closed_issues_count_1=已關閉的 Issue -activity.closed_issues_count_n=已關閉的 Issue -activity.title.issues_1=%d Issue -activity.title.issues_n=%d Issues -activity.title.issues_closed_by=%[2]s 關閉了 %[1]s -activity.title.issues_created_by=%[2]s 建立了 %[1]s +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=%[2]s關閉了 %[1]s +activity.title.issues_created_by=%[2]s建立了 %[1]s activity.closed_issue_label=已關閉 -activity.new_issues_count_1=建立問題 -activity.new_issues_count_n=建立問題 -activity.new_issue_label=已開啟 -activity.title.unresolved_conv_1=%d 未解決的對話 -activity.title.unresolved_conv_n=%d 未解決的對話 +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.unresolved_conv_label=開放 +activity.title.releases_1=%d 個版本 +activity.title.releases_n=%d 個版本 +activity.title.releases_published_by=%[2]s發佈了 %[1]s activity.published_release_label=已發佈 +activity.no_git_activity=期間內沒有任何提交動態 +activity.git_stats_exclude_merges=不計合併, +activity.git_stats_author_1=%d 位作者 +activity.git_stats_author_n=%d 位作者 +activity.git_stats_pushed_1=已經推送 +activity.git_stats_pushed_n=已經推送 +activity.git_stats_commit_1=%d 次提交 +activity.git_stats_commit_n=%d 次提交 +activity.git_stats_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=搜尋儲存庫 search.results=在 %s 中搜尋 "%s" 的结果 -settings=儲存庫設定 +settings=設定 settings.desc=設定是您可以管理儲存庫設定的地方 settings.options=儲存庫 settings.collaboration=協作者 settings.collaboration.admin=管理員 settings.collaboration.write=可寫權限 settings.collaboration.read=可讀權限 +settings.collaboration.owner=擁有者 settings.collaboration.undefined=未定義 -settings.hooks=管理 Webhooks -settings.githooks=管理 Git Hooks +settings.hooks=Webhook +settings.githooks=Git Hook settings.basic_settings=基本設定 settings.mirror_settings=鏡像設定 settings.sync_mirror=現在同步 settings.mirror_sync_in_progress=鏡像同步正在進行中。 請稍後再回來看看。 +settings.email_notifications.enable=啟用郵件通知 +settings.email_notifications.onmention=只在被提到時傳送郵件通知 +settings.email_notifications.disable=關閉郵件通知 +settings.email_notifications.submit=套用郵件偏好設定 settings.site=網站 -settings.update_settings=更新儲存庫設定 -settings.advanced_settings=高級設定 +settings.update_settings=更新設定 +settings.advanced_settings=進階設定 settings.wiki_desc=啟用儲存庫 Wiki settings.use_internal_wiki=使用內建 Wiki settings.use_external_wiki=使用外部 Wiki settings.external_wiki_url=外部 Wiki 連結 settings.external_wiki_url_error=外部 Wiki 網址不是有效的網址。 settings.external_wiki_url_desc=點擊問題標籤時,使用者會被導向到外部 Wiki URL。 -settings.external_tracker_url=外部 Issue 追蹤網址 -settings.tracker_url_format=外部問題管理系統的 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 無效。 +settings.external_tracker_url_desc=點擊問題頁籤時,使用者會被導向至外部問題追蹤器 URL。 +settings.tracker_url_format=外部問題追蹤器的 URL 格式 +settings.tracker_url_format_error=該外部問題追蹤器 URL 格式無效。 +settings.tracker_issue_style=外部問題追蹤器的編號格式 settings.tracker_issue_style.numeric=數字 settings.tracker_issue_style.alphanumeric=字母及數字 +settings.tracker_url_format_desc=使用占位符 {user}, {repo}{index} 代表帳號、儲存庫名稱和問題編號。 settings.enable_timetracker=啟用時間追蹤 +settings.allow_only_contributors_to_track_time=只讓貢獻者追蹤時間 +settings.pulls_desc=啟用儲存庫合併請求 +settings.pulls.ignore_whitespace=衝突時忽略空白 +settings.pulls.allow_merge_commits=啟用提交合併 +settings.pulls.allow_rebase_merge=啟用 Rebase 合併提交 +settings.pulls.allow_rebase_merge_commit=啟用 Rebase 顯式合併提交(--no-ff) +settings.pulls.allow_squash_commits=啟用 Squash 合併提交 +settings.projects_desc=啟用儲存庫專案 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=轉換為普通儲存庫 -settings.convert_desc=您可以將此鏡像轉成普通儲存庫。此動作不可恢復。 +settings.convert_desc=您可以將此鏡像轉成普通儲存庫。此動作不可還原。 +settings.convert_notices_1=此操作會將此鏡像轉換成普通儲存庫且不可還原。 settings.convert_confirm=轉換儲存庫 settings.convert_succeed=鏡像儲存庫已成功轉換為一般儲存庫。 +settings.convert_fork=轉換成普通儲存庫 +settings.convert_fork_desc=您可以將此 fork 轉換成普通儲存庫。此動作不可還原。 +settings.convert_fork_notices_1=此操作會將此 fork 轉換成普通儲存庫且不可還原。 +settings.convert_fork_confirm=轉換儲存庫 +settings.convert_fork_succeed=此 fork 已轉換成普通儲存庫。 settings.transfer=轉移儲存庫所有權 +settings.transfer_desc=將此儲存庫轉移給其它使用者或受您管理的組織。 +settings.transfer_notices_1=- 如果將此儲存庫轉移給個別使用者,您將會失去此儲存庫的存取權。 +settings.transfer_notices_2=- 如果將此儲存庫轉移到您(共同)擁有的組織,您將能繼續保有此儲存庫的存取權。 +settings.transfer_form_title=輸入儲存庫名稱以確認: +settings.signing_settings=簽署驗證設定 +settings.trust_model=簽署信任模式 +settings.trust_model.default=預設信任模式 +settings.trust_model.default.desc=使用此 Gitea 的預設儲存庫信任模式。 +settings.trust_model.collaborator=協作者 +settings.trust_model.collaborator.long=協作者:信任協作者的簽署 +settings.trust_model.committer=提交者 +settings.trust_model.collaboratorcommitter=協作者+提交者 +settings.trust_model.collaboratorcommitter.long=協作者 + 提交者:信任協作者同時是提交者的簽署 settings.wiki_delete=刪除 Wiki 資料 +settings.wiki_delete_desc=刪除儲存庫 Wiki 資料是永久的且不可還原。 +settings.wiki_delete_notices_1=- 這將會永久刪除與停用 %s 的儲存庫 Wiki。 settings.confirm_wiki_delete=刪除 Wiki 資料 +settings.wiki_deletion_success=已刪除儲存庫的 Wiki 資料。 settings.delete=刪除本儲存庫 -settings.delete_notices_1=- 此操作 不可以 被回滾。 +settings.delete_desc=刪除儲存庫是永久的且不可還原。 +settings.delete_notices_1=- 此操作不可還原。 +settings.delete_notices_2=- 此操作將永久刪除 %s 儲存庫,包括程式碼、問題、留言、Wiki 資料和協作者設定。 +settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。 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.add_collaborator_inactive_user=無法加入未啟用的使用者為協作者。 +settings.add_collaborator_duplicate=此協作者早已被加入此儲存庫。 settings.delete_collaborator=移除 settings.collaborator_deletion=移除協作者 +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.teams=團隊 +settings.add_team=增加團隊 +settings.add_team_duplicate=團隊已擁有該儲存庫 +settings.add_team_success=團隊現在可存取該儲存庫了。 +settings.search_team=搜尋團隊... +settings.change_team_permission_tip=團隊權限可於團隊設定頁面修改,不能針對儲存庫分別調整。 +settings.delete_team_tip=此團隊可存取所有儲存庫,無法移除 +settings.remove_team_success=已移除團隊存取儲存庫的權限。 settings.add_webhook=建立 Webhook -settings.webhook_deletion=刪除 Webhook -settings.webhook_deletion_success=Webhook 已刪除。 -settings.webhook.test_delivery=測試推送 -settings.webhook.request=請求內容 -settings.webhook.response=響應內容 -settings.webhook.headers=標題 +settings.add_webhook.invalid_channel_name=Webhook 頻道名稱不可留白,且不能僅有 # 字號。 +settings.hooks_desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。在Webhook 指南閱讀更多內容。 +settings.webhook_deletion=移除 Webhook +settings.webhook_deletion_desc=移除 Webhook 將刪除它的設定及傳送記錄,是否繼續? +settings.webhook_deletion_success=Webhook 已移除。 +settings.webhook.test_delivery=傳送測試資料 +settings.webhook.test_delivery_desc=使用假事件測試這個 Webhook +settings.webhook.test_delivery_success=已將假事件加入到傳送佇列,可能需要等待幾分鐘才會出現於傳送紀錄。 +settings.webhook.request=請求 +settings.webhook.response=回應 +settings.webhook.headers=標頭 settings.webhook.payload=內容 -settings.webhook.body=響應內容 -settings.githook_edit_desc=如果 Hook 未啟動,則會顯示樣例文件中的內容。如果想要刪除某個 Hook,則提交空白文本即可。 +settings.webhook.body=本體 +settings.githooks_desc=Git Hook 是 Git 本身提供的功能。您可以在下方編輯 Hook 檔案以設定自訂作業。 +settings.githook_edit_desc=如果 Hook 未啟動,則會顯示範例文件中的內容。如果想要刪除某個 Hook,則送出空白內容即可。 settings.githook_name=Hook 名稱 settings.githook_content=Hook 內容 -settings.update_githook=更新 Hook 設定 +settings.update_githook=更新 Hook +settings.add_webhook_desc=Gitea 會發送含有指定 Content Type 的 POST 請求到目標 URL。 在 Webhook 指南閱讀更多內容。 settings.payload_url=目標 URL +settings.http_method=HTTP 請求方法 settings.content_type=POST Content Type -settings.secret=金鑰文本 +settings.secret=Secret settings.slack_username=服務名稱 settings.slack_icon_url=圖標 URL settings.discord_username=使用者名稱 @@ -884,63 +1536,210 @@ settings.event_desc=觸發條件: settings.event_push_only=推送事件 settings.event_send_everything=所有事件 settings.event_choose=自訂事件... +settings.event_header_repository=儲存庫事件 settings.event_create=建立 -settings.event_create_desc=建立分支或標籤 +settings.event_create_desc=建立分支或標籤。 settings.event_delete=刪除 -settings.event_fork=複製 +settings.event_delete_desc=刪除分支或標籤。 +settings.event_fork=Fork +settings.event_fork_desc=儲存庫已被 fork。 +settings.event_release=版本發佈 +settings.event_release_desc=在儲存庫中發佈、更新或刪除版本。 settings.event_push=推送 +settings.event_push_desc=推送到儲存庫。 settings.event_repository=儲存庫 +settings.event_repository_desc=建立或刪除儲存庫。 +settings.event_header_issue=問題事件 settings.event_issues=問題 -settings.event_issue_comment=問題評論 -settings.event_issue_comment_desc=已經建立、編輯或刪除的問題評論。 +settings.event_issues_desc=建立、編輯、關閉及重新開放問題。 +settings.event_issue_assign=指派 +settings.event_issue_assign_desc=指派或取消指派問題。 +settings.event_issue_label=標籤 +settings.event_issue_label_desc=更新或清除問題標籤。 +settings.event_issue_milestone=里程碑 +settings.event_issue_milestone_desc=設定或取消設定問題里程碑。 +settings.event_issue_comment=問題留言 +settings.event_issue_comment_desc=已經建立、編輯或刪除的問題留言。 +settings.event_header_pull_request=合併請求事件 settings.event_pull_request=合併請求 +settings.event_pull_request_desc=建立、編輯、關閉及重新開放合併請求。 +settings.event_pull_request_assign=合併請求指派 +settings.event_pull_request_assign_desc=指派或取消指派合併請求。 +settings.event_pull_request_label=合併請求標籤 +settings.event_pull_request_label_desc=更新或清除合併請求標籤。 +settings.event_pull_request_milestone=合併請求里程碑 +settings.event_pull_request_milestone_desc=設定或取消設定合併請求里程碑。 +settings.event_pull_request_comment=合併請求留言 +settings.event_pull_request_comment_desc=建立、編輯或刪除合併請求的留言。 +settings.event_pull_request_review=合併請求審核 +settings.event_pull_request_review_desc=核准、退回或提出審核留言。 +settings.event_pull_request_sync=合併請求同步 +settings.event_pull_request_sync_desc=合併請求同步。 +settings.branch_filter=分支篩選 +settings.active=啟用 +settings.active_helper=觸發事件的資訊將會被送到此 Webhook URL。 settings.add_hook_success=Webhook 新增成功! settings.update_webhook=更新 Webhook settings.update_hook_success=Webhook 更新成功! -settings.delete_webhook=刪除 Webhook -settings.recent_deliveries=最近推送記錄 +settings.delete_webhook=移除 Webhook +settings.recent_deliveries=最近傳送記錄 settings.hook_type=Hook 類型 -settings.slack_token=令牌 +settings.add_slack_hook_desc=將 Slack 整合到您的儲存庫。 +settings.slack_token=Token settings.slack_domain=域名 settings.slack_channel=頻道 -settings.deploy_keys=管理部署金鑰 +settings.add_discord_hook_desc=將 Discord 整合到您的儲存庫。 +settings.add_dingtalk_hook_desc=將 Dingtalk 整合到您的儲存庫。 +settings.add_telegram_hook_desc=將 Telegram 整合到您的儲存庫。 +settings.add_matrix_hook_desc=將 Matrix 整合到您的儲存庫。 +settings.add_msteams_hook_desc=將 Microsoft Teams 整合到您的儲存庫。 +settings.add_feishu_hook_desc=將 Feishu 整合到您的儲存庫。 +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.deploy_key_content=內容 +settings.key_been_used=具有相同內容的部署金鑰已在使用中。 +settings.key_name_used=已有相同名稱的部署金鑰。 +settings.add_key_success=已新增部署金鑰「%s」。 settings.deploy_key_deletion=刪除部署金鑰 -settings.branches=分支列表 +settings.deploy_key_deletion_desc=移除部署金鑰將拒絕它存取此儲存庫。是否繼續? +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_this_branch_desc=防止刪除分支,並限制 Git 推送與合併到分支。 +settings.protect_disable_push=停用推送 +settings.protect_disable_push_desc=不允許推送到此分支。 +settings.protect_enable_push=啟用推送 +settings.protect_enable_push_desc=任何擁有寫入權限的使用者將可推送至該分支(但不可使用force push)。 +settings.protect_whitelist_committers=使用白名單控管推送 +settings.protect_whitelist_committers_desc=僅允許白名單內的使用者或團隊推送至該分支(但不可使用force push)。 +settings.protect_whitelist_deploy_keys=將擁有寫入權限的部署金鑰加入白名單。 +settings.protect_whitelist_users=允許推送的使用者: settings.protect_whitelist_search_users=搜尋使用者... +settings.protect_whitelist_teams=允許推送的團隊: settings.protect_whitelist_search_teams=搜尋團隊... +settings.protect_merge_whitelist_committers=啟用合併白名單 +settings.protect_merge_whitelist_committers_desc=僅允許白名單內的使用者或團隊將合併請求合併至該分支。 +settings.protect_merge_whitelist_users=允許合併的使用者: +settings.protect_merge_whitelist_teams=允許合併的團隊: +settings.protect_check_status_contexts=啟用狀態檢查 +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.dismiss_stale_approvals=捨棄過時的核可 +settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的內容,並被推送到此分支時,將捨棄舊的核可。 +settings.require_signed_commits=僅接受經簽署的提交 +settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。 settings.add_protected_branch=啟用保護 settings.delete_protected_branch=停用保護 +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=沒有受保護的分支。 settings.edit_protected_branch=編輯 +settings.protected_branch_required_approvals_min=需要的核可數量不能為負數。 +settings.bot_token=Bot Token +settings.chat_id=Chat ID +settings.matrix.homeserver_url=Homeserver 網址 +settings.matrix.room_id=聊天室 ID +settings.matrix.access_token=Access Token +settings.matrix.message_type=訊息類型 +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_delete=刪除 OID 為 %s 的 LFS 檔案 +settings.lfs_findpointerfiles=尋找指標檔案 +settings.lfs_invalid_locking_path=無效的路徑: %s +settings.lfs_invalid_lock_directory=無法鎖定目錄: %s +settings.lfs_lock_path=要鎖定的檔案路徑... +settings.lfs_pointers.sha=Blob SHA +settings.lfs_pointers.oid=OID diff.browse_source=瀏覽代碼 diff.parent=父節點 diff.commit=當前提交 +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.show_unified_view=合併檢視 +diff.whitespace_button=空白符號 +diff.whitespace_show_everything=顯示所有變更 +diff.whitespace_ignore_all_whitespace=比較「行」時忽略空白符號 +diff.whitespace_ignore_amount_changes=忽略空白符號的數量變化 +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=留言... +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=核可 +diff.review.reject=請求變更 +diff.committed_by=提交者 +diff.protected=受保護 + +releases.desc=追蹤專案版本和檔案下載。 release.releases=版本發佈 release.new_release=發佈新版本 release.draft=草稿 release.prerelease=預發佈版本 release.stable=穩定 release.edit=編輯 +release.ahead.commits=%d 次提交 release.source_code=程式碼 +release.new_subheader=發佈、整理專案的版本。 +release.edit_subheader=發佈、整理專案的版本。 release.tag_name=標籤名稱 release.target=目標分支 +release.tag_helper=新增或選擇一個既有的標籤。 release.title=標題 release.content=內容 release.prerelease_desc=標記為 Pre-Release @@ -955,33 +1754,46 @@ release.deletion_success=已刪除此版本發佈。 release.tag_name_already_exist=已經存在使用相同標籤的發佈版本。 release.tag_name_invalid=標籤名稱無效。 release.downloads=下載附件 +release.download_count=下載次數:%s branch.name=分支名稱 branch.search=搜尋分支 branch.already_exists=分支名稱 ”%s“ 已經存在 branch.delete_head=刪除 -branch.delete=刪除分支 '%s' +branch.delete=刪除分支「%s」 branch.delete_html=刪除分支 -branch.delete_desc=刪除分支是永久的。 此動作無法復原,繼續? -branch.deletion_success=分支 '%s' 已被刪除。 -branch.deletion_failed=刪除分支 '%s' 失敗。 +branch.delete_desc=刪除分支是永久的。 此動作不可還原,是否繼續? +branch.deletion_success=分支「%s」已被刪除。 +branch.deletion_failed=刪除分支「%s」失敗。 +branch.delete_branch_has_new_commits=因為合併後已加入了新的提交,「%s」分支無法被刪除。 branch.create_branch=建立分支 %s branch.create_from=從 '%s' +branch.create_success=已建立分支 '%s'。 branch.branch_already_exists=分支 '%s' 已存在此儲存庫 +branch.branch_name_conflict=分支名稱「%s」與現有分支「%s」衝突。 branch.deleted_by=刪除人: %s +branch.restore_success=已還原分支「%s」。 branch.restore_failed=還原分支 %s 失敗 -branch.protected_deletion_failed=分支 '%s' 已被保護,不能刪除。 +branch.protected_deletion_failed=分支「%s」已被保護,不能刪除。 +branch.default_deletion_failed=分支「%s」為預設分支,不能刪除。 +branch.restore=還原分支「%s」 +branch.download=下載分支 '%s' +branch.included_desc=此分支是預設分支的一部分 +branch.included=包含 topic.manage_topics=管理主題 topic.done=完成 +topic.count_prompt=您最多能選擇 25 個主題 +topic.format_prompt=主題必須以字母或數字為開頭,可包含連接號 ('-') 且最長為 35 個字。 [org] org_name_holder=組織名稱 org_full_name_holder=組織全名 +org_name_helper=組織名稱應該要簡短且方便記憶 create_org=建立組織 -repo_updated=最後更新於 -people=組織成員 -teams=組織團隊 +repo_updated=更新於 +people=成員 +teams=團隊 lower_members=名成員 lower_repositories=個儲存庫 create_new_team=建立團隊 @@ -989,7 +1801,12 @@ create_team=建立新的團隊 org_desc=組織描述 team_name=團隊名稱 team_desc=團隊描述 +team_name_helper=團隊名稱應該要簡短且方便記憶 +team_desc_helper=描述團隊的目的或角色。 +team_access_desc=儲存庫存取權 team_permission_desc=權限 +team_unit_desc=允許存取的儲存庫區域 +team_unit_disabled=(已停用) form.name_reserved=組織名稱 '%s' 是被保留的。 form.name_pattern_not_allowed=儲存庫名稱無法使用 "%s"。 @@ -1000,17 +1817,28 @@ settings.options=組織 settings.full_name=組織全名 settings.website=官方網站 settings.location=所在地區 - -settings.update_settings=更新組織設定 +settings.permission=權限 +settings.repoadminchangeteam=儲存庫管理者可增加與移除團隊權限 +settings.visibility=瀏覽權限 +settings.visibility.public=公開 +settings.visibility.limited=受限(只有登入的使用者才能看到) +settings.visibility.limited_shortname=受限 +settings.visibility.private=私有(只有組織成員才能看到) +settings.visibility.private_shortname=私有 + +settings.update_settings=更新設定 settings.update_setting_success=組織設定已更新。 settings.change_orgname_prompt=注意:修改組織名稱將會同時修改對應的 URL。 -settings.update_avatar_success=組織大頭貼已經更新。 +settings.update_avatar_success=已更新組織的大頭貼。 settings.delete=刪除組織 -settings.delete_account=刪除當前組織 +settings.delete_account=刪除這個組織 +settings.delete_prompt=該組織將被永久刪除。此動作不可還原! settings.confirm_delete_account=確認刪除組織 settings.delete_org_title=刪除組織 -settings.hooks_desc=新增 webhooks 將觸發在這個組織下 全部的儲存庫 。 +settings.delete_org_desc=即將永久刪除這個組織,是否繼續? +settings.hooks_desc=此組織下的所有存儲庫都會觸發在此新增的 Webhook。 +settings.labels_desc=在此處新增的標籤可用於此組織下的所有儲存庫。 members.membership_visibility=成員可見性: members.public=可見 @@ -1018,40 +1846,66 @@ members.public_helper=隱藏 members.private=隱藏 members.private_helper=顯示 members.member_role=成員角色: -members.owner=管理員 +members.owner=擁有者 members.member=普通成員 -members.remove=移除成員 -members.leave=離開組織 +members.remove=移除 +members.leave=離開 members.invite_desc=邀請新的用戶加入 %s: members.invite_now=立即邀請 -teams.join=加入團隊 -teams.leave=離開團隊 +teams.join=加入 +teams.leave=離開 +teams.can_create_org_repo=建立儲存庫 +teams.can_create_org_repo_helper=成員可以在組織中新增儲存庫。建立者將自動取得新儲存庫的管理員權限。 teams.read_access=讀取權限 -teams.read_access_helper=成員可以查看和複製團隊儲存庫。 +teams.read_access_helper=成員可以查看和 Clone 團隊儲存庫。 teams.write_access=寫入權限 teams.write_access_helper=成員可以查看和推送到團隊儲存庫。 teams.admin_access=管理員權限 +teams.admin_access_helper=成員可以拉取、推送和新增協作者到團隊儲存庫中。 teams.no_desc=該團隊暫無描述 teams.settings=團隊設定 +teams.owners_permission_desc=擁有者對 所有儲存庫 具有完整權限,且對組織具有 管理員權限。 teams.members=團隊成員 -teams.update_settings=更新團隊設定 +teams.update_settings=更新設定 teams.delete_team=刪除團隊 -teams.add_team_member=新增團隊成員 +teams.add_team_member=增加團隊成員 teams.delete_team_title=刪除團隊 +teams.delete_team_desc=刪除團隊將會撤銷成員的儲存庫存取權,是否繼續? teams.delete_team_success=該團隊已被刪除。 +teams.read_permission_desc=這個團隊擁有讀取 權限:成員可以查看和 Clone 團隊儲存庫。 +teams.write_permission_desc=這個團隊擁有寫入 權限:成員可以查看和推送到團隊儲存庫。 +teams.admin_permission_desc=這個團隊擁有管理員 權限:成員可以讀取、推送和增加協作者到儲存庫。 +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=這個團隊擁有所有儲存庫讀取 權限:成員可以查看和 Clone 儲存庫。 +teams.all_repositories_write_permission_desc=這個團隊擁有所有儲存庫寫入 權限:成員可以讀取和推送到儲存庫。 +teams.all_repositories_admin_permission_desc=這個團隊擁有所有儲存庫管理員 權限:成員可以讀取、推送和增加協作者到儲存庫。 [admin] -dashboard=控制面版 -users=使用者帳號 -organizations=組織管理 -repositories=儲存庫管理 +dashboard=資訊主頁 +users=使用者帳戶 +organizations=組織 +repositories=儲存庫 +hooks=預設 Webhook +systemhooks=系統 Webhook authentication=認證來源 -config=應用設定管理 -notices=系統提示管理 +emails=使用者電子信箱 +config=組態 +notices=系統提示 monitor=應用監控面版 first_page=首頁 last_page=末頁 @@ -1060,52 +1914,78 @@ total=總計:%d dashboard.statistic=摘要 dashboard.operations=維護操作 dashboard.system_status=系統狀態 -dashboard.statistic_info=Gitea 資料庫統計:%d 位使用者,%d 個組織,%d 個公鑰,%d 個儲存庫,%d 個儲存庫關注,%d 個讚,%d 次行為,%d 條權限記錄,%d 個問題,%d 次評論,%d 個社交帳號,%d 個用戶關註,%d 個鏡像,%d 個版本發佈,%d 個登錄源,%d 個 Webhook ,%d 個里程碑,%d 個標籤,%d 個 Hook 任務,%d 個團隊,%d 個更新任務,%d 個附件。 +dashboard.statistic_info=Gitea 資料庫統計:%d 位使用者,%d 個組織,%d 個公鑰,%d 個儲存庫,%d 個儲存庫關注,%d 個星號,%d 次行為,%d 條權限記錄,%d 個問題,%d 則留言,%d 個社群帳戶,%d 個用戶關注,%d 個鏡像,%d 個版本發佈,%d 個認證來源,%d 個 Webhook ,%d 個里程碑,%d 個標籤,%d 個 Hook 任務,%d 個團隊,%d 個更新任務,%d 個附件。 dashboard.operation_name=操作名稱 dashboard.operation_switch=開關 dashboard.operation_run=執行 -dashboard.clean_unbind_oauth=清理未綁定OAuth的連結 -dashboard.clean_unbind_oauth_success=所有未綁定 OAuth 的連結已刪除。 +dashboard.clean_unbind_oauth=清理未綁定的 OAuth 連結 +dashboard.clean_unbind_oauth_success=所有未綁定的 OAuth 連結已刪除。 +dashboard.task.started=已開始的任務: %[1]s +dashboard.task.process=任務: %[1]s +dashboard.task.cancelled=任務: %[1]s 已取消: %[3]s +dashboard.task.error=任務中的錯誤: %[1]s: %[3]s +dashboard.task.finished=任務: 已完成由 %[2]s 啟動的 %[1]s +dashboard.task.unknown=未知的任務: %[1]s +dashboard.cron.started=已開始的 Cron: %[1]s +dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %s 已取消: %[3]s +dashboard.cron.finished=Cron: %[1]s 已完成 +dashboard.delete_inactive_accounts=刪除所有未啟用帳戶 +dashboard.delete_inactive_accounts.started=刪除所有未啟用帳戶的任務已啟動。 dashboard.delete_repo_archives=刪除所有儲存庫存檔 -dashboard.delete_missing_repos=刪除所有遺失 Git 檔案的儲存庫記錄 -dashboard.git_gc_repos=對儲存庫進行垃圾回收 +dashboard.delete_repo_archives.started=刪除所有儲存庫存檔的任務已啟動。 +dashboard.delete_missing_repos=刪除所有遺失 Git 檔案的儲存庫 +dashboard.delete_missing_repos.started=刪除所有遺失 Git 檔案的儲存庫的任務已啟動。 +dashboard.update_mirrors=更新鏡像 +dashboard.repo_health_check=對所有儲存庫進行健康檢查 +dashboard.archive_cleanup=刪除舊的儲存庫存檔 +dashboard.deleted_branches_cleanup=清理已刪除的分支 +dashboard.git_gc_repos=對所有儲存庫進行垃圾回收 +dashboard.resync_all_sshkeys=使用 Gitea 的 SSH 金鑰更新「.ssh/authorized_keys」檔案。 +dashboard.resync_all_sshkeys.desc=(內建 SSH 伺服器無需使用。) +dashboard.resync_all_sshprincipals=使用 Gitea 的 SSH 主體更新「.ssh/authorized_principals」檔案。 +dashboard.resync_all_sshprincipals.desc=(內建 SSH 伺服器無需使用。) +dashboard.resync_all_hooks=重新同步所有儲存庫的 pre-receive、update 和 post-receive Hook。 dashboard.reinit_missing_repos=重新初始化所有遺失具已存在記錄的Git 儲存庫 dashboard.sync_external_users=同步外部使用者資料 dashboard.server_uptime=服務執行時間 -dashboard.current_goroutine=當前 Goroutines 數量 -dashboard.current_memory_usage=當前內存使用量 -dashboard.total_memory_allocated=所有被分配的內存 -dashboard.memory_obtained=內存佔用量 +dashboard.current_goroutine=目前的 Goroutines 數量 +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=當前 Heap 內存使用量 -dashboard.heap_memory_obtained=Heap 內存佔用量 -dashboard.heap_memory_idle=Heap 內存空閒量 -dashboard.heap_memory_in_use=正在使用的 Heap 內存 -dashboard.heap_memory_released=被釋放的 Heap 內存 -dashboard.heap_objects=Heap 對象數量 +dashboard.current_heap_usage=目前 Heap 記憶體使用量 +dashboard.heap_memory_obtained=Heap 記憶體佔用量 +dashboard.heap_memory_idle=Heap 記憶體閒置量 +dashboard.heap_memory_in_use=正在使用的 Heap 記憶體 +dashboard.heap_memory_released=被釋放的 Heap 記憶體 +dashboard.heap_objects=Heap 物件數量 dashboard.bootstrap_stack_usage=啟動 Stack 使用量 -dashboard.stack_memory_obtained=被分配的 Stack 內存 -dashboard.mspan_structures_usage=MSpan 結構內存使用量 -dashboard.mspan_structures_obtained=被分配的 MSpan 結構內存 -dashboard.mcache_structures_usage=MCache 結構內存使用量 -dashboard.mcache_structures_obtained=被分配的 MCache 結構內存 -dashboard.profiling_bucket_hash_table_obtained=被分配的剖析哈希表內存 -dashboard.gc_metadata_obtained=被分配的垃圾收集元資料內存 -dashboard.other_system_allocation_obtained=其它被分配的系統內存 -dashboard.next_gc_recycle=下次垃圾收集內存回收量 -dashboard.last_gc_time=距離上次垃圾收集時間 -dashboard.total_gc_time=垃圾收集執行時間總量 -dashboard.total_gc_pause=垃圾收集暫停時間總量 -dashboard.last_gc_pause=上次垃圾收集暫停時間 -dashboard.gc_times=垃圾收集執行次數 - -users.user_manage_panel=帳號管理 -users.new_account=建立新帳號 -users.name=使用者名稱 +dashboard.stack_memory_obtained=被分配的 Stack 記憶體 +dashboard.mspan_structures_usage=MSpan 結構使用量 +dashboard.mspan_structures_obtained=被分配的 MSpan 結構 +dashboard.mcache_structures_usage=MCache 結構記使用量 +dashboard.mcache_structures_obtained=被分配的 MCache 結構 +dashboard.profiling_bucket_hash_table_obtained=被分配的剖析雜湊表 +dashboard.gc_metadata_obtained=被分配 GC Metadata +dashboard.other_system_allocation_obtained=其它被分配的系統記憶體 +dashboard.next_gc_recycle=下次 GC 記憶體回收量 +dashboard.last_gc_time=距離上次 GC 時間 +dashboard.total_gc_time=總 GC 暫停時間 +dashboard.total_gc_pause=總 GC 暫停時間 +dashboard.last_gc_pause=上次 GC 暫停時間 +dashboard.gc_times=GC 執行次數 + +users.user_manage_panel=使用者帳戶管理 +users.new_account=建立使用者帳戶 +users.name=帳號 +users.full_name=全名 users.activated=已啟用 users.admin=管理員 +users.restricted=受限 +users.2fa=兩步驟驗證 users.repos=儲存庫數 users.created=建立時間 users.last_login=上次登入 @@ -1113,21 +1993,38 @@ users.never_login=從未登入 users.send_register_notify=寄送使用者註冊通知 users.new_success=已建立新帳戶 '%s'。 users.edit=編輯 -users.auth_source=認證源 +users.auth_source=認證來源 users.local=本地 +users.auth_login_name=認證登入名稱 users.password_helper=密碼留空則不修改。 -users.edit_account=編輯帳號 +users.update_profile_success=已更新使用者帳戶。 +users.edit_account=編輯使用者帳戶 +users.max_repo_creation=最大儲存庫數量 users.max_repo_creation_desc=(設定 -1 使用全域預設限制) users.is_activated=該使用者帳號已被啟用 users.prohibit_login=停用登入 users.is_admin=是管理員 -users.allow_import_local=允許匯入本地儲存庫 -users.allow_create_organization=允許建立組織 -users.update_profile=更新帳號 -users.delete_account=刪除帳號 +users.is_restricted=受限制的 +users.allow_git_hook=可以建立 Git Hook +users.allow_import_local=可以匯入本地儲存庫 +users.allow_create_organization=可以建立組織 +users.update_profile=更新使用者帳戶 +users.delete_account=刪除使用者帳戶 users.still_own_repo=這個使用者還擁有一個或更多的儲存庫。請先刪除或是轉移這些儲存庫。 -users.deletion_success=使用者帳號已被刪除。 - +users.deletion_success=使用者帳戶已被刪除。 + +emails.email_manage_panel=使用者電子信箱管理 +emails.primary=主要 +emails.activated=已啟用 +emails.filter_sort.email=電子信箱 +emails.filter_sort.email_reverse=電子信箱(倒序) +emails.filter_sort.name=使用者名稱 +emails.filter_sort.name_reverse=使用者名稱(倒序) +emails.updated=信箱已更新 +emails.not_updated=電子信箱更新失敗: %v +emails.duplicate_active=此信箱已被其他使用者使用 +emails.change_email_header=更新電子信箱屬性 +emails.change_email_text=您確定要更新這個電子信箱? orgs.org_manage_panel=組織管理 orgs.name=組織名稱 @@ -1136,17 +2033,26 @@ orgs.members=成員數 orgs.new_orga=新增組織 repos.repo_manage_panel=儲存庫管理 -repos.owner=所有者 +repos.unadopted=未接管的儲存庫 +repos.unadopted.no_more=找不到其它未接管的儲存庫 +repos.owner=擁有者 repos.name=儲存庫名稱 -repos.private=私有庫 -repos.watches=關註數 -repos.stars=讚好數 -repos.forks=複製 +repos.private=私有 +repos.watches=關注數 +repos.stars=星號數 +repos.forks=Fork 數 repos.issues=問題數 -repos.size=由小到大 +repos.size=大小 +hooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。這裡所定義的 Webhook 是預設的,並且會複製到所有新儲存庫。在Webhook 指南閱讀更多內容。 +hooks.add_webhook=新增預設 Webhook +hooks.update_webhook=更新預設 Webhook +systemhooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。由於這裡所定義的 Webhook 會影響此系統上的所有儲存庫,因此請評估這會對效能造成多少影響。在Webhook 指南閱讀更多內容。 +systemhooks.add_webhook=新增系統 Webhook +systemhooks.update_webhook=更新系統 Webhook +auths.auth_manage_panel=認證來源管理 auths.new=新增認證來源 auths.name=認證名稱 auths.type=認證類型 @@ -1158,73 +2064,97 @@ auths.auth_name=認證名稱 auths.security_protocol=安全協定 auths.domain=域名 auths.host=主機地址 -auths.port=主機端口 -auths.bind_dn=綁定DN -auths.bind_password=綁定密碼 -auths.bind_password_helper=警告:此密碼以明文存儲。 如果可能請使用只讀帳號。 +auths.port=連接埠 +auths.bind_dn=Bind DN +auths.bind_password=Bind 密碼 +auths.bind_password_helper=警告:此密碼以明文存儲。 請儘可能使用唯讀帳戶。 auths.user_base=用戶搜尋基準 auths.user_dn=用戶 DN -auths.attribute_username=使用者名稱屬性 -auths.attribute_username_placeholder=留空將使用「Gitea」作為使用者名稱 +auths.attribute_username=帳號屬性 +auths.attribute_username_placeholder=留空將使用於 Gitea 輸入的帳號。 auths.attribute_name=名字屬性 auths.attribute_surname=姓氏屬性 auths.attribute_mail=電子郵件屬性 +auths.attribute_ssh_public_key=SSH 公鑰屬性 +auths.attributes_in_bind=從 Bind DN 中取得屬性資訊 +auths.use_paged_search=使用分頁查詢 auths.search_page_size=頁面大小 auths.filter=使用者篩選器 auths.admin_filter=管理者篩選器 +auths.restricted_filter=受限制的篩選器 +auths.restricted_filter_helper=留白則不限制任何使用者。使用米字「*」將所有不符合管理員篩選條件的使用者設定為受限。 auths.ms_ad_sa=MS AD 搜尋屬性 auths.smtp_auth=SMTP 驗證類型 auths.smtphost=SMTP 主機地址 -auths.smtpport=SMTP 主機端口 +auths.smtpport=SMTP 連接埠 auths.allowed_domains=域名白名單 +auths.allowed_domains_helper=留白以允許所有域名。以逗號 (',') 分隔多個域名。 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.oauth2_clientID=客戶端 ID (金鑰) +auths.oauth2_clientSecret=客戶端密鑰 auths.openIdConnectAutoDiscoveryURL=OpenID 連接自動探索 URL auths.oauth2_use_custom_url=使用自定義 URL 而不是預設 URL auths.oauth2_tokenURL=Token URL auths.oauth2_authURL=授權 URL -auths.oauth2_profileURL=個人訊息 URL +auths.oauth2_profileURL=個人資料 URL auths.oauth2_emailURL=電子郵件 URL auths.enable_auto_register=允許授權用戶自動註冊 +auths.sspi_auto_create_users=自動新增使用者 +auths.sspi_auto_activate_users=自動啟用使用者 +auths.sspi_strip_domain_names=從帳號中移除域名 +auths.sspi_default_language=使用者預設語言 auths.tips=幫助提示 auths.tips.oauth2.general=OAuth2 認證 -auths.tips.oauth2.general.tip=當註冊一個新的 OAuth2 認證,callback/redirect 網址應該是:/user/oauth2//callback +auths.tips.oauth2.general.tip=註冊新的 OAuth2 認證時,callback/redirect 網址應為:/user/oauth2//callback auths.tip.oauth2_provider=OAuth2 提供者 -auths.tip.dropbox=建立新 App 在 https://www.dropbox.com/developers/apps -auths.tip.facebook=在 https://developers.facebook.com/apps 註冊一個新的應用,並且新增一個產品 "Facebook Login -auths.tip.github=在 https://github.com/settings/applications/new 註冊一個新的 OAuth 應用程式 -auths.tip.gitlab=在 https://gitlab.com/profile/applications 註冊一個新的應用程式 +auths.tip.bitbucket=註冊新的 OAuth 客戶端並加入權限「Account - Read」。網址:https://bitbucket.org/account/user//oauth-consumers/new +auths.tip.nextcloud=在您的 Nextcloud 使用「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端 +auths.tip.dropbox=建立一個新的 App。網址:https://www.dropbox.com/developers/apps +auths.tip.facebook=註冊一個新的應用程式並新增產品「Facebook 登入」。網址:https://developers.facebook.com/apps +auths.tip.github=註冊新的 OAuth 應用程式。網址:https://github.com/settings/applications/new +auths.tip.gitlab=註冊新的應用程式。網址:https://gitlab.com/profile/applications +auths.tip.google_plus=從 Google API 控制台取得 OAuth2 用戶端憑證。網址:https://console.developers.google.com/ auths.tip.openid_connect=使用 OpenID 連接探索 URL (/.well-known/openid-configuration) 來指定節點 +auths.tip.twitter=建立應用程式並確保有啟用「Allow this application to be used to Sign in with Twitter」。網址:https://dev.twitter.com/apps +auths.tip.discord=註冊新的應用程式。網址:https://discordapp.com/developers/applications/me +auths.tip.gitea=註冊新的 OAuth2 應用程式。到 https://docs.gitea.io/en-us/oauth2-provider/ 觀看指南 +auths.tip.yandex=建立新的應用程式,從「Yandex.Passport API」區塊選擇「Access to email address」、「Access to user avatar」和「Access to username, first name and surname, gender」。網址:https://oauth.yandex.com/client/new auths.edit=修改認證來源 -auths.activated=該授權來源已啟用 +auths.activated=該認證來源已啟用 auths.new_success=已增加認證'%s'。 -auths.update_success=認證來源已更新。 -auths.update=更新驗證來源 -auths.delete=刪除驗證來源 +auths.update_success=已更新認證來源。 +auths.update=更新認證來源 +auths.delete=刪除認證來源 auths.delete_auth_title=刪除認證來源 +auths.delete_auth_desc=刪除認證來源將會拒絕使用它登入的使用者。是否繼續? +auths.still_in_used=此認證來源正在使用中。請先轉換或刪除使用此授權來源的使用者。 +auths.deletion_success=已刪除認證來源。 +auths.login_source_exist=認證來源「%s」已經存在。 +auths.login_source_of_type_exist=已經有相同類型的認證來源。 -config.server_config=伺服器設定 +config.server_config=伺服器組態 config.app_name=網站標題 config.app_ver=Gitea 版本 config.app_url=Gitea 基本 URL config.custom_conf=設定檔案路徑 +config.custom_file_root_path=自訂檔案根目錄 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.static_file_root_path=靜態檔案根目錄 config.log_file_root_path=日誌路徑 config.script_type=腳本類型 config.reverse_auth_user=反向代理認證 -config.ssh_config=SSH 設定 +config.ssh_config=SSH 組態 config.ssh_enabled=已啟用 config.ssh_start_builtin_server=使用內建的伺服器 config.ssh_domain=伺服器域名 @@ -1236,38 +2166,47 @@ config.ssh_keygen_path=金鑰產生 (' ssh-keygen ') 路徑 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_config=資料庫組態 config.db_type=資料庫類型 config.db_host=主機地址 config.db_name=資料庫名稱 config.db_user=使用者名稱 +config.db_schema=結構描述 config.db_ssl_mode=SSL config.db_path=資料庫路徑 -config.service_config=服務設定 +config.service_config=服務組態 config.register_email_confirm=要求註冊時確認電子郵件 config.disable_register=關閉註冊功能 config.enable_openid_signup=啟用 OpenID 註冊 config.enable_openid_signin=啟用 OpenID 登入 config.show_registration_button=顯示註冊按鈕 -config.require_sign_in_view=啓用登錄瀏覽限制 +config.require_sign_in_view=需要登入才能瀏覽頁面 config.mail_notify=啟用郵件通知 config.disable_key_size_check=禁用金鑰最小長度檢查 config.enable_captcha=啟用驗證碼 config.active_code_lives=啟用用戶連結有效期 -config.default_keep_email_private=預設隱藏電子郵件地址 +config.reset_password_code_lives=帳戶救援碼有效時間 +config.default_keep_email_private=預設隱藏電子信箱 config.default_allow_create_organization=預設允許新增組織 config.enable_timetracking=啟用時間追蹤 config.default_enable_timetracking=預設啟用時間追蹤 -config.no_reply_address=隱藏電子郵件域名 - -config.webhook_config=Webhook 設定 -config.queue_length=隊列長度 -config.deliver_timeout=推送超時 +config.default_allow_only_contributors_to_track_time=只讓貢獻者追蹤時間 +config.no_reply_address=隱藏電子信箱域名 +config.default_visibility_organization=新組織的預設瀏覽權限 +config.default_enable_dependencies=預設啟用問題的先決條件 + +config.webhook_config=Webhook 組態 +config.queue_length=佇列長度 +config.deliver_timeout=傳送逾時 config.skip_tls_verify=略過 TLS 驗證 -config.mailer_config=SMTP 設定 +config.mailer_config=SMTP 組態 config.mailer_enabled=啟用服務 config.mailer_disable_helo=禁用 HELO 操作 config.mailer_name=發送者名稱 @@ -1276,62 +2215,107 @@ config.mailer_user=發送者帳號 config.mailer_use_sendmail=使用 Sendmail config.mailer_sendmail_path=Sendmail 路徑 config.mailer_sendmail_args=Sendmail 參數 +config.test_email_placeholder=電子信箱 (例:test@example.com) config.send_test_mail=傳送測試郵件 config.test_mail_failed=傳送測試郵件到 '%s' 時失敗:'%v" config.test_mail_sent=測試郵件已發送到 '%s' -config.oauth_config=社交帳號設定 +config.oauth_config=OAuth 組態 config.oauth_enabled=啟用服務 -config.cache_config=Cache 設定 +config.cache_config=Cache 組態 config.cache_adapter=Cache 適配器 -config.cache_interval=Cache 周期 +config.cache_interval=Cache 週期 config.cache_conn=Cache 連接字符串 +config.cache_item_ttl=快取項目 TTL -config.session_config=Session 設定 +config.session_config=Session 組態 config.session_provider=Session 提供者 config.provider_config=提供者設定 config.cookie_name=Cookie 名稱 -config.enable_set_cookie=啟用設定 Cookie -config.gc_interval_time=垃圾收集周期 -config.session_life_time=Session 生命周期 +config.gc_interval_time=GC 週期 +config.session_life_time=Session 生命週期 config.https_only=僅限 HTTPS -config.cookie_life_time=Cookie 生命周期 +config.cookie_life_time=Cookie 生命週期 -config.picture_config=圖片和大頭貼設定 +config.picture_config=圖片和大頭貼組態 config.picture_service=圖片服務 -config.disable_gravatar=禁用 Gravatar 頭像 -config.enable_federated_avatar=開啟聯合頭像 +config.disable_gravatar=停用 Gravatar +config.enable_federated_avatar=啟用 Federated Avatars -config.git_config=Git 設定 +config.git_config=Git 組態 config.git_disable_diff_highlight=禁用比較語法高亮 config.git_max_diff_lines=Max Diff 線 (對於單個檔) config.git_max_diff_line_characters=最大比較的字元 (單行) config.git_max_diff_files=Max Diff 檔 (顯示) config.git_gc_args=GC 參數 -config.git_migrate_timeout=移動超時 +config.git_migrate_timeout=遷移逾時 config.git_mirror_timeout=鏡像更新超時 -config.git_clone_timeout=複製操作超時 -config.git_pull_timeout=操作超時 -config.git_gc_timeout=GC 操作超時 +config.git_clone_timeout=Clone 作業逾時 +config.git_pull_timeout=Pull 作業逾時 +config.git_gc_timeout=GC 作業逾時 -config.log_config=日誌設定 +config.log_config=日誌組態 config.log_mode=日誌模式 +config.macaron_log_mode=Macaron 日誌模式 +config.own_named_logger=具名日誌記錄器 +config.routes_to_default_logger=路由到預設日誌記錄器 +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=任務名稱 monitor.schedule=任務安排 monitor.next=下次執行時間 monitor.previous=上次執行時間 -monitor.execute_times=執行時間 -monitor.process=執行中進程 -monitor.desc=進程描述 +monitor.execute_times=執行次數 +monitor.process=執行中的處理程序 +monitor.desc=描述 monitor.start=開始時間 monitor.execute_time=已執行時間 - - - -notices.system_notice_list=系統提示管理 +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.numberworkers=工作者數量 +monitor.queue.maxnumberworkers=最大工作者數量 +monitor.queue.review=檢視組態 +monitor.queue.review_add=檢視/新增工作者 +monitor.queue.configuration=初始組態 +monitor.queue.pool.timeout=逾時 +monitor.queue.pool.addworkers.title=新增工作者 +monitor.queue.pool.addworkers.submit=新增工作者 +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.timeout.placeholder=目前 %[1]v +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.value=%[1]v + +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 個工作者嗎? + +notices.system_notice_list=系統提示 notices.view_detail_header=查看提示細節 notices.actions=操作 notices.select_all=選取全部 @@ -1341,6 +2325,7 @@ notices.delete_selected=刪除選取項 notices.delete_all=刪除所有提示 notices.type=提示類型 notices.type_1=儲存庫 +notices.type_2=任務 notices.desc=描述 notices.op=操作 notices.delete_success=已刪除系統提示。 @@ -1348,19 +2333,29 @@ notices.delete_success=已刪除系統提示。 [action] create_repo=建立了儲存庫 %s rename_repo=重新命名儲存庫 %[1]s%[3]s +commit_repo=推送了 %[3]s%[4]s create_issue=`建立了問題 %s#%[2]s` -close_issue=`已關閉問題 %s#%[2]s` -reopen_issue=`已重新開啟問題 %s#%[2]s` +close_issue=`關閉了問題 %s#%[2]s` +reopen_issue=`重新開放了問題 %s#%[2]s` create_pull_request=`建立了合併請求 %s#%[2]s` close_pull_request=`已關閉合併請求 %s#%[2]s` reopen_pull_request=`已重新開啟合併請求 %s#%[2]s` -comment_issue=`評論了問題 %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=刪除了 %[3]s 的標籤 %[2]s +delete_branch=刪除了 %[3]s 的 %[2]s 分支 +compare_branch=比較 compare_commits=比較 %d 提交 +compare_commits_general=比較提交 +approve_pull_request=`核可了 %s#%[2]s` +reject_pull_request=`建議變更 %s#%[2]s` +publish_release=`發佈了 %[3]s「%[4]s」` [tool] -ago=%s之前 +ago=%s前 from_now=%s之後 now=現在 future=未來 @@ -1368,15 +2363,15 @@ future=未來 1m=1 分鐘 1h=1 小時 1d=1 天 -1w=1 周 -1mon=1 月 +1w=1 週 +1mon=1 個月 1y=1 年 seconds=%d 秒 -minutes=%d 分 +minutes=%d 分鐘 hours=%d 小時 days=%d 天 weeks=%d 週 -months=%d 月 +months=%d 個月 years=%d 年 raw_seconds=秒 raw_minutes=分鐘 @@ -1388,7 +2383,7 @@ file_too_big=檔案大小({{filesize}} MB) 超過了最大允許大小({{maxFile remove_file=移除文件 [notification] -notifications=訊息 +notifications=通知 unread=未讀 read=已讀 no_unread=沒有未讀通知 @@ -1399,13 +2394,15 @@ mark_as_unread=標記為未讀 mark_all_as_read=標記所有為已讀 [gpg] +default_key=使用預設金鑰簽署 error.extract_sign=無法提取簽署 error.generate_hash=無法產生提交的雜湊值 -error.no_committer_account=沒有連結到貢獻者的電子郵件帳戶。 +error.no_committer_account=提交者的電子信箱沒有連結到任何帳戶 error.no_gpg_keys_found=沒有發現已知的金鑰在資料庫的簽署中 error.not_signed_commit=未簽名的提交 error.failed_retrieval_gpg_keys=找不到任何與該提交者帳戶相關的金鑰 [units] +error.no_unit_allowed_repo=您未被允許存取此儲存庫的任何區域。 error.unit_not_allowed=您未被允許訪問此儲存庫區域 diff --git a/package-lock.json b/package-lock.json index 282e55e3f1bd..05f47a153d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,28 +11,23 @@ } }, "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.1.tgz", + "integrity": "sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ==" }, "@babel/core": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.1.tgz", - "integrity": "sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.1", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -44,11 +39,11 @@ } }, "@babel/generator": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", - "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", "requires": { - "@babel/types": "^7.11.0", + "@babel/types": "^7.12.1", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -71,38 +66,36 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz", + "integrity": "sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g==", "requires": { - "@babel/compat-data": "^7.10.4", + "@babel/compat-data": "^7.12.1", + "@babel/helper-validator-option": "^7.12.1", "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", "semver": "^5.5.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", "requires": { "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-member-expression-to-functions": "^7.12.1", "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", + "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" + "regexpu-core": "^4.7.1" } }, "@babel/helper-define-map": { @@ -116,12 +109,11 @@ } }, "@babel/helper-explode-assignable-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", - "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "requires": { - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.1" } }, "@babel/helper-function-name": { @@ -151,32 +143,34 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", + "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", "requires": { - "@babel/types": "^7.11.0" + "@babel/types": "^7.12.1" } }, "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", + "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.1" } }, "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", "lodash": "^4.17.19" } }, @@ -202,43 +196,40 @@ } }, "@babel/helper-remap-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", - "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.1" } }, "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", + "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1" } }, "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.1" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", - "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", "requires": { - "@babel/types": "^7.11.0" + "@babel/types": "^7.12.1" } }, "@babel/helper-split-export-declaration": { @@ -254,10 +245,15 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, + "@babel/helper-validator-option": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", + "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==" + }, "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "requires": { "@babel/helper-function-name": "^7.10.4", "@babel/template": "^7.10.4", @@ -266,13 +262,13 @@ } }, "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", + "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", "requires": { "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1" } }, "@babel/highlight": { @@ -286,127 +282,127 @@ } }, "@babel/parser": { - "version": "7.11.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz", - "integrity": "sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==" + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", + "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-create-class-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", - "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", - "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz", + "integrity": "sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" + "@babel/plugin-transform-parameters": "^7.12.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz", + "integrity": "sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", - "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-create-class-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-create-regexp-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, @@ -419,9 +415,9 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -499,331 +495,331 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", "requires": { - "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-module-imports": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" + "@babel/helper-remap-async-to-generator": "^7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", + "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-define-map": "^7.10.4", "@babel/helper-function-name": "^7.10.4", "@babel/helper-optimise-call-expression": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-create-regexp-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "requires": { "@babel/helper-function-name": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "requires": { - "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-module-transforms": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "requires": { - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "requires": { "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-module-transforms": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", "requires": { - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" + "@babel/helper-replace-supers": "^7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "requires": { - "@babel/helper-get-function-arity": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-runtime": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.0.tgz", - "integrity": "sha512-LFEsP+t3wkYBlis8w6/kmnd6Kb1dxTd+wGJ8MlxTGzQo//ehtqlVL4S9DNUa53+dtPSQobN2CXx4d81FqC58cw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", "requires": { - "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-module-imports": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", "resolve": "^1.8.1", "semver": "^5.5.1" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz", + "integrity": "sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", + "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", - "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-create-regexp-features-plugin": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz", - "integrity": "sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", + "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", "requires": { - "@babel/compat-data": "^7.11.0", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", + "@babel/compat-data": "^7.12.1", + "@babel/helper-compilation-targets": "^7.12.1", + "@babel/helper-module-imports": "^7.12.1", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.11.0", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.1", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-class-properties": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", @@ -833,52 +829,49 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.11.0", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.1", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.0", - "browserslist": "^4.12.0", + "@babel/types": "^7.12.1", "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", "semver": "^5.5.0" } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -888,27 +881,20 @@ } }, "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", + "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", "requires": { "regenerator-runtime": "^0.13.4" } }, - "@babel/runtime-corejs2": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.11.2.tgz", - "integrity": "sha512-AC/ciV28adSSpEkBglONBWq4/Lvm6GAZuxIoyVtsnUpZMl0bxLtoChEnYAkP+47KyOCayZanojtflUEUJtR/6Q==", + "@babel/runtime-corejs3": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz", + "integrity": "sha512-umhPIcMrlBZ2aTWlWjUseW9LjQKxi1dpFlQS8DzsxB//5K+u6GLTC/JliPKHsd5kJVPIU6X/Hy0YvWOYPcMxBw==", "requires": { - "core-js": "^2.6.5", + "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - } } }, "@babel/template": { @@ -922,25 +908,25 @@ } }, "@babel/traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", - "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", + "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", + "@babel/generator": "^7.12.1", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/parser": "^7.12.1", + "@babel/types": "^7.12.1", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", - "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", + "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", "requires": { "@babel/helper-validator-identifier": "^7.10.4", "lodash": "^4.17.19", @@ -957,10 +943,56 @@ "resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.5.tgz", "integrity": "sha512-LpiN8hyqRPYB2tEzFD4lI54GxKHQXhzrJMnKnsumElYxjkjbdAPmiIm+1k/Mkfn92HepL7t9uaK5iQSFP/19aw==" }, - "@csstools/convert-colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + "@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } }, "@kyleshockey/object-assign-deep": { "version": "0.4.2", @@ -1156,17 +1188,17 @@ } }, "@octokit/types": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.4.0.tgz", - "integrity": "sha512-D/uotqF69M50OIlwMqgyIg9PuLT2daOiBAYF0P40I2ekFA2ESwwBY5dxZe/UhXdPvIbNKDzuZmQrO7rMpuFbcg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.4.1.tgz", + "integrity": "sha512-OlMlSySBJoJ6uozkr/i03nO5dlYQyE05vmQNZhAh9MyO4DPBP88QlwsDVLmVjIMFssvIZB6WO0ctIGMRG+xsJQ==", "requires": { "@types/node": ">= 8" } }, "@primer/octicons": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-10.0.0.tgz", - "integrity": "sha512-iuQubq62zXZjPmaqrsfsCZUqIJgZhmA6W0tKzIKGRbkoLnff4TFFCL87hfIRATZ5qZPM4m8ioT8/bXI7WVa9WQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-11.0.0.tgz", + "integrity": "sha512-aMM2n7dl4ToEqQH9ZWQ8M8alGCoGsRk2k5hT5h3KXd54YFKte1twhJDvyQjIjjxqggNh5NUfyuqTOv6MPCVSKQ==", "requires": { "object-assign": "^4.1.1" } @@ -1195,10 +1227,18 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/hast": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", + "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", + "requires": { + "@types/unist": "*" + } + }, "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" }, "@types/json5": { "version": "0.0.29", @@ -1213,9 +1253,9 @@ "dev": true }, "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" + "version": "14.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", + "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1251,8 +1291,7 @@ "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", - "dev": true + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" }, "@types/webpack-sources": { "version": "0.1.8", @@ -1287,10 +1326,24 @@ "vue-template-es2015-compiler": "^1.9.0" }, "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -1500,9 +1553,9 @@ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "add-asset-webpack-plugin": { @@ -1511,18 +1564,18 @@ "integrity": "sha512-Pg6UTxMPmVpGobpBTBmTO54aKvZfnY/rm7YvY86BXv4toE8I29ail6kAzsf0GyEMK6MgvGVDxs4IZXPTCUooNw==" }, "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2237,9 +2290,9 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==" + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" }, "boolbase": { "version": "1.0.0", @@ -2360,14 +2413,14 @@ } }, "browserslist": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", - "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", + "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", "requires": { - "caniuse-lite": "^1.0.30001111", - "electron-to-chromium": "^1.3.523", + "caniuse-lite": "^1.0.30001125", + "electron-to-chromium": "^1.3.564", "escalade": "^3.0.2", - "node-releases": "^1.1.60" + "node-releases": "^1.1.61" } }, "btoa": { @@ -2433,39 +2486,10 @@ "unique-filename": "^1.1.1" }, "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -2516,9 +2540,9 @@ } }, "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==" }, "camelcase-keys": { "version": "6.2.2", @@ -2551,9 +2575,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001113", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001113.tgz", - "integrity": "sha512-qMvjHiKH21zzM/VDZr6oosO6Ri3U0V2tC015jRXjOecwQCJtsU5zklTNTk31jQbIOP8gha0h1ccM/g0ECP+4BA==" + "version": "1.0.30001125", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001125.tgz", + "integrity": "sha512-9f+r7BW8Qli917mU3j0fUaTweT3f3vnX/Lcs+1C73V+RADmFme+Ih0Br8vONQi3X0lseOe6ZHfsZLCA8MSjxUA==" }, "ccount": { "version": "1.0.5", @@ -2925,12 +2949,12 @@ } }, "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", "requires": { "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-string": "^1.5.4" } }, "color-convert": { @@ -2947,9 +2971,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -3086,6 +3110,16 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } } }, "copy-descriptor": { @@ -3141,6 +3175,11 @@ } } }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3199,21 +3238,20 @@ } }, "create-react-class": { - "version": "15.6.3", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", "requires": { - "fbjs": "^0.8.9", "loose-envify": "^1.3.1", "object-assign": "^4.1.1" } }, "cross-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz", - "integrity": "sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz", + "integrity": "sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==", "requires": { - "node-fetch": "2.6.0" + "node-fetch": "2.6.1" } }, "cross-spawn": { @@ -3269,14 +3307,6 @@ "resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz", "integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI=" }, - "css-blank-pseudo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "requires": { - "postcss": "^7.0.5" - } - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3291,52 +3321,36 @@ "timsort": "^0.3.0" } }, - "css-has-pseudo": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", - "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^5.0.0-rc.4" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, "css-loader": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.2.1.tgz", - "integrity": "sha512-MoqmF1if7Z0pZIEXA4ZF9PgtCXxWbfzfJM+3p+OYfhcrwcqhaCRb74DSnfzRl7e024xEiCRn5hCvfUbTf2sgFA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.0.tgz", + "integrity": "sha512-9g35eXRBgjvswyJWoqq/seWp+BOxvUl8IinVNTsUBFFxtwfEYvlmEn6ciyn0liXGbGh5HyJjPGCuobDSfqMIVg==", "requires": { - "camelcase": "^6.0.0", + "camelcase": "^6.1.0", "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", + "icss-utils": "^5.0.0", "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.3", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", + "postcss": "^8.1.1", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", + "schema-utils": "^3.0.0", "semver": "^7.3.2" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -3347,29 +3361,66 @@ "json5": "^2.1.2" } }, + "postcss": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.1.2.tgz", + "integrity": "sha512-mToqEVFq8jF9TFhlIK4HhE34zknFJuNTgqtsr60vUvrWn+9TIYugCwiV1JZRxCuOrej2jjstun1bn4Bc7/1HkA==", + "requires": { + "colorette": "^1.2.1", + "line-column": "^1.0.2", + "nanoid": "^3.1.12", + "source-map": "^0.6.1" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "css-minimizer-webpack-plugin": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-1.1.1.tgz", - "integrity": "sha512-x6TUYLXeg3PotX9l6zbna/61S8YNYrCVDlav72ORHu5hKfbSEQ6HIGpQpYBNvbc9KjT7KaWRQpHSXvWxmctcgQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-1.1.5.tgz", + "integrity": "sha512-mXgaoFjNpIudZfxD49N1aPtLxfXGJt+BVPVjQ+H66I48b5n4wJtFpYfffVr7izK8W6fD01J7K0kUcP6HGjw90w==", "requires": { "cacache": "^15.0.5", "cssnano": "^4.1.10", "find-cache-dir": "^3.3.1", "jest-worker": "^26.3.0", "p-limit": "^3.0.2", - "schema-utils": "^2.7.0", - "serialize-javascript": "^4.0.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", "webpack-sources": "^1.4.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -3444,11 +3495,29 @@ "find-up": "^4.0.0" } }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3456,14 +3525,6 @@ } } }, - "css-prefers-color-scheme": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "requires": { - "postcss": "^7.0.5" - } - }, "css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -3506,11 +3567,6 @@ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" }, - "cssdb": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" - }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3685,9 +3741,9 @@ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" }, "d3-brush": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", - "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -3748,9 +3804,9 @@ } }, "d3-ease": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", - "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, "d3-fetch": { "version": "1.2.0", @@ -3772,9 +3828,9 @@ } }, "d3-format": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", - "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "d3-geo": { "version": "1.12.1", @@ -3858,9 +3914,9 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz", - "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", "requires": { "d3-time": "1" } @@ -3931,11 +3987,11 @@ "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "decamelize": { @@ -4075,10 +4131,23 @@ "rimraf": "^2.2.8" }, "dependencies": { + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } } } }, @@ -4200,9 +4269,9 @@ } }, "dompurify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.12.tgz", - "integrity": "sha512-Fl8KseK1imyhErHypFPA8qpq9gPzlsJ/EukA6yk9o0gX23p1TzC+rh9LqNg1qvErRTc0UNMYlKxEGSfSh43NDg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.1.1.tgz", + "integrity": "sha512-NijiNVkS/OL8mdQL1hUbCD6uty/cgFpmNiuFxrmJ5YPH2cXrPKIewoixoji56rbZ6XBPmtM8GA8/sf9unlSuwg==" }, "domutils": { "version": "1.7.0", @@ -4214,9 +4283,9 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "requires": { "is-obj": "^2.0.0" } @@ -4301,12 +4370,28 @@ "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } } }, "electron-to-chromium": { - "version": "1.3.529", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.529.tgz", - "integrity": "sha512-n3sriLldqNyjBlosbnPftjCY+m1dVOY307I1Y0HaHAqDGe3hRvK7ksJwWd+qs599ybR4jobCo1+7zXM9GyNMSA==" + "version": "1.3.564", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.564.tgz", + "integrity": "sha512-fNaYN3EtKQWLQsrKXui8mzcryJXuA0LbCLoizeX6oayG2emBaS5MauKjCPAvc29NEY4FpLHIUWiP+Y0Bfrs5dg==" }, "elliptic": { "version": "6.5.3", @@ -4521,22 +4606,23 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.6.0.tgz", - "integrity": "sha512-QlAManNtqr7sozWm5TF4wIH9gmUm2hE3vNRUvyoYAa4y1l5/jxD/PQStEjBMQtCqZmSep8UxrcecI60hOpe61w==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", + "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.1.3", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", - "eslint-scope": "^5.1.0", + "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^1.3.0", - "espree": "^7.2.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.0", "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -4571,12 +4657,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -4617,15 +4702,21 @@ } }, "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -4700,9 +4791,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -4836,9 +4927,9 @@ } }, "eslint-plugin-import": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", - "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -4846,7 +4937,7 @@ "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", + "eslint-import-resolver-node": "^0.3.4", "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", @@ -4990,18 +5081,18 @@ } }, "eslint-plugin-unicorn": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-21.0.0.tgz", - "integrity": "sha512-S8v7+v4gZTQPj4pKKvexhgSUaLQSyItvxW2SVZDaX9Iu5IjlAmF2eni+L6w8a2aqshxgU8Lle4FIAVDtuejSKQ==", + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-23.0.0.tgz", + "integrity": "sha512-Vabo3cjl6cjyhcf+76CdQEY6suOFzK0Xh3xo0uL9VDYrDJP5+B6PjV0tHTYm82WZmFWniugFJM3ywHSNYTi/ZQ==", "dev": true, "requires": { "ci-info": "^2.0.0", "clean-regexp": "^1.0.0", "eslint-ast-utils": "^1.1.0", - "eslint-template-visitor": "^2.0.0", + "eslint-template-visitor": "^2.2.1", "eslint-utils": "^2.1.0", "import-modules": "^2.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "pluralize": "^8.0.0", "read-pkg-up": "^7.0.1", "regexp-tree": "^0.1.21", @@ -5039,14 +5130,14 @@ } }, "parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -5140,20 +5231,20 @@ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" }, "espree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.2.0.tgz", - "integrity": "sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", "dev": true, "requires": { - "acorn": "^7.3.1", + "acorn": "^7.4.0", "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.3.0" }, "dependencies": { "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true } } @@ -5181,11 +5272,18 @@ } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } } }, "estraverse": { @@ -5306,9 +5404,9 @@ }, "dependencies": { "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" } } }, @@ -5463,9 +5561,14 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=" + }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, "fastq": { @@ -5528,14 +5631,25 @@ } }, "file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" + "schema-utils": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -5545,6 +5659,16 @@ "emojis-list": "^3.0.0", "json5": "^2.1.2" } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } } } }, @@ -5772,11 +5896,6 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, - "flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" - }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -5787,9 +5906,9 @@ } }, "fomantic-ui": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/fomantic-ui/-/fomantic-ui-2.8.6.tgz", - "integrity": "sha512-4/CU9XeaX42LHWmajOxJf2f3vXK++qOG5nhEv9Gjw7T7dA4MzI5I5XSpoRkQ2L5u3v4c+iiPKJAJpExWtvh23w==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/fomantic-ui/-/fomantic-ui-2.8.7.tgz", + "integrity": "sha512-u22d28Z+U8mduTIM50MYzBGRz7CXYjGs2fUY6KO8N3enE8OAatDOXV4Mb/Xvj/ck5aNE6er6XJNK1fFWXt/u/w==", "requires": { "@octokit/rest": "^16.16.0", "better-console": "1.0.1", @@ -6152,9 +6271,9 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, "gsap": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.4.2.tgz", - "integrity": "sha512-2DMG5x9IeQ7csnVaGxmhN4v06sJ4MklqYETMBjfmPQoPel4O4yTvpnINKLDYz7D22xNRwwyd1YWWxpkKgYkuiw==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.5.1.tgz", + "integrity": "sha512-EMV0RSUKZNeTUzLKAizGlwxVOUyif3/g8I3S1aA/hf3gbqwBvmQ02x1RdTBQNQMOpHCVBv9y/vaHwfctoAg8zw==" }, "gulp": { "version": "4.0.2", @@ -6973,9 +7092,9 @@ }, "dependencies": { "uglify-js": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz", - "integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==" + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.4.tgz", + "integrity": "sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw==" } } }, @@ -7200,10 +7319,11 @@ "integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA==" }, "hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", "requires": { + "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", @@ -7221,9 +7341,9 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, "highlight.js": { - "version": "9.15.10", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", - "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==" + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.3.1.tgz", + "integrity": "sha512-jeW8rdPdhshYKObedYg5XGbpVgb1/DT4AHvDFXhkU7UnGSIjy9kkJ7zHG7qplhFHMitTSzh5/iClKQk3Kb2RFQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -7286,9 +7406,9 @@ }, "dependencies": { "uglify-js": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz", - "integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==" + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.3.tgz", + "integrity": "sha512-wDRziHG94mNj2n3R864CvYw/+pc9y/RNImiTyrrf8BzgWn75JgFSwYvXrtZQMnMnOp/4UTrf3iCSQxSStPiByA==" } } }, @@ -7345,12 +7465,9 @@ } }, "icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "requires": { - "postcss": "^7.0.14" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.0.0.tgz", + "integrity": "sha512-aF2Cf/CkEZrI/vsu5WI/I+akFgdbwQHVE9YRZxATrhH4PVIe6a3BIjwjEcW+z+jP/hNh+YvM3lAAn1wJQ6opSg==" }, "ieee754": { "version": "1.1.13", @@ -7379,14 +7496,6 @@ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=" }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "requires": { - "import-from": "^2.1.0" - } - }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -7396,14 +7505,6 @@ "resolve-from": "^3.0.0" } }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "requires": { - "resolve-from": "^3.0.0" - } - }, "import-lazy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", @@ -7793,9 +7894,10 @@ } }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true }, "is-plain-object": { "version": "4.1.1", @@ -7965,9 +8067,9 @@ } }, "jest-worker": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", - "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -7980,9 +8082,9 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { "has-flag": "^4.0.0" } @@ -8003,15 +8105,15 @@ } }, "js-beautify": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.11.0.tgz", - "integrity": "sha512-a26B+Cx7USQGSWnz9YxgJNMmML/QG2nqIaL7VVYPCXbqiKz8PN0waSNvroMtvAK6tY7g/wPdNWGEP+JTNIBr6A==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", + "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", "requires": { "config-chain": "^1.1.12", "editorconfig": "^0.15.3", "glob": "^7.1.3", - "mkdirp": "~1.0.3", - "nopt": "^4.0.3" + "mkdirp": "^1.0.4", + "nopt": "^5.0.0" }, "dependencies": { "mkdirp": { @@ -8050,6 +8152,12 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -8083,6 +8191,11 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==" + }, "known-css-properties": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.19.0.tgz", @@ -8151,16 +8264,26 @@ } }, "less-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-6.2.0.tgz", - "integrity": "sha512-Cl5h95/Pz/PWub/tCBgT1oNMFeH1WTD33piG80jn5jr12T4XbxZcjThwNXDQ7AG649WEynuIzO4b0+2Tn9Qolg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-7.0.2.tgz", + "integrity": "sha512-7MKlgjnkCf63E3Lv6w2FvAEgLMx3d/tNBExITcanAq7ys5U8VPWT3F6xcRjYmdNfkoQ9udoVFb1r2azSiTnD6w==", "requires": { - "clone": "^2.1.2", - "less": "^3.11.3", + "klona": "^2.0.4", "loader-utils": "^2.0.0", - "schema-utils": "^2.7.0" + "schema-utils": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -8170,22 +8293,19 @@ "emojis-list": "^3.0.0", "json5": "^2.1.2" } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } } } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "requires": { - "leven": "^3.1.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8230,6 +8350,25 @@ } } }, + "line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=", + "requires": { + "isarray": "^1.0.0", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -8298,9 +8437,14 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" }, "lodash._baseassign": { "version": "3.2.0", @@ -8540,12 +8684,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -8581,9 +8724,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -8616,21 +8759,20 @@ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" }, "lowlight": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz", - "integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.16.0.tgz", + "integrity": "sha512-ECLdzIJvBEjK4ef51sWiGZyz21yx4IEPaF/62DRxLehoOHkWqN3OsLB1GUMfc6Mcf87rR5eW7z6lI9cNEXZDsQ==", "requires": { - "fault": "^1.0.2", - "highlight.js": "~9.15.0" + "fault": "^1.0.0", + "highlight.js": "~10.3.0" } }, "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^4.0.0" } }, "lru-queue": { @@ -8897,9 +9039,9 @@ } }, "meow": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.0.tgz", - "integrity": "sha512-kq5F0KVteskZ3JdfyQFivJEj2RaA8NFsS4+r9DaMKLcUHpk5OcHS3Q0XkCXONB1mZRPsu/Y/qImKri0nwSEZog==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", "dev": true, "requires": { "@types/minimist": "^1.2.0", @@ -8950,14 +9092,14 @@ } }, "parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -9050,9 +9192,9 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "mermaid": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.7.0.tgz", - "integrity": "sha512-SkinxAY3sIdML+o5U4U7rQEIa628OEywEw+pfhc3wSVDFqLk2XNdX2j3YmkyCw3Kcbp9BXar533ei+/saYBs5g==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.2.tgz", + "integrity": "sha512-Ib9jl5TMwgMKv2+vdfKZ/SIUxKYc4GMauKeV2+3BD/bU47kGbc5zv5taCY5iZR+V4hdweHKE7YOl11VGcWBy/w==", "requires": { "@braintree/sanitize-url": "^3.1.0", "babel-eslint": "^10.1.0", @@ -9124,35 +9266,44 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.10.0.tgz", - "integrity": "sha512-QgKgJBjaJhxVPwrLNqqwNS0AGkuQQ31Hp4xGXEK/P7wehEg6qmNtReHKai3zRXqY60wGVWLYcOMJK2b98aGc3A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.1.1.tgz", + "integrity": "sha512-pzlnOi/lMkwIkdb7zoRQvbkW18AFCQffouSBpxy+e3pnKTKMC5IuMVHYndexKZmacfsOZS2LXCe8gIgkrC+yqg==", "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", "webpack-sources": "^1.1.0" }, "dependencies": { - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -9203,6 +9354,14 @@ "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", "kind-of": "^6.0.3" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + } } }, "minipass": { @@ -9211,13 +9370,6 @@ "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", "requires": { "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } } }, "minipass-collect": { @@ -9245,19 +9397,12 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } } }, "mississippi": { @@ -9318,16 +9463,28 @@ "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" }, "monaco-editor": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.20.0.tgz", - "integrity": "sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==" + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.21.2.tgz", + "integrity": "sha512-jS51RLuzMaoJpYbu7F6TPuWpnWTLD4kjRW0+AZzcryvbxrTwhNy1KC9yboyKpgMTahpUbDUsuQULoo0GV1EPqg==" }, "monaco-editor-webpack-plugin": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.9.0.tgz", - "integrity": "sha512-tOiiToc94E1sb50BgZ8q8WK/bxus77SRrwCqIpAB5er3cpX78SULbEBY4YPOB8kDolOzKRt30WIHG/D6gz69Ww==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-2.0.0.tgz", + "integrity": "sha512-z3nUGhnNis8eHcPepqrxyt3XwrnHD76E4KwV2ozWlBwYM6B3R5YYqzy40ECfJWqRFcwT4DhaLYaXOk5ym4MZhA==", "requires": { - "loader-utils": "^1.2.3" + "loader-utils": "^2.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } } }, "move-concurrently": { @@ -9341,6 +9498,16 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } } }, "ms": { @@ -9378,6 +9545,11 @@ "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "optional": true }, + "nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9432,9 +9604,9 @@ } }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-libs-browser": { "version": "2.2.1", @@ -9496,9 +9668,9 @@ } }, "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==" + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==" }, "node.extend": { "version": "2.0.2", @@ -9510,12 +9682,11 @@ } }, "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1" } }, "normalize-package-data": { @@ -9736,6 +9907,14 @@ "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" + }, + "dependencies": { + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + } } }, "ordered-read-streams": { @@ -9778,15 +9957,6 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -9809,9 +9979,12 @@ } }, "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } }, "p-try": { "version": "2.2.0", @@ -9871,9 +10044,9 @@ } }, "parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", "requires": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", @@ -10098,72 +10271,16 @@ } } }, - "postcss-attribute-case-insensitive": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", - "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^6.0.2" - } - }, "postcss-calc": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.3.tgz", - "integrity": "sha512-IB/EAEmZhIMEIhG7Ov4x+l47UaXOS1n2f4FBUk/aKllQhtSCxWhTzn0nJgkqN7fo/jcWySvWTSB6Syk9L+31bA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "requires": { "postcss": "^7.0.27", "postcss-selector-parser": "^6.0.2", "postcss-value-parser": "^4.0.2" } }, - "postcss-color-functional-notation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", - "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-gray": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", - "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-hex-alpha": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", - "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", - "requires": { - "postcss": "^7.0.14", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-color-mod-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", - "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", - "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, "postcss-colormin": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", @@ -10199,75 +10316,6 @@ } } }, - "postcss-custom-media": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "requires": { - "postcss": "^7.0.14" - } - }, - "postcss-custom-properties": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", - "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", - "requires": { - "postcss": "^7.0.17", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-custom-selectors": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", - "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-dir-pseudo-class": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", - "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, "postcss-discard-comments": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", @@ -10300,167 +10348,22 @@ "postcss": "^7.0.0" } }, - "postcss-double-position-gradients": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", - "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", - "requires": { - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-env-function": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", - "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-focus-visible": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-focus-within": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-font-variant": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", - "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-gap-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "requires": { - "postcss": "^7.0.2" - } - }, "postcss-html": { "version": "0.36.0", "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", "dev": true, "requires": { - "htmlparser2": "^3.10.0" - } - }, - "postcss-image-set-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", - "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", - "requires": { - "lodash.template": "^4.5.0", - "postcss": "^7.0.2" - }, - "dependencies": { - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "requires": { - "lodash._reinterpolate": "^3.0.0" - } - } - } - }, - "postcss-lab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", - "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-less": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", - "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", - "dev": true, - "requires": { - "postcss": "^7.0.14" - } - }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "postcss-logical": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "requires": { - "postcss": "^7.0.2" + "htmlparser2": "^3.10.0" } }, - "postcss-media-minmax": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, "requires": { - "postcss": "^7.0.2" + "postcss": "^7.0.14" } }, "postcss-media-query-parser": { @@ -10590,48 +10493,47 @@ } }, "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "requires": { - "postcss": "^7.0.5" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" }, "postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", "requires": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", + "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", "postcss-value-parser": "^4.1.0" } }, "postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - } - }, - "postcss-modules-values": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" + "postcss-selector-parser": "^6.0.4" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + } } }, - "postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "requires": { - "postcss": "^7.0.2" + "icss-utils": "^5.0.0" } }, "postcss-normalize-charset": { @@ -10797,101 +10699,6 @@ } } }, - "postcss-overflow-shorthand": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-page-break": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-place": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", - "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-preset-env": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", - "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", - "requires": { - "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", - "caniuse-lite": "^1.0.30000981", - "css-blank-pseudo": "^0.1.4", - "css-has-pseudo": "^0.10.0", - "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.4.0", - "postcss": "^7.0.17", - "postcss-attribute-case-insensitive": "^4.0.1", - "postcss-color-functional-notation": "^2.0.1", - "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.3", - "postcss-color-mod-function": "^3.0.3", - "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.8", - "postcss-custom-properties": "^8.0.11", - "postcss-custom-selectors": "^5.1.2", - "postcss-dir-pseudo-class": "^5.0.0", - "postcss-double-position-gradients": "^1.0.0", - "postcss-env-function": "^2.0.2", - "postcss-focus-visible": "^4.0.0", - "postcss-focus-within": "^3.0.0", - "postcss-font-variant": "^4.0.0", - "postcss-gap-properties": "^2.0.0", - "postcss-image-set-function": "^3.0.1", - "postcss-initial": "^3.0.0", - "postcss-lab-function": "^2.0.1", - "postcss-logical": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nesting": "^7.0.0", - "postcss-overflow-shorthand": "^2.0.0", - "postcss-page-break": "^2.0.0", - "postcss-place": "^4.0.1", - "postcss-pseudo-class-any-link": "^6.0.0", - "postcss-replace-overflow-wrap": "^3.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0" - } - }, - "postcss-pseudo-class-any-link": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", - "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, "postcss-reduce-initial": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", @@ -10921,37 +10728,6 @@ } } }, - "postcss-replace-overflow-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" - }, - "dependencies": { - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - } - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -10986,24 +10762,6 @@ "postcss": "^7.0.6" } }, - "postcss-selector-matches": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", - "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "postcss-selector-not": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", - "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, "postcss-selector-parser": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", @@ -11053,27 +10811,12 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, - "postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, "prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", @@ -11086,9 +10829,9 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" }, "prismjs": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz", - "integrity": "sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.22.0.tgz", + "integrity": "sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==", "requires": { "clipboard": "^2.0.0" } @@ -11133,9 +10876,9 @@ } }, "property-information": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.5.0.tgz", - "integrity": "sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", "requires": { "xtend": "^4.0.0" } @@ -11220,15 +10963,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -11245,9 +10979,9 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "quick-lru": { "version": "4.0.1", @@ -11288,14 +11022,25 @@ } }, "raw-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.1.tgz", - "integrity": "sha512-baolhQBSi3iNh1cglJjA0mYzga+wePk7vdEX//1dTFd+v4TsQlQE0jitJSNF1OIP82rdYulH7otaVmdlDaJ64A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" + "schema-utils": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -11305,13 +11050,23 @@ "emojis-list": "^3.0.0", "json5": "^2.1.2" } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } } } }, "react": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", - "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/react/-/react-15.7.0.tgz", + "integrity": "sha512-5/MMRYmpmM0sMTHGLossnJCrmXQIiJilD6y3YN3TzAwGFj6zdnMtFv6xmi65PHKRV+pehIHpT7oy67Sr6s9AHA==", "requires": { "create-react-class": "^15.6.0", "fbjs": "^0.8.9", @@ -11339,9 +11094,9 @@ } }, "react-dom": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", - "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.7.0.tgz", + "integrity": "sha512-mpjXqC2t1FuYsILOLCj0kg6pbg460byZkVA/80VtDmKU/pYmoTdHOtaMcTRIDiyXLz4sIur0cQ04nOC6iGndJg==", "requires": { "fbjs": "^0.8.9", "loose-envify": "^1.1.0", @@ -11377,11 +11132,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "react-motion": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", @@ -11393,29 +11143,28 @@ } }, "react-redux": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz", - "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.10.tgz", + "integrity": "sha512-tjL0Bmpkj75Td0k+lXlF8Fc8a9GuXFv/3ahUOCXExWs/jhsKiQeTffdH0j5byejCGCRL4tvGFYlrwBF1X/Aujg==", "requires": { - "@babel/runtime": "^7.1.2", + "create-react-class": "^15.5.1", "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.1.0", - "prop-types": "^15.6.1", - "react-is": "^16.6.0", - "react-lifecycles-compat": "^3.0.0" + "invariant": "^2.0.0", + "lodash": "^4.17.11", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2" } }, "react-syntax-highlighter": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", - "integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-13.5.0.tgz", + "integrity": "sha512-2nKo8spFxe9shcjbdUiqxkrf/IMDqKUZLx7JVIxEJ17P+fYFGL4CRsZZC66UPeQ2o/f29eKu31CrkKGCK1RHuA==", "requires": { "@babel/runtime": "^7.3.1", - "highlight.js": "~9.15.1", - "lowlight": "1.12.1", - "prismjs": "^1.8.4", - "refractor": "^2.4.1" + "highlight.js": "^10.1.1", + "lowlight": "^1.14.0", + "prismjs": "^1.21.0", + "refractor": "^3.1.0" } }, "read-pkg": { @@ -11596,12 +11345,14 @@ } }, "redux": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", - "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" } }, "redux-immutable": { @@ -11613,23 +11364,13 @@ } }, "refractor": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz", - "integrity": "sha512-Xh9o7hQiQlDbxo5/XkOX6H+x/q8rmlmZKr97Ie1Q8ZM32IRRd3B/UxuA/yXDW79DBSXGWxm2yRTbcTVmAciJRw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.2.0.tgz", + "integrity": "sha512-hSo+EyMIZTLBvNNgIU5lW4yjCzNYMZ4dcEhBq/3nReGfqzd2JfVhdlPDfU9rEsgcAyWx+OimIIUoL4ZU7NtYHQ==", "requires": { - "hastscript": "^5.0.0", - "parse-entities": "^1.1.2", - "prismjs": "~1.17.0" - }, - "dependencies": { - "prismjs": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", - "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", - "requires": { - "clipboard": "^2.0.0" - } - } + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.22.0" } }, "regenerate": { @@ -11680,9 +11421,9 @@ "dev": true }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "requires": { "regenerate": "^1.4.0", "regenerate-unicode-properties": "^8.2.0", @@ -11750,22 +11491,6 @@ "unist-util-remove-position": "^2.0.0", "vfile-location": "^3.0.0", "xtend": "^4.0.1" - }, - "dependencies": { - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - } } }, "remark-stringify": { @@ -11788,22 +11513,6 @@ "stringify-entities": "^3.0.0", "unherit": "^1.0.4", "xtend": "^4.0.1" - }, - "dependencies": { - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - } } }, "remarkable": { @@ -12069,9 +11778,9 @@ } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } @@ -12086,9 +11795,9 @@ } }, "rtlcss": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.5.0.tgz", - "integrity": "sha512-NCVdF45w70/3CQeqVvQ84bu2HN8agNn+CDjw+RxXaiWb7mPOmEvltdd1z4qzm9kin4Jnu9ShFBIx28yvWerZ2g==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.6.0.tgz", + "integrity": "sha512-eRctJtYmVFgCUeGMm6uzrJE0P/1ipuaKHY9TaksDt+UZ7SLMVoatQZePPFAJeSNJ8eh3QJ/ROb44bl0wgaXksQ==", "requires": { "chalk": "^2.4.2", "findup": "^0.1.5", @@ -12138,9 +11847,9 @@ "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, "rxjs": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", - "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "requires": { "tslib": "^1.9.0" } @@ -12169,13 +11878,13 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "select": { @@ -12435,18 +12144,10 @@ } } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, "sortablejs": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz", - "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.12.0.tgz", + "integrity": "sha512-bPn57rCjBRlt2sC24RBsu40wZsmLkSo2XeqG8k6DC1zru5eObQUIPPZAQG7W2SJ8FZQYq+BEJmvuw1Zxb3chqg==" }, "source-list-map": { "version": "2.0.1", @@ -12644,11 +12345,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -12686,16 +12382,14 @@ } }, "stringify-entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", - "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", + "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.2", - "is-hexadecimal": "^1.0.0" + "xtend": "^4.0.0" } }, "stringify-object": { @@ -12796,19 +12490,21 @@ } }, "stylelint": { - "version": "13.6.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.6.1.tgz", - "integrity": "sha512-XyvKyNE7eyrqkuZ85Citd/Uv3ljGiuYHC6UiztTR6sWS9rza8j3UeQv/eGcQS9NZz/imiC4GKdk1EVL3wst5vw==", + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.7.2.tgz", + "integrity": "sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==", "dev": true, "requires": { - "@stylelint/postcss-css-in-js": "^0.37.1", + "@stylelint/postcss-css-in-js": "^0.37.2", "@stylelint/postcss-markdown": "^0.36.1", - "autoprefixer": "^9.8.0", + "autoprefixer": "^9.8.6", "balanced-match": "^1.0.0", "chalk": "^4.1.0", - "cosmiconfig": "^6.0.0", + "cosmiconfig": "^7.0.0", "debug": "^4.1.1", "execall": "^2.0.0", + "fast-glob": "^3.2.4", + "fastest-levenshtein": "^1.0.12", "file-entry-cache": "^5.0.1", "get-stdin": "^8.0.0", "global-modules": "^2.0.0", @@ -12819,18 +12515,16 @@ "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "known-css-properties": "^0.19.0", - "leven": "^3.1.0", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "log-symbols": "^4.0.0", "mathml-tag-names": "^2.1.3", - "meow": "^7.0.1", + "meow": "^7.1.1", "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", "postcss": "^7.0.32", "postcss-html": "^0.36.0", "postcss-less": "^3.1.4", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^4.0.2", "postcss-sass": "^0.4.4", @@ -12846,7 +12540,7 @@ "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^5.4.6", + "table": "^6.0.1", "v8-compile-cache": "^2.1.1", "write-file-atomic": "^3.0.3" }, @@ -12858,12 +12552,11 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -12873,6 +12566,12 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -12899,16 +12598,16 @@ "dev": true }, "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" } }, "emoji-regex": { @@ -12988,14 +12687,14 @@ "dev": true }, "parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -13011,6 +12710,17 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -13032,13 +12742,25 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } + }, + "table": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.3.tgz", + "integrity": "sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + } } } }, @@ -13115,15 +12837,15 @@ } }, "swagger-client": { - "version": "3.10.12", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.10.12.tgz", - "integrity": "sha512-h2o7axvFViMc5sxwTjjza84Rhfz+C52wgMKPOT0P05jODjZhldBK7y9EvGt4zvqgzBJHS+FDQBmOT/dGf9SWdw==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.11.1.tgz", + "integrity": "sha512-/D4I9lnGKaRmiNFTSY6xBBWYgPCHcDw7J9m4mcyeSOmZs7yGHVSFa7ozNyc7TAjNY0x2zQKjivGMUNkJ72W7ZQ==", "requires": { - "@babel/runtime-corejs2": "^7.10.4", + "@babel/runtime-corejs3": "^7.11.2", "btoa": "^1.2.1", "buffer": "^5.6.0", "cookie": "~0.4.1", - "cross-fetch": "^3.0.5", + "cross-fetch": "^3.0.6", "deep-extend": "~0.6.0", "fast-json-patch": "^2.2.1", "isomorphic-form-data": "~2.0.0", @@ -13136,12 +12858,12 @@ } }, "swagger-ui": { - "version": "3.31.1", - "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-3.31.1.tgz", - "integrity": "sha512-xunFBRPTqFnVd80zyHqr9RW/14gka+Rj3SuqBCvPhlFn2xEBSOkOXKfbUEJnI9zRRlaSPNyjvvA9/F68OOkURA==", + "version": "3.35.2", + "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-3.35.2.tgz", + "integrity": "sha512-LXUxkGWU1vKLVK5z8EUncPskCKva5LiReOFZxYqy3hWrByGBLPeMp8OcD8IVfw6/tCb+FeEMHgOn1szAIDASzg==", "requires": { - "@babel/runtime-corejs2": "^7.10.4", - "@braintree/sanitize-url": "^4.0.0", + "@babel/runtime-corejs3": "^7.11.2", + "@braintree/sanitize-url": "^5.0.0", "@kyleshockey/object-assign-deep": "^0.4.2", "@kyleshockey/xml": "^1.0.2", "base64-js": "^1.2.0", @@ -13166,24 +12888,24 @@ "react-immutable-pure-component": "^1.1.1", "react-inspector": "^2.3.0", "react-motion": "^0.5.2", - "react-redux": "^5.1.2", - "react-syntax-highlighter": "=12.2.1", - "redux": "^4.0.5", + "react-redux": "=4.4.10", + "react-syntax-highlighter": "=13.5.0", + "redux": "=3.7.2", "redux-immutable": "3.1.0", "remarkable": "^2.0.1", "reselect": "^4.0.0", "serialize-error": "^2.1.0", "sha.js": "^2.4.11", - "swagger-client": "=3.10.12", + "swagger-client": "=3.11.1", "url-parse": "^1.4.7", "xml-but-prettier": "^1.0.1", "zenscroll": "^4.0.2" }, "dependencies": { "@braintree/sanitize-url": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-4.1.1.tgz", - "integrity": "sha512-epVksusKVEpwBs2vRg3SWssxn9KXs9CxEYNOcgeSRLRjq070ABj5bLPxkmtQpVeSPCHj8kfAE9J6z2WsLr4wZg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz", + "integrity": "sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA==" }, "core-js": { "version": "2.6.11", @@ -13249,14 +12971,14 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, @@ -13265,11 +12987,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -13312,21 +13029,32 @@ } }, "terser-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-0ZWDPIP8BtEDZdChbufcXUigOYk6dOX/P/X0hWxqDDcVAQLb8Yy/0FAaemSfax3PAA67+DJR778oz8qVbmy4hA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", "requires": { "cacache": "^15.0.5", "find-cache-dir": "^3.3.1", - "jest-worker": "^26.3.0", + "jest-worker": "^26.5.0", "p-limit": "^3.0.2", - "schema-utils": "^2.6.6", - "serialize-javascript": "^4.0.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", - "terser": "^5.0.0", + "terser": "^5.3.4", "webpack-sources": "^1.4.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -13401,24 +13129,49 @@ "find-up": "^4.0.0" } }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "terser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.0.0.tgz", - "integrity": "sha512-olH2DwGINoSuEpSGd+BsPuAQaA3OrHnHnFL/rDB2TVNc3srUbz/rq/j2BlF4zDXI+JqAvGr86bIm1R2cJgZ3FA==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.7.tgz", + "integrity": "sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w==", "requires": { "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } } } } @@ -13692,9 +13445,9 @@ } }, "ua-parser-js": { - "version": "0.7.21", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", - "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", + "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==" }, "uglify-js": { "version": "2.8.29", @@ -13746,15 +13499,16 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", "requires": { "arr-flatten": "^1.0.1", "arr-map": "^2.0.0", "bach": "^1.0.0", "collection-map": "^1.0.0", "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", "last-run": "^1.1.0", "object.defaults": "^1.0.0", "object.reduce": "^1.0.0", @@ -13801,9 +13555,9 @@ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" }, "unified": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.1.0.tgz", - "integrity": "sha512-VXOv7Ic6twsKGJDeZQ2wwPqXs2hM0KNu5Hkg9WgAZbSD1pxhZ7p8swqg583nw1Je2fhwHy6U8aEjiI79x1gvag==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", "dev": true, "requires": { "bail": "^1.0.0", @@ -13819,12 +13573,6 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true } } }, @@ -13919,9 +13667,9 @@ } }, "unist-util-visit-parents": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz", - "integrity": "sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -13983,9 +13731,9 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, "updates": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/updates/-/updates-10.3.3.tgz", - "integrity": "sha512-B+Ti1W9HNCI67/0ilfkhcerbrXdl3GTyUTzERHS73xdODc/obXtnuAtT5K2HM1j/pK5B+EYQTTjeXcXECMxtBA==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/updates/-/updates-11.3.0.tgz", + "integrity": "sha512-HJz+SdS6EWnIoTW1Ns1vuSyrlGWKPvDPdBhBFJDVfGZjmQ3FBdRfE4EjzC9SpUWrX18hxgK3u7vHSBjbpOJ6jA==", "dev": true }, "upper-case": { @@ -13994,9 +13742,9 @@ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" } @@ -14145,9 +13893,9 @@ } }, "vfile-location": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", - "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz", + "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==", "dev": true }, "vfile-message": { @@ -14235,9 +13983,9 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, "vue": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", - "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", + "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" }, "vue-bar-graph": { "version": "1.2.0", @@ -14289,9 +14037,9 @@ } }, "vue-template-compiler": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", - "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", + "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", "requires": { "de-indent": "^1.0.2", "he": "^1.1.0" @@ -14531,6 +14279,14 @@ "to-regex": "^3.0.2" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -14541,14 +14297,6 @@ "ajv-keywords": "^3.1.0" } }, - "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", - "requires": { - "randombytes": "^2.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14563,15 +14311,15 @@ } }, "terser-webpack-plugin": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", - "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", @@ -14745,9 +14493,9 @@ } }, "webpack-fix-style-only-entries": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/webpack-fix-style-only-entries/-/webpack-fix-style-only-entries-0.5.1.tgz", - "integrity": "sha512-G4TBoc5JvIVNR0GXG+t314V4AqpqLJuApX7aDNTZl8yhXojAXCwJXKQeF4SF65UpP/bjrvptmHLPmdA+aGUZMw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/webpack-fix-style-only-entries/-/webpack-fix-style-only-entries-0.6.0.tgz", + "integrity": "sha512-9d3S+Y3b90D5OFUxO6bA/o7Ebx0uGWSPoVepvMvMEup7XefqdrDPEDI10tuFhxERD/xkMJAViakE1NPZn9zA+w==" }, "webpack-sources": { "version": "1.4.3", @@ -14766,9 +14514,9 @@ } }, "whatwg-fetch": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz", - "integrity": "sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", + "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" }, "when": { "version": "3.7.8", @@ -14813,25 +14561,25 @@ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, "workbox-core": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.3.tgz", - "integrity": "sha512-TFSIPxxciX9sFaj0FDiohBeIKpwMcCyNduydi9i3LChItcndDS6TJpErxybv8aBWeCMraXt33TWtF6kKuIObNw==" + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz", + "integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg==" }, "workbox-routing": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.3.tgz", - "integrity": "sha512-F+sAp9Iy3lVl3BEG+pzXWVq4AftzjiFpHDaZ4Kf4vLoBoKQE0hIHet4zE5DpHqYdyw+Udhp4wrfHamX6PN6z1Q==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.4.tgz", + "integrity": "sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==", "requires": { - "workbox-core": "^5.1.3" + "workbox-core": "^5.1.4" } }, "workbox-strategies": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.3.tgz", - "integrity": "sha512-wiXHfmOKnWABeIVW+/ye0e00+2CcS5y7SIj2f9zcdy2ZLEbcOf7B+yOl5OrWpBGlTUwRjIYhV++ZqiKm3Dc+8w==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.4.tgz", + "integrity": "sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA==", "requires": { - "workbox-core": "^5.1.3", - "workbox-routing": "^5.1.3" + "workbox-core": "^5.1.4", + "workbox-routing": "^5.1.4" } }, "worker-farm": { @@ -14843,14 +14591,25 @@ } }, "worker-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.1.tgz", - "integrity": "sha512-rTDeHzZtzOnF18m4eH1DPj7IFfyjYXIew4kwipsKtozzXpctOQLowtsi8PwfDel5B9112tVtUAj444yeDMtetg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.5.tgz", + "integrity": "sha512-cOh4UqTtvT8eHpyuuTK2C66Fg/G5Pb7g11bwtKm7uyD0vj2hCGY1APlSzVD75V9ciYZt44VPbFPiSFTSLxkQ+w==", "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.7.0" + "schema-utils": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -14860,6 +14619,16 @@ "emojis-list": "^3.0.0", "json5": "^2.1.2" } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } } } }, @@ -14980,9 +14749,9 @@ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.0", diff --git a/package.json b/package.json index 3b5ba482ee7a..4d0a05529711 100644 --- a/package.json +++ b/package.json @@ -5,60 +5,59 @@ "node": ">= 10.13.0" }, "dependencies": { - "@babel/core": "7.11.1", - "@babel/plugin-transform-runtime": "7.11.0", - "@babel/preset-env": "7.11.0", - "@babel/runtime": "7.11.2", + "@babel/core": "7.12.3", + "@babel/plugin-transform-runtime": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/runtime": "7.12.1", "@claviska/jquery-minicolors": "2.3.5", - "@primer/octicons": "10.0.0", + "@primer/octicons": "11.0.0", "add-asset-webpack-plugin": "1.0.0", "babel-loader": "8.1.0", "clipboard": "2.0.6", "core-js": "3.6.5", - "css-loader": "4.2.1", - "css-minimizer-webpack-plugin": "1.1.1", + "css-loader": "5.0.0", + "css-minimizer-webpack-plugin": "1.1.5", "dropzone": "5.7.2", "escape-goat": "3.0.0", "fast-glob": "3.2.4", - "file-loader": "6.0.0", - "fomantic-ui": "2.8.6", + "file-loader": "6.1.1", + "fomantic-ui": "2.8.7", "font-awesome": "4.7.0", "jquery": "3.5.1", "jquery.are-you-sure": "1.9.0", - "less-loader": "6.2.0", + "less": "3.12.2", + "less-loader": "7.0.2", "license-webpack-plugin": "2.3.0", - "mermaid": "8.7.0", - "mini-css-extract-plugin": "0.10.0", - "monaco-editor": "0.20.0", - "monaco-editor-webpack-plugin": "1.9.0", - "postcss-loader": "3.0.0", - "postcss-preset-env": "6.7.0", - "raw-loader": "4.0.1", - "sortablejs": "1.10.2", - "swagger-ui": "3.31.1", - "terser-webpack-plugin": "4.1.0", + "mermaid": "8.8.2", + "mini-css-extract-plugin": "1.1.1", + "monaco-editor": "0.21.2", + "monaco-editor-webpack-plugin": "2.0.0", + "raw-loader": "4.0.2", + "sortablejs": "1.12.0", + "swagger-ui": "3.35.2", + "terser-webpack-plugin": "4.2.3", "tributejs": "5.1.3", - "vue": "2.6.11", + "vue": "2.6.12", "vue-bar-graph": "1.2.0", "vue-calendar-heatmap": "0.8.4", "vue-loader": "15.9.3", - "vue-template-compiler": "2.6.11", + "vue-template-compiler": "2.6.12", "webpack": "4.44.1", "webpack-cli": "3.3.12", - "webpack-fix-style-only-entries": "0.5.1", - "workbox-routing": "5.1.3", - "workbox-strategies": "5.1.3", - "worker-loader": "3.0.1", + "webpack-fix-style-only-entries": "0.6.0", + "workbox-routing": "5.1.4", + "workbox-strategies": "5.1.4", + "worker-loader": "3.0.5", "wrap-ansi": "7.0.0" }, "devDependencies": { - "eslint": "7.6.0", - "eslint-plugin-import": "2.22.0", - "eslint-plugin-unicorn": "21.0.0", - "stylelint": "13.6.1", + "eslint": "7.11.0", + "eslint-plugin-import": "2.22.1", + "eslint-plugin-unicorn": "23.0.0", + "stylelint": "13.7.2", "stylelint-config-standard": "20.0.0", "svgo": "1.3.2", - "updates": "10.3.3" + "updates": "11.3.0" }, "browserslist": [ "defaults", diff --git a/public/img/svg/gitea-git.svg b/public/img/svg/gitea-git.svg new file mode 100644 index 000000000000..c716b1b283e2 --- /dev/null +++ b/public/img/svg/gitea-git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/gitea-gitea.svg b/public/img/svg/gitea-gitea.svg new file mode 100644 index 000000000000..7ccd7973a77b --- /dev/null +++ b/public/img/svg/gitea-gitea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/gitea-github.svg b/public/img/svg/gitea-github.svg new file mode 100644 index 000000000000..0ed1d44b5592 --- /dev/null +++ b/public/img/svg/gitea-github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/gitea-gitlab.svg b/public/img/svg/gitea-gitlab.svg new file mode 100644 index 000000000000..a72a378234b8 --- /dev/null +++ b/public/img/svg/gitea-gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-arrow-switch.svg b/public/img/svg/octicon-arrow-switch.svg new file mode 100644 index 000000000000..9063ba02b5cb --- /dev/null +++ b/public/img/svg/octicon-arrow-switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-circle.svg b/public/img/svg/octicon-circle.svg new file mode 100644 index 000000000000..80118b4d800f --- /dev/null +++ b/public/img/svg/octicon-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-file-badge.svg b/public/img/svg/octicon-file-badge.svg new file mode 100644 index 000000000000..ef91f0e01331 --- /dev/null +++ b/public/img/svg/octicon-file-badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-octoface.svg b/public/img/svg/octicon-octoface.svg index 33455095476e..da9da10f0c84 100644 --- a/public/img/svg/octicon-octoface.svg +++ b/public/img/svg/octicon-octoface.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/img/svg/octicon-triangle-down.svg b/public/img/svg/octicon-triangle-down.svg index 6be1e7c8f89b..dcc36c5c7ab2 100644 --- a/public/img/svg/octicon-triangle-down.svg +++ b/public/img/svg/octicon-triangle-down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/img/svg/octicon-x-circle-fill.svg b/public/img/svg/octicon-x-circle-fill.svg new file mode 100644 index 000000000000..1a17027a59bb --- /dev/null +++ b/public/img/svg/octicon-x-circle-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-x-circle.svg b/public/img/svg/octicon-x-circle.svg new file mode 100644 index 000000000000..4c4fe02ab260 --- /dev/null +++ b/public/img/svg/octicon-x-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/admin/admin.go b/routers/admin/admin.go index f43c1e69c592..d0f027f5f8a9 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -131,6 +131,7 @@ func Dashboard(ctx *context.Context) { // FIXME: update periodically updateSystemStatus() ctx.Data["SysStatus"] = sysStatus + ctx.Data["SSH"] = setting.SSH ctx.HTML(200, tplDashboard) } @@ -243,7 +244,9 @@ func Config(ctx *context.Context) { ctx.Data["DisableRouterLog"] = setting.DisableRouterLog ctx.Data["RunUser"] = setting.RunUser ctx.Data["RunMode"] = strings.Title(macaron.Env) - ctx.Data["GitVersion"], _ = git.BinVersion() + if version, err := git.LocalVersion(); err == nil { + ctx.Data["GitVersion"] = version.Original() + } ctx.Data["RepoRootPath"] = setting.RepoRootPath ctx.Data["CustomRootPath"] = setting.CustomPath ctx.Data["StaticRootPath"] = setting.StaticRootPath diff --git a/routers/admin/auths.go b/routers/admin/auths.go index a4fd5290b74a..98f6e25b1f93 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -129,6 +129,11 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig { AttributeSSHPublicKey: form.AttributeSSHPublicKey, SearchPageSize: pageSize, Filter: form.Filter, + GroupsEnabled: form.GroupsEnabled, + GroupDN: form.GroupDN, + GroupFilter: form.GroupFilter, + GroupMemberUID: form.GroupMemberUID, + UserUID: form.UserUID, AdminFilter: form.AdminFilter, RestrictedFilter: form.RestrictedFilter, AllowDeactivateAll: form.AllowDeactivateAll, diff --git a/routers/admin/repos.go b/routers/admin/repos.go index 39a1d7596c3f..10abaf9547ad 100644 --- a/routers/admin/repos.go +++ b/routers/admin/repos.go @@ -5,17 +5,22 @@ package admin import ( + "strings" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" repo_service "code.gitea.io/gitea/services/repository" + "github.com/unknwon/com" ) const ( - tplRepos base.TplName = "admin/repo/list" + tplRepos base.TplName = "admin/repo/list" + tplUnadoptedRepos base.TplName = "admin/repo/unadopted" ) // Repos show all the repositories @@ -50,3 +55,91 @@ func DeleteRepo(ctx *context.Context) { "redirect": setting.AppSubURL + "/admin/repos?page=" + ctx.Query("page") + "&sort=" + ctx.Query("sort"), }) } + +// UnadoptedRepos lists the unadopted repositories +func UnadoptedRepos(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.repositories") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminRepositories"] = true + + opts := models.ListOptions{ + PageSize: setting.UI.Admin.UserPagingNum, + Page: ctx.QueryInt("page"), + } + + if opts.Page <= 0 { + opts.Page = 1 + } + + doSearch := ctx.QueryBool("search") + + ctx.Data["search"] = doSearch + q := ctx.Query("q") + + if !doSearch { + pager := context.NewPagination(0, opts.PageSize, opts.Page, 5) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager + ctx.HTML(200, tplUnadoptedRepos) + return + } + + ctx.Data["Keyword"] = q + repoNames, count, err := repository.ListUnadoptedRepositories(q, &opts) + if err != nil { + ctx.ServerError("ListUnadoptedRepositories", err) + } + ctx.Data["Dirs"] = repoNames + pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager + ctx.HTML(200, tplUnadoptedRepos) +} + +// AdoptOrDeleteRepository adopts or deletes a repository +func AdoptOrDeleteRepository(ctx *context.Context) { + dir := ctx.Query("id") + action := ctx.Query("action") + dirSplit := strings.SplitN(dir, "/", 2) + if len(dirSplit) != 2 { + ctx.Redirect(setting.AppSubURL + "/admin/repos") + return + } + + ctxUser, err := models.GetUserByName(dirSplit[0]) + if err != nil { + if models.IsErrUserNotExist(err) { + log.Debug("User does not exist: %s", dirSplit[0]) + ctx.Redirect(setting.AppSubURL + "/admin/repos") + return + } + ctx.ServerError("GetUserByName", err) + return + } + + repoName := dirSplit[1] + + // check not a repo + if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil { + ctx.ServerError("IsRepositoryExist", err) + return + } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) { + // Fallthrough to failure mode + } else if action == "adopt" { + if _, err := repository.AdoptRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: dirSplit[1], + IsPrivate: true, + }); err != nil { + ctx.ServerError("repository.AdoptRepository", err) + return + } + ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) + } else if action == "delete" { + if err := repository.DeleteUnadoptedRepository(ctx.User, ctxUser, dirSplit[1]); err != nil { + ctx.ServerError("repository.AdoptRepository", err) + return + } + ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir)) + } + ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted") +} diff --git a/routers/admin/users.go b/routers/admin/users.go index a28db2b44531..9fb758621b0a 100644 --- a/routers/admin/users.go +++ b/routers/admin/users.go @@ -108,6 +108,17 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) { ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserNew, &form) return } + pwned, err := password.IsPwned(ctx.Req.Context(), form.Password) + if pwned { + ctx.Data["Err_Password"] = true + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.RenderWithErr(errMsg, tplUserNew, &form) + return + } u.MustChangePassword = form.MustChangePassword } if err := models.CreateUser(u); err != nil { @@ -213,7 +224,7 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { } } - if len(form.Password) > 0 { + if len(form.Password) > 0 && (u.IsLocal() || u.IsOAuth2()) { var err error if len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true @@ -224,6 +235,17 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { ctx.RenderWithErr(password.BuildComplexityError(ctx), tplUserEdit, &form) return } + pwned, err := password.IsPwned(ctx.Req.Context(), form.Password) + if pwned { + ctx.Data["Err_Password"] = true + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.RenderWithErr(errMsg, tplUserNew, &form) + return + } if u.Salt, err = models.GetUserSalt(); err != nil { ctx.ServerError("UpdateUser", err) return diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go new file mode 100644 index 000000000000..1a7a62a55cf0 --- /dev/null +++ b/routers/api/v1/admin/adopt.go @@ -0,0 +1,164 @@ +// 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 admin + +import ( + "fmt" + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/routers/api/v1/utils" + "github.com/unknwon/com" +) + +// ListUnadoptedRepositories lists the unadopted repositories that match the provided names +func ListUnadoptedRepositories(ctx *context.APIContext) { + // swagger:operation GET /admin/unadopted admin adminUnadoptedList + // --- + // summary: List unadopted repositories + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // - name: pattern + // in: query + // description: pattern of repositories to search for + // type: string + // responses: + // "200": + // "$ref": "#/responses/StringSlice" + // "403": + // "$ref": "#/responses/forbidden" + + listOptions := utils.GetListOptions(ctx) + repoNames, count, err := repository.ListUnadoptedRepositories(ctx.Query("query"), &listOptions) + if err != nil { + ctx.InternalServerError(err) + } + + ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count)) + ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count") + + ctx.JSON(http.StatusOK, repoNames) +} + +// AdoptRepository will adopt an unadopted repository +func AdoptRepository(ctx *context.APIContext) { + // swagger:operation POST /admin/unadopted/{owner}/{repo} admin adminAdoptRepository + // --- + // summary: Adopt unadopted files as a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + // "403": + // "$ref": "#/responses/forbidden" + ownerName := ctx.Params(":username") + repoName := ctx.Params(":reponame") + + ctxUser, err := models.GetUserByName(ownerName) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.NotFound() + return + } + ctx.InternalServerError(err) + return + } + + // check not a repo + if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil { + ctx.InternalServerError(err) + return + } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) { + ctx.NotFound() + return + } + if _, err := repository.AdoptRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: repoName, + IsPrivate: true, + }); err != nil { + ctx.InternalServerError(err) + return + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteUnadoptedRepository will delete an unadopted repository +func DeleteUnadoptedRepository(ctx *context.APIContext) { + // swagger:operation DELETE /admin/unadopted/{owner}/{repo} admin adminDeleteUnadoptedRepository + // --- + // summary: Delete unadopted files + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + ownerName := ctx.Params(":username") + repoName := ctx.Params(":reponame") + + ctxUser, err := models.GetUserByName(ownerName) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.NotFound() + return + } + ctx.InternalServerError(err) + return + } + + // check not a repo + if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil { + ctx.InternalServerError(err) + return + } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) { + ctx.NotFound() + return + } + + if err := repository.DeleteUnadoptedRepository(ctx.User, ctxUser, repoName); err != nil { + ctx.InternalServerError(err) + return + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go new file mode 100644 index 000000000000..2531346fcb0c --- /dev/null +++ b/routers/api/v1/admin/cron.go @@ -0,0 +1,86 @@ +// 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 admin + +import ( + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/cron" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/utils" +) + +// ListCronTasks api for getting cron tasks +func ListCronTasks(ctx *context.APIContext) { + // swagger:operation GET /admin/cron admin adminCronList + // --- + // summary: List cron tasks + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/CronList" + // "403": + // "$ref": "#/responses/forbidden" + tasks := cron.ListTasks() + listOpts := utils.GetListOptions(ctx) + start, end := listOpts.GetStartEnd() + + if len(tasks) > listOpts.PageSize { + tasks = tasks[start:end] + } + + res := make([]structs.Cron, len(tasks)) + for i, task := range tasks { + res[i] = structs.Cron{ + Name: task.Name, + Schedule: task.Spec, + Next: task.Next, + Prev: task.Prev, + ExecTimes: task.ExecTimes, + } + } + ctx.JSON(http.StatusOK, res) +} + +// PostCronTask api for getting cron tasks +func PostCronTask(ctx *context.APIContext) { + // swagger:operation POST /admin/cron/{task} admin adminCronRun + // --- + // summary: Run cron task + // produces: + // - application/json + // parameters: + // - name: task + // in: path + // description: task to run + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + task := cron.GetTask(ctx.Params(":task")) + if task == nil { + ctx.NotFound() + return + } + task.Run() + log.Trace("Cron Task %s started by admin(%s)", task.Name, ctx.User.Name) + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index a8a573eaf075..dc095f3a1351 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -87,6 +87,15 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) { ctx.Error(http.StatusBadRequest, "PasswordComplexity", err) return } + pwned, err := password.IsPwned(ctx.Req.Context(), form.Password) + if pwned { + if err != nil { + log.Error(err.Error()) + } + ctx.Data["Err_Password"] = true + ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned")) + return + } if err := models.CreateUser(u); err != nil { if models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err) || @@ -151,7 +160,15 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) { ctx.Error(http.StatusBadRequest, "PasswordComplexity", err) return } - var err error + pwned, err := password.IsPwned(ctx.Req.Context(), form.Password) + if pwned { + if err != nil { + log.Error(err.Error()) + } + ctx.Data["Err_Password"] = true + ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned")) + return + } if u.Salt, err = models.GetUserSalt(); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateUser", err) return diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 506e6a3ec05a..147cb8e2761b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -404,7 +404,7 @@ func orgAssignment(args ...bool) macaron.Handler { if assignTeam { ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid")) if err != nil { - if models.IsErrUserNotExist(err) { + if models.IsErrTeamNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetTeamById", err) @@ -521,6 +521,8 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/markdown/raw", misc.MarkdownRaw) m.Group("/settings", func() { m.Get("/ui", settings.GetGeneralUISettings) + m.Get("/api", settings.GetGeneralAPISettings) + m.Get("/attachment", settings.GetGeneralAttachmentSettings) m.Get("/repository", settings.GetGeneralRepoSettings) }) @@ -634,7 +636,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/issues/search", repo.SearchIssues) - m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate) + m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) m.Group("/:username/:reponame", func() { m.Combo("").Get(reqAnyRepoReader(), repo.Get). @@ -795,6 +797,9 @@ func RegisterRoutes(m *macaron.Macaron) { Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment) }) }) + m.Group("/tags", func() { + m.Get("/:tag", repo.GetReleaseTag) + }) }, reqRepoReader(models.UnitTypeReleases)) m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync) m.Get("/editorconfig/:filename", context.RepoRef(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig) @@ -822,7 +827,9 @@ func RegisterRoutes(m *macaron.Macaron) { Get(repo.GetPullReviewComments) }) }) - + m.Combo("/requested_reviewers"). + Delete(reqToken(), bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests). + Post(reqToken(), bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests) }) }, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false)) m.Group("/statuses", func() { @@ -864,6 +871,7 @@ func RegisterRoutes(m *macaron.Macaron) { Delete(reqToken(), repo.DeleteTopic) }, reqAdmin()) }, reqAnyRepoReader()) + m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates) m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages) }, repoAssignment()) }) @@ -934,6 +942,10 @@ func RegisterRoutes(m *macaron.Macaron) { }) m.Group("/admin", func() { + m.Group("/cron", func() { + m.Get("", admin.ListCronTasks) + m.Post("/:task", admin.PostCronTask) + }) m.Get("/orgs", admin.GetAllOrgs) m.Group("/users", func() { m.Get("", admin.GetAllUsers) @@ -950,6 +962,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) }) }) + m.Group("/unadopted", func() { + m.Get("", admin.ListUnadoptedRepositories) + m.Post("/:username/:reponame", admin.AdoptRepository) + m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository) + }) }, reqToken(), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go index 0b295f389843..3c3391bfe736 100644 --- a/routers/api/v1/misc/signing.go +++ b/routers/api/v1/misc/signing.go @@ -10,11 +10,10 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/log" ) // SigningKey returns the public key of the default signing key if it exists -func SigningKey(ctx *context.Context) { +func SigningKey(ctx *context.APIContext) { // swagger:operation GET /signing-key.gpg miscellaneous getSigningKey // --- // summary: Get default signing-key.gpg @@ -55,12 +54,11 @@ func SigningKey(ctx *context.Context) { content, err := models.PublicSigningKey(path) if err != nil { - ctx.ServerError("gpg export", err) + ctx.Error(http.StatusInternalServerError, "gpg export", err) return } _, err = ctx.Write([]byte(content)) if err != nil { - log.Error("Error writing key content %v", err) - ctx.Error(http.StatusInternalServerError, fmt.Sprintf("%v", err)) + ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %v", err)) } } diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index b4a8a5ac33ce..9a8a4fa29154 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -201,7 +201,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { label.Description = *form.Description } if err := models.UpdateLabel(label); err != nil { - ctx.ServerError("UpdateLabel", err) + ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } ctx.JSON(http.StatusOK, convert.ToLabel(label)) diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 7577700abfef..987f14107808 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -85,7 +85,7 @@ func ListUserOrgs(ctx *context.APIContext) { if ctx.Written() { return } - listUserOrgs(ctx, u, ctx.User.IsAdmin) + listUserOrgs(ctx, u, ctx.User != nil && (ctx.User.IsAdmin || ctx.User.ID == u.ID)) } // GetAll return list of all public organizations diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 90db597ef7cb..50ca3977a589 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -14,9 +14,10 @@ import ( "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/repofiles" repo_module "code.gitea.io/gitea/modules/repository" api "code.gitea.io/gitea/modules/structs" + pull_service "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" ) // GetBranch get a branch of a repository @@ -160,10 +161,8 @@ func DeleteBranch(ctx *context.APIContext) { } // Don't return error below this - if err := repofiles.PushUpdate( - ctx.Repo.Repository, - ctx.Repo.BranchName, - repofiles.PushUpdateOptions{ + if err := repo_service.PushUpdate( + &repo_service.PushUpdateOptions{ RefFullName: git.BranchPrefix + ctx.Repo.BranchName, OldCommitID: c.ID.String(), NewCommitID: git.EmptySHA, @@ -547,6 +546,11 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec return } + if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { + ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) + return + } + // Reload from db to get all whitelists bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName) if err != nil { @@ -770,6 +774,11 @@ func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtection return } + if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { + ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err) + return + } + // Reload from db to ensure get all whitelists bp, err := models.GetProtectedBranchBy(repo.ID, bpName) if err != nil { diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 8b2dc28bdde6..8c877285a844 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -10,10 +10,10 @@ import ( "math" "net/http" "strconv" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -63,7 +63,7 @@ func GetSingleCommit(ctx *context.APIContext) { func getCommit(ctx *context.APIContext, identifier string) { gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) if err != nil { - ctx.ServerError("OpenRepository", err) + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) return } defer gitRepo.Close() @@ -73,9 +73,9 @@ func getCommit(ctx *context.APIContext, identifier string) { return } - json, err := toCommit(ctx, ctx.Repo.Repository, commit, nil) + json, err := convert.ToCommit(ctx.Repo.Repository, commit, nil) if err != nil { - ctx.ServerError("toCommit", err) + ctx.Error(http.StatusInternalServerError, "toCommit", err) return } ctx.JSON(http.StatusOK, json) @@ -129,7 +129,7 @@ func GetAllCommits(ctx *context.APIContext) { gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) if err != nil { - ctx.ServerError("OpenRepository", err) + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) return } defer gitRepo.Close() @@ -150,20 +150,20 @@ func GetAllCommits(ctx *context.APIContext) { // no sha supplied - use default branch head, err := gitRepo.GetHEADBranch() if err != nil { - ctx.ServerError("GetHEADBranch", err) + ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err) return } baseCommit, err = gitRepo.GetBranchCommit(head.Name) if err != nil { - ctx.ServerError("GetCommit", err) + ctx.Error(http.StatusInternalServerError, "GetCommit", err) return } } else { // get commit specified by sha baseCommit, err = gitRepo.GetCommit(sha) if err != nil { - ctx.ServerError("GetCommit", err) + ctx.Error(http.StatusInternalServerError, "GetCommit", err) return } } @@ -171,7 +171,7 @@ func GetAllCommits(ctx *context.APIContext) { // Total commit count commitsCountTotal, err := baseCommit.CommitsCount() if err != nil { - ctx.ServerError("GetCommitsCount", err) + ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err) return } @@ -180,7 +180,7 @@ func GetAllCommits(ctx *context.APIContext) { // Query commits commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize) if err != nil { - ctx.ServerError("CommitsByRange", err) + ctx.Error(http.StatusInternalServerError, "CommitsByRange", err) return } @@ -193,9 +193,9 @@ func GetAllCommits(ctx *context.APIContext) { commit := commitPointer.Value.(*git.Commit) // Create json struct - apiCommits[i], err = toCommit(ctx, ctx.Repo.Repository, commit, userCache) + apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, commit, userCache) if err != nil { - ctx.ServerError("toCommit", err) + ctx.Error(http.StatusInternalServerError, "toCommit", err) return } @@ -215,98 +215,3 @@ func GetAllCommits(ctx *context.APIContext) { ctx.JSON(http.StatusOK, &apiCommits) } - -func toCommit(ctx *context.APIContext, repo *models.Repository, commit *git.Commit, userCache map[string]*models.User) (*api.Commit, error) { - - var apiAuthor, apiCommitter *api.User - - // Retrieve author and committer information - - var cacheAuthor *models.User - var ok bool - if userCache == nil { - cacheAuthor = ((*models.User)(nil)) - ok = false - } else { - cacheAuthor, ok = userCache[commit.Author.Email] - } - - if ok { - apiAuthor = cacheAuthor.APIFormat() - } else { - author, err := models.GetUserByEmail(commit.Author.Email) - if err != nil && !models.IsErrUserNotExist(err) { - return nil, err - } else if err == nil { - apiAuthor = author.APIFormat() - if userCache != nil { - userCache[commit.Author.Email] = author - } - } - } - - var cacheCommitter *models.User - if userCache == nil { - cacheCommitter = ((*models.User)(nil)) - ok = false - } else { - cacheCommitter, ok = userCache[commit.Committer.Email] - } - - if ok { - apiCommitter = cacheCommitter.APIFormat() - } else { - committer, err := models.GetUserByEmail(commit.Committer.Email) - if err != nil && !models.IsErrUserNotExist(err) { - return nil, err - } else if err == nil { - apiCommitter = committer.APIFormat() - if userCache != nil { - userCache[commit.Committer.Email] = committer - } - } - } - - // Retrieve parent(s) of the commit - apiParents := make([]*api.CommitMeta, commit.ParentCount()) - for i := 0; i < commit.ParentCount(); i++ { - sha, _ := commit.ParentID(i) - apiParents[i] = &api.CommitMeta{ - URL: repo.APIURL() + "/git/commits/" + sha.String(), - SHA: sha.String(), - } - } - - return &api.Commit{ - CommitMeta: &api.CommitMeta{ - URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), - SHA: commit.ID.String(), - }, - HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(), - RepoCommit: &api.RepoCommit{ - URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: commit.Committer.Name, - Email: commit.Committer.Email, - }, - Date: commit.Author.When.Format(time.RFC3339), - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: commit.Committer.Name, - Email: commit.Committer.Email, - }, - Date: commit.Committer.When.Format(time.RFC3339), - }, - Message: commit.Message(), - Tree: &api.CommitMeta{ - URL: repo.APIURL() + "/git/trees/" + commit.ID.String(), - SHA: commit.ID.String(), - }, - }, - Author: apiAuthor, - Committer: apiCommitter, - Parents: apiParents, - }, nil -} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index a43fdb074e2e..bc85fec63020 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -87,7 +87,7 @@ func GetArchive(ctx *context.APIContext) { // required: true // - name: archive // in: path - // description: archive to download, consisting of a git reference and archive + // description: the git reference for download with attached archive format (e.g. master.zip) // type: string // required: true // responses: diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 0dd961d7f0f0..e6f5f5e630bc 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -109,7 +109,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) { } isMember, err := org.IsOrgMember(ctx.User.ID) if err != nil { - ctx.ServerError("IsOrgMember", err) + ctx.Error(http.StatusInternalServerError, "IsOrgMember", err) return } else if !isMember { ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name)) diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go index 37584933445f..1a38fc3018a5 100644 --- a/routers/api/v1/repo/hook.go +++ b/routers/api/v1/repo/hook.go @@ -144,7 +144,7 @@ func TestHook(ctx *context.APIContext) { Before: ctx.Repo.Commit.ID.String(), After: ctx.Repo.Commit.ID.String(), Commits: []*api.PayloadCommit{ - convert.ToCommit(ctx.Repo.Repository, ctx.Repo.Commit), + convert.ToPayloadCommit(ctx.Repo.Repository, ctx.Repo.Commit), }, Repo: ctx.Repo.Repository.APIFormat(models.AccessModeNone), Pusher: convert.ToUser(ctx.User, ctx.IsSigned, false), diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 1653b85940ca..0dbf2741ad4b 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -93,7 +93,6 @@ func SearchIssues(ctx *context.APIContext) { opts.AllLimited = true } - issueCount := 0 for page := 1; ; page++ { opts.Page = page repos, count, err := models.SearchRepositoryByName(opts) @@ -107,19 +106,12 @@ func SearchIssues(ctx *context.APIContext) { } log.Trace("Processing next %d repos of %d", len(repos), count) for _, repo := range repos { - switch isClosed { - case util.OptionalBoolTrue: - issueCount += repo.NumClosedIssues - case util.OptionalBoolFalse: - issueCount += repo.NumOpenIssues - case util.OptionalBoolNone: - issueCount += repo.NumIssues - } repoIDs = append(repoIDs, repo.ID) } } var issues []*models.Issue + var filteredCount int64 keyword := strings.Trim(ctx.Query("q"), " ") if strings.IndexByte(keyword, 0) >= 0 { @@ -129,7 +121,10 @@ func SearchIssues(ctx *context.APIContext) { var labelIDs []int64 var err error if len(keyword) > 0 && len(repoIDs) > 0 { - issueIDs, err = issue_indexer.SearchIssuesByKeyword(repoIDs, keyword) + if issueIDs, err = issue_indexer.SearchIssuesByKeyword(repoIDs, keyword); err != nil { + ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) + return + } } var isPull util.OptionalBool @@ -151,12 +146,11 @@ func SearchIssues(ctx *context.APIContext) { // Only fetch the issues if we either don't have a keyword or the search returned issues // This would otherwise return all issues if no issues were found by the search. if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { - issues, err = models.Issues(&models.IssuesOptions{ + issuesOpt := &models.IssuesOptions{ ListOptions: models.ListOptions{ Page: ctx.QueryInt("page"), PageSize: setting.UI.IssuePagingNum, }, - RepoIDs: repoIDs, IsClosed: isClosed, IssueIDs: issueIDs, @@ -164,16 +158,24 @@ func SearchIssues(ctx *context.APIContext) { SortType: "priorityrepo", PriorityRepoID: ctx.QueryInt64("priority_repo_id"), IsPull: isPull, - }) - } + } - if err != nil { - ctx.Error(http.StatusInternalServerError, "Issues", err) - return + if issues, err = models.Issues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "Issues", err) + return + } + + issuesOpt.ListOptions = models.ListOptions{ + Page: -1, + } + if filteredCount, err = models.CountIssues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "CountIssues", err) + return + } } - ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", issueCount)) + ctx.SetLinkHeader(int(filteredCount), setting.UI.IssuePagingNum) + ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", filteredCount)) ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) } @@ -241,6 +243,7 @@ func ListIssues(ctx *context.APIContext) { } var issues []*models.Issue + var filteredCount int64 keyword := strings.Trim(ctx.Query("q"), " ") if strings.IndexByte(keyword, 0) >= 0 { @@ -251,6 +254,10 @@ func ListIssues(ctx *context.APIContext) { var err error if len(keyword) > 0 { issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{ctx.Repo.Repository.ID}, keyword) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) + return + } } if splitted := strings.Split(ctx.Query("labels"), ","); len(splitted) > 0 { @@ -306,7 +313,7 @@ func ListIssues(ctx *context.APIContext) { // Only fetch the issues if we either don't have a keyword or the search returned issues // This would otherwise return all issues if no issues were found by the search. if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { - issues, err = models.Issues(&models.IssuesOptions{ + issuesOpt := &models.IssuesOptions{ ListOptions: listOptions, RepoIDs: []int64{ctx.Repo.Repository.ID}, IsClosed: isClosed, @@ -314,18 +321,25 @@ func ListIssues(ctx *context.APIContext) { LabelIDs: labelIDs, MilestoneIDs: mileIDs, IsPull: isPull, - }) - } + } - if err != nil { - ctx.Error(http.StatusInternalServerError, "Issues", err) - return + if issues, err = models.Issues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "Issues", err) + return + } + + issuesOpt.ListOptions = models.ListOptions{ + Page: -1, + } + if filteredCount, err = models.CountIssues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "CountIssues", err) + return + } } - ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", ctx.Repo.Repository.NumIssues)) + ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize) + ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", filteredCount)) ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") - ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) } diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index bf86b42402f3..2da2f2906371 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" comment_service "code.gitea.io/gitea/services/comments" @@ -85,7 +86,7 @@ func ListIssueComments(ctx *context.APIContext) { apiComments := make([]*api.Comment, len(comments)) for i, comment := range comments { comment.Issue = issue - apiComments[i] = comments[i].APIFormat() + apiComments[i] = convert.ToComment(comments[i]) } ctx.JSON(http.StatusOK, &apiComments) } @@ -167,7 +168,7 @@ func ListRepoIssueComments(ctx *context.APIContext) { return } for i := range comments { - apiComments[i] = comments[i].APIFormat() + apiComments[i] = convert.ToComment(comments[i]) } ctx.JSON(http.StatusOK, &apiComments) } @@ -225,7 +226,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti return } - ctx.JSON(http.StatusCreated, comment.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToComment(comment)) } // GetIssueComment Get a comment by ID @@ -293,7 +294,7 @@ func GetIssueComment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, comment.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToComment(comment)) } // EditIssueComment modify a comment of an issue @@ -414,7 +415,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) return } - ctx.JSON(http.StatusOK, comment.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToComment(comment)) } // DeleteIssueComment delete a comment from an issue diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index 564f67e49329..f5387fdd1adf 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -75,7 +76,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) { var result []api.Reaction for _, r := range reactions { result = append(result, api.Reaction{ - User: r.User.APIFormat(), + User: convert.ToUser(r.User, ctx.IsSigned, false), Reaction: r.Type, Created: r.CreatedUnix.AsTime(), }) @@ -193,7 +194,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp ctx.Error(http.StatusForbidden, err.Error(), err) } else if models.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ - User: ctx.User.APIFormat(), + User: convert.ToUser(ctx.User, true, true), Reaction: reaction.Type, Created: reaction.CreatedUnix.AsTime(), }) @@ -204,7 +205,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp } ctx.JSON(http.StatusCreated, api.Reaction{ - User: ctx.User.APIFormat(), + User: convert.ToUser(ctx.User, true, true), Reaction: reaction.Type, Created: reaction.CreatedUnix.AsTime(), }) @@ -289,7 +290,7 @@ func GetIssueReactions(ctx *context.APIContext) { var result []api.Reaction for _, r := range reactions { result = append(result, api.Reaction{ - User: r.User.APIFormat(), + User: convert.ToUser(r.User, ctx.IsSigned, false), Reaction: r.Type, Created: r.CreatedUnix.AsTime(), }) @@ -402,7 +403,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i ctx.Error(http.StatusForbidden, err.Error(), err) } else if models.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ - User: ctx.User.APIFormat(), + User: convert.ToUser(ctx.User, true, true), Reaction: reaction.Type, Created: reaction.CreatedUnix.AsTime(), }) @@ -413,7 +414,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i } ctx.JSON(http.StatusCreated, api.Reaction{ - User: ctx.User.APIFormat(), + User: convert.ToUser(ctx.User, true, true), Reaction: reaction.Type, Created: reaction.CreatedUnix.AsTime(), }) diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go index ca1593619ef6..a4a2261b9a10 100644 --- a/routers/api/v1/repo/issue_stopwatch.go +++ b/routers/api/v1/repo/issue_stopwatch.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -224,7 +225,7 @@ func GetStopwatches(ctx *context.APIContext) { return } - apiSWs, err := sws.APIFormat() + apiSWs, err := convert.ToStopWatches(sws) if err != nil { ctx.Error(http.StatusInternalServerError, "APIFormat", err) return diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go index f0df0976fb32..2bbd72299ab5 100644 --- a/routers/api/v1/repo/issue_subscription.go +++ b/routers/api/v1/repo/issue_subscription.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -276,6 +277,10 @@ func GetIssueSubscribers(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err) return } + apiUsers := make([]*api.User, 0, len(users)) + for i := range users { + apiUsers[i] = convert.ToUser(users[i], ctx.IsSigned, false) + } - ctx.JSON(http.StatusOK, users.APIFormat()) + ctx.JSON(http.StatusOK, apiUsers) } diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go index 5d63a25d64ba..3d16ae6d4055 100644 --- a/routers/api/v1/repo/key.go +++ b/routers/api/v1/repo/key.go @@ -177,6 +177,8 @@ func HandleAddKeyError(ctx *context.APIContext, err error) { ctx.Error(http.StatusUnprocessableEntity, "", "Key content has been used as non-deploy key") case models.IsErrKeyNameAlreadyUsed(err): ctx.Error(http.StatusUnprocessableEntity, "", "Key title has been used") + case models.IsErrDeployKeyNameAlreadyUsed(err): + ctx.Error(http.StatusUnprocessableEntity, "", "A key with the same name already exists") default: ctx.Error(http.StatusInternalServerError, "AddKey", err) } diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index a293801c4f81..fef6ebf07aa6 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -222,7 +222,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { label.Description = *form.Description } if err := models.UpdateLabel(label); err != nil { - ctx.ServerError("UpdateLabel", err) + ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } ctx.JSON(http.StatusOK, convert.ToLabel(label)) diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index fa4b7366e89a..f9cddbb7cdce 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -9,12 +9,12 @@ import ( "errors" "fmt" "net/http" - "net/url" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations" @@ -26,7 +26,7 @@ import ( ) // Migrate migrate remote git repository to gitea -func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { +func Migrate(ctx *context.APIContext, form api.MigrateRepoOptions) { // swagger:operation POST /repos/migrate repository repoMigrate // --- // summary: Migrate a remote git repository @@ -38,7 +38,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { // - name: body // in: body // schema: - // "$ref": "#/definitions/MigrateRepoForm" + // "$ref": "#/definitions/MigrateRepoOptions" // responses: // "201": // "$ref": "#/responses/Repository" @@ -47,20 +47,25 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { // "422": // "$ref": "#/responses/validationError" - ctxUser := ctx.User - // Not equal means context user is an organization, - // or is another user/organization if current user is admin. - if form.UID != ctxUser.ID { - org, err := models.GetUserByID(form.UID) - if err != nil { - if models.IsErrUserNotExist(err) { - ctx.Error(http.StatusUnprocessableEntity, "", err) - } else { - ctx.Error(http.StatusInternalServerError, "GetUserByID", err) - } - return + //get repoOwner + var ( + repoOwner *models.User + err error + ) + if len(form.RepoOwner) != 0 { + repoOwner, err = models.GetUserByName(form.RepoOwner) + } else if form.RepoOwnerID != 0 { + repoOwner, err = models.GetUserByID(form.RepoOwnerID) + } else { + repoOwner = ctx.User + } + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(http.StatusUnprocessableEntity, "", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetUser", err) } - ctxUser = org + return } if ctx.HasError() { @@ -69,14 +74,14 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { } if !ctx.User.IsAdmin { - if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID { + if !repoOwner.IsOrganization() && ctx.User.ID != repoOwner.ID { ctx.Error(http.StatusForbidden, "", "Given user is not an organization.") return } - if ctxUser.IsOrganization() { + if repoOwner.IsOrganization() { // Check ownership of organization. - isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID) + isOwner, err := repoOwner.IsOwnedBy(ctx.User.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err) return @@ -87,7 +92,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { } } - remoteAddr, err := form.ParseRemoteAddr(ctx.User) + remoteAddr, err := auth.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword, ctx.User) if err != nil { if models.IsErrInvalidCloneAddr(err) { addrErr := err.(models.ErrInvalidCloneAddr) @@ -107,11 +112,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { return } - var gitServiceType = api.PlainGitService - u, err := url.Parse(remoteAddr) - if err == nil && strings.EqualFold(u.Host, "github.com") { - gitServiceType = api.GithubService - } + gitServiceType := convert.ToGitServiceType(form.Service) if form.Mirror && setting.Repository.DisableMirrors { ctx.Error(http.StatusForbidden, "MirrorsGlobalDisabled", fmt.Errorf("the site administrator has disabled mirrors")) @@ -126,6 +127,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { Mirror: form.Mirror, AuthUsername: form.AuthUsername, AuthPassword: form.AuthPassword, + AuthToken: form.AuthToken, Wiki: form.Wiki, Issues: form.Issues, Milestones: form.Milestones, @@ -144,7 +146,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { opts.Releases = false } - repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + repo, err := repo_module.CreateRepository(ctx.User, repoOwner, models.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, OriginalURL: form.CloneAddr, @@ -154,7 +156,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { Status: models.RepositoryBeingMigrated, }) if err != nil { - handleMigrateError(ctx, ctxUser, remoteAddr, err) + handleMigrateError(ctx, repoOwner, remoteAddr, err) return } @@ -171,24 +173,24 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { if err == nil { repo.Status = models.RepositoryReady if err := models.UpdateRepositoryCols(repo, "status"); err == nil { - notification.NotifyMigrateRepository(ctx.User, ctxUser, repo) + notification.NotifyMigrateRepository(ctx.User, repoOwner, repo) return } } if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { + if errDelete := models.DeleteRepository(ctx.User, repoOwner.ID, repo.ID); errDelete != nil { log.Error("DeleteRepository: %v", errDelete) } } }() - if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil { - handleMigrateError(ctx, ctxUser, remoteAddr, err) + if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts); err != nil { + handleMigrateError(ctx, repoOwner, remoteAddr, err) return } - log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) + log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName) ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin)) } @@ -196,6 +198,8 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA switch { case models.IsErrRepoAlreadyExist(err): ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") + case models.IsErrRepoFilesAlreadyExist(err): + ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.") case migrations.IsRateLimitError(err): ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.") case migrations.IsTwoFactorAuthError(err): diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index f6f6b29465bf..db86d0868f8b 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -7,6 +7,7 @@ package repo import ( "net/http" + "strconv" "time" "code.gitea.io/gitea/models" @@ -73,7 +74,7 @@ func ListMilestones(ctx *context.APIContext) { ctx.JSON(http.StatusOK, &apiMilestones) } -// GetMilestone get a milestone for a repository +// GetMilestone get a milestone for a repository by ID and if not available by name func GetMilestone(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/milestones/{id} issue issueGetMilestone // --- @@ -93,23 +94,18 @@ func GetMilestone(ctx *context.APIContext) { // required: true // - name: id // in: path - // description: id of the milestone - // type: integer - // format: int64 + // description: the milestone to get, identified by ID and if not available by name + // type: string // required: true // responses: // "200": // "$ref": "#/responses/Milestone" - milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - ctx.NotFound() - } else { - ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) - } + milestone := getMilestoneByIDOrName(ctx) + if ctx.Written() { return } + ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) } @@ -165,7 +161,7 @@ func CreateMilestone(ctx *context.APIContext, form api.CreateMilestoneOption) { ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone)) } -// EditMilestone modify a milestone for a repository +// EditMilestone modify a milestone for a repository by ID and if not available by name func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { // swagger:operation PATCH /repos/{owner}/{repo}/milestones/{id} issue issueEditMilestone // --- @@ -187,9 +183,8 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { // required: true // - name: id // in: path - // description: id of the milestone - // type: integer - // format: int64 + // description: the milestone to edit, identified by ID and if not available by name + // type: string // required: true // - name: body // in: body @@ -199,13 +194,8 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { // "200": // "$ref": "#/responses/Milestone" - milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) - if err != nil { - if models.IsErrMilestoneNotExist(err) { - ctx.NotFound() - } else { - ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) - } + milestone := getMilestoneByIDOrName(ctx) + if ctx.Written() { return } @@ -225,13 +215,13 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) { } if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil { - ctx.ServerError("UpdateMilestone", err) + ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err) return } ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) } -// DeleteMilestone delete a milestone for a repository +// DeleteMilestone delete a milestone for a repository by ID and if not available by name func DeleteMilestone(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo}/milestones/{id} issue issueDeleteMilestone // --- @@ -249,17 +239,49 @@ func DeleteMilestone(ctx *context.APIContext) { // required: true // - name: id // in: path - // description: id of the milestone to delete - // type: integer - // format: int64 + // description: the milestone to delete, identified by ID and if not available by name + // type: string // required: true // responses: // "204": // "$ref": "#/responses/empty" - if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil { + m := getMilestoneByIDOrName(ctx) + if ctx.Written() { + return + } + + if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, m.ID); err != nil { ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err) return } ctx.Status(http.StatusNoContent) } + +// getMilestoneByIDOrName get milestone by ID and if not available by name +func getMilestoneByIDOrName(ctx *context.APIContext) *models.Milestone { + mile := ctx.Params(":id") + mileID, _ := strconv.ParseInt(mile, 0, 64) + + if mileID != 0 { + milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, mileID) + if err == nil { + return milestone + } else if !models.IsErrMilestoneNotExist(err) { + ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) + return nil + } + } + + milestone, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, mile) + if err != nil { + if models.IsErrMilestoneNotExist(err) { + ctx.NotFound() + return nil + } + ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) + return nil + } + + return milestone +} diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index e58f1ab31420..379d8b2b5ae2 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -725,7 +725,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) { } if err = pr.LoadHeadRepo(); err != nil { - ctx.ServerError("LoadHeadRepo", err) + ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } @@ -792,7 +792,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) { return } - if err := pull_service.CheckPRReadyToMerge(pr); err != nil { + if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil { if !models.IsErrNotAllowedToMerge(err) { ctx.Error(http.StatusInternalServerError, "CheckPRReadyToMerge", err) return @@ -903,7 +903,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) if models.IsErrUserNotExist(err) { ctx.NotFound("GetUserByName") } else { - ctx.ServerError("GetUserByName", err) + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) } return nil, nil, nil, nil, "", "" } @@ -947,7 +947,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) if err != nil { headGitRepo.Close() - ctx.ServerError("GetUserRepoPermission", err) + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) { @@ -966,7 +966,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) if err != nil { headGitRepo.Close() - ctx.ServerError("GetUserRepoPermission", err) + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return nil, nil, nil, nil, "", "" } if !permHead.CanRead(models.UnitTypeCode) { diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 3f2cb011d843..9e7fd156642f 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" + issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" ) @@ -318,14 +319,14 @@ func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions) if opts.CommitID == "" { gitRepo, err := git.OpenRepository(pr.Issue.Repo.RepoPath()) if err != nil { - ctx.ServerError("git.OpenRepository", err) + ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err) return } defer gitRepo.Close() headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { - ctx.ServerError("GetRefCommitID", err) + ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err) return } @@ -350,7 +351,7 @@ func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions) 0, // no reply opts.CommitID, ); err != nil { - ctx.ServerError("CreateCodeComment", err) + ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err) return } } @@ -539,3 +540,214 @@ func prepareSingleReview(ctx *context.APIContext) (*models.Review, *models.PullR return review, pr, false } + +// CreateReviewRequests create review requests to an pull request +func CreateReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) { + // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoCreatePullReviewRequests + // --- + // summary: create review requests for a pull request + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: index + // in: path + // description: index of the pull request + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/PullReviewRequestOptions" + // responses: + // "201": + // "$ref": "#/responses/PullReviewList" + // "422": + // "$ref": "#/responses/validationError" + // "404": + // "$ref": "#/responses/notFound" + apiReviewRequest(ctx, opts, true) +} + +// DeleteReviewRequests delete review requests to an pull request +func DeleteReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) { + // swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoDeletePullReviewRequests + // --- + // summary: cancel review requests for a pull request + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: index + // in: path + // description: index of the pull request + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/PullReviewRequestOptions" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "422": + // "$ref": "#/responses/validationError" + // "404": + // "$ref": "#/responses/notFound" + apiReviewRequest(ctx, opts, false) +} + +func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) { + pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrPullRequestNotExist(err) { + ctx.NotFound("GetPullRequestByIndex", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err) + } + return + } + + if err := pr.Issue.LoadRepo(); err != nil { + ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err) + return + } + + reviewers := make([]*models.User, 0, len(opts.Reviewers)) + + permDoer, err := models.GetUserRepoPermission(pr.Issue.Repo, ctx.User) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) + return + } + + for _, r := range opts.Reviewers { + var reviewer *models.User + if strings.Contains(r, "@") { + reviewer, err = models.GetUserByEmail(r) + } else { + reviewer, err = models.GetUserByName(r) + } + + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r)) + return + } + ctx.Error(http.StatusInternalServerError, "GetUser", err) + return + } + + err = issue_service.IsValidReviewRequest(reviewer, ctx.User, isAdd, pr.Issue, &permDoer) + if err != nil { + if models.IsErrNotValidReviewRequest(err) { + ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) + return + } + ctx.Error(http.StatusInternalServerError, "IsValidReviewRequest", err) + return + } + + reviewers = append(reviewers, reviewer) + } + + var reviews []*models.Review + if isAdd { + reviews = make([]*models.Review, 0, len(reviewers)) + } + + for _, reviewer := range reviewers { + comment, err := issue_service.ReviewRequest(pr.Issue, ctx.User, reviewer, isAdd) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) + return + } + + if comment != nil && isAdd { + if err = comment.LoadReview(); err != nil { + ctx.ServerError("ReviewRequest", err) + return + } + reviews = append(reviews, comment.Review) + } + } + + if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 { + + teamReviewers := make([]*models.Team, 0, len(opts.TeamReviewers)) + for _, t := range opts.TeamReviewers { + var teamReviewer *models.Team + teamReviewer, err = models.GetTeam(ctx.Repo.Owner.ID, t) + if err != nil { + if models.IsErrTeamNotExist(err) { + ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t)) + return + } + ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) + return + } + + err = issue_service.IsValidTeamReviewRequest(teamReviewer, ctx.User, isAdd, pr.Issue) + if err != nil { + if models.IsErrNotValidReviewRequest(err) { + ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) + return + } + ctx.Error(http.StatusInternalServerError, "IsValidTeamReviewRequest", err) + return + } + + teamReviewers = append(teamReviewers, teamReviewer) + } + + for _, teamReviewer := range teamReviewers { + comment, err := issue_service.TeamReviewRequest(pr.Issue, ctx.User, teamReviewer, isAdd) + if err != nil { + ctx.ServerError("TeamReviewRequest", err) + return + } + + if comment != nil && isAdd { + if err = comment.LoadReview(); err != nil { + ctx.ServerError("ReviewRequest", err) + return + } + reviews = append(reviews, comment.Review) + } + } + } + + if isAdd { + apiReviews, err := convert.ToPullReviewList(reviews, ctx.User) + if err != nil { + ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err) + return + } + ctx.JSON(http.StatusCreated, apiReviews) + } else { + ctx.Status(http.StatusNoContent) + return + } +} diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 752b5c76e080..358cc011433a 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" releaseservice "code.gitea.io/gitea/services/release" @@ -41,22 +42,26 @@ func GetRelease(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/Release" + // "404": + // "$ref": "#/responses/notFound" id := ctx.ParamsInt64(":id") release, err := models.GetReleaseByID(id) - if err != nil { + if err != nil && !models.IsErrReleaseNotExist(err) { ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err) return } - if release.RepoID != ctx.Repo.Repository.ID { + if err != nil && models.IsErrReleaseNotExist(err) || + release.IsTag || release.RepoID != ctx.Repo.Repository.ID { ctx.NotFound() return } + if err := release.LoadAttributes(); err != nil { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, release.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToRelease(release)) } // ListReleases list a repository's releases @@ -113,7 +118,7 @@ func ListReleases(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - rels[i] = release.APIFormat() + rels[i] = convert.ToRelease(release) } ctx.JSON(http.StatusOK, rels) } @@ -145,13 +150,15 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { // responses: // "201": // "$ref": "#/responses/Release" + // "404": + // "$ref": "#/responses/notFound" // "409": // "$ref": "#/responses/error" rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName) if err != nil { if !models.IsErrReleaseNotExist(err) { - ctx.ServerError("GetRelease", err) + ctx.Error(http.StatusInternalServerError, "GetRelease", err) return } // If target is not provided use default branch @@ -195,11 +202,11 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { rel.Publisher = ctx.User if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, nil, true); err != nil { - ctx.ServerError("UpdateReleaseOrCreatReleaseFromTag", err) + ctx.Error(http.StatusInternalServerError, "UpdateReleaseOrCreatReleaseFromTag", err) return } } - ctx.JSON(http.StatusCreated, rel.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToRelease(rel)) } // EditRelease edit a release @@ -235,6 +242,8 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) { // responses: // "200": // "$ref": "#/responses/Release" + // "404": + // "$ref": "#/responses/notFound" id := ctx.ParamsInt64(":id") rel, err := models.GetReleaseByID(id) @@ -280,7 +289,7 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, rel.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToRelease(rel)) } // DeleteRelease delete a release from a repository @@ -308,6 +317,8 @@ func DeleteRelease(ctx *context.APIContext) { // responses: // "204": // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" id := ctx.ParamsInt64(":id") rel, err := models.GetReleaseByID(id) diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go index 3d1084f211fd..51e1b160dad8 100644 --- a/routers/api/v1/repo/release_attachment.go +++ b/routers/api/v1/repo/release_attachment.go @@ -6,10 +6,10 @@ package repo import ( "net/http" - "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -63,7 +63,7 @@ func GetReleaseAttachment(ctx *context.APIContext) { return } // FIXME Should prove the existence of the given repo, but results in unnecessary database requests - ctx.JSON(http.StatusOK, attach.APIFormat()) + ctx.JSON(http.StatusOK, convert.ToReleaseAttachment(attach)) } // ListReleaseAttachments lists all attachments of the release @@ -108,7 +108,7 @@ func ListReleaseAttachments(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, release.APIFormat().Attachments) + ctx.JSON(http.StatusOK, convert.ToRelease(release).Attachments) } // CreateReleaseAttachment creates an attachment and saves the given file @@ -182,7 +182,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { } // Check if the filetype is allowed by the settings - err = upload.VerifyAllowedContentType(buf, strings.Split(setting.Attachment.AllowedTypes, ",")) + err = upload.Verify(buf, header.Filename, setting.Repository.Release.AllowedTypes) if err != nil { ctx.Error(http.StatusBadRequest, "DetectContentType", err) return @@ -204,7 +204,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { return } - ctx.JSON(http.StatusCreated, attach.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach)) } // EditReleaseAttachment updates the given attachment @@ -268,7 +268,7 @@ func EditReleaseAttachment(ctx *context.APIContext, form api.EditAttachmentOptio if err := models.UpdateAttachment(attach); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach) } - ctx.JSON(http.StatusCreated, attach.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach)) } // DeleteReleaseAttachment delete a given attachment diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go new file mode 100644 index 000000000000..2a72e0000e65 --- /dev/null +++ b/routers/api/v1/repo/release_tags.go @@ -0,0 +1,61 @@ +// 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 repo + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" +) + +// GetReleaseTag get a single release of a repository by its tagname +func GetReleaseTag(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/releases/tags/{tag} repository repoGetReleaseTag + // --- + // summary: Get a release by tag name + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: tag + // in: path + // description: tagname of the release to get + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Release" + // "404": + // "$ref": "#/responses/notFound" + + tag := ctx.Params(":tag") + + release, err := models.GetRelease(ctx.Repo.Repository.ID, tag) + if err != nil { + if models.IsErrReleaseNotExist(err) { + ctx.Error(http.StatusNotFound, "GetRelease", err) + return + } + ctx.Error(http.StatusInternalServerError, "GetRelease", err) + return + } + + if err := release.LoadAttributes(); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) + return + } + ctx.JSON(http.StatusOK, convert.ToRelease(release)) +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index d4184d6148a5..be340cb07e9d 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -244,6 +244,8 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR IsPrivate: opt.Private, AutoInit: opt.AutoInit, DefaultBranch: opt.DefaultBranch, + TrustModel: models.ToTrustModel(opt.TrustModel), + IsTemplate: opt.Template, }) if err != nil { if models.IsErrRepoAlreadyExist(err) { @@ -257,6 +259,12 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR return } + // reload repo from db to get a real state after creation + repo, err = models.GetRepositoryByID(repo.ID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err) + } + ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner)) } @@ -366,7 +374,7 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) { if !ctx.User.IsAdmin { canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) if err != nil { - ctx.ServerError("CanCreateOrgRepo", err) + ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err) return } else if !canCreate { ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.") @@ -820,3 +828,28 @@ func Delete(ctx *context.APIContext) { log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name) ctx.Status(http.StatusNoContent) } + +// GetIssueTemplates returns the issue templates for a repository +func GetIssueTemplates(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/issue_templates repository repoGetIssueTemplates + // --- + // summary: Get available issue templates for a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/IssueTemplates" + + ctx.JSON(http.StatusOK, ctx.IssueTemplatesFromDefaultBranch()) +} diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 69661dca9177..e7318edaa517 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/repofiles" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/utils" @@ -64,7 +65,7 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) { return } - ctx.JSON(http.StatusCreated, status.APIFormat()) + ctx.JSON(http.StatusCreated, convert.ToCommitStatus(status)) } // GetCommitStatuses returns all statuses for any given commit hash @@ -222,7 +223,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) { apiStatuses := make([]*api.Status, 0, len(statuses)) for _, status := range statuses { - apiStatuses = append(apiStatuses, status.APIFormat()) + apiStatuses = append(apiStatuses, convert.ToCommitStatus(status)) } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) @@ -305,7 +306,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) { retStatus.Statuses = make([]*api.Status, 0, len(statuses)) for _, status := range statuses { - retStatus.Statuses = append(retStatus.Statuses, status.APIFormat()) + retStatus.Statuses = append(retStatus.Statuses, convert.ToCommitStatus(status)) if status.State.NoBetterThan(retStatus.State) { retStatus.State = status.State } diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go index 8403a51d3eaa..2b7b11bfd8a8 100644 --- a/routers/api/v1/settings/settings.go +++ b/routers/api/v1/settings/settings.go @@ -27,6 +27,24 @@ func GetGeneralUISettings(ctx *context.APIContext) { }) } +// GetGeneralAPISettings returns instance's global settings for api +func GetGeneralAPISettings(ctx *context.APIContext) { + // swagger:operation GET /settings/api settings getGeneralAPISettings + // --- + // summary: Get instance's global settings for api + // produces: + // - application/json + // responses: + // "200": + // "$ref": "#/responses/GeneralAPISettings" + ctx.JSON(http.StatusOK, api.GeneralAPISettings{ + MaxResponseItems: setting.API.MaxResponseItems, + DefaultPagingNum: setting.API.DefaultPagingNum, + DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage, + DefaultMaxBlobSize: setting.API.DefaultMaxBlobSize, + }) +} + // GetGeneralRepoSettings returns instance's global settings for repositories func GetGeneralRepoSettings(ctx *context.APIContext) { // swagger:operation GET /settings/repository settings getGeneralRepositorySettings @@ -42,3 +60,21 @@ func GetGeneralRepoSettings(ctx *context.APIContext) { HTTPGitDisabled: setting.Repository.DisableHTTPGit, }) } + +// GetGeneralAttachmentSettings returns instance's global settings for Attachment +func GetGeneralAttachmentSettings(ctx *context.APIContext) { + // swagger:operation GET /settings/attachment settings getGeneralAttachmentSettings + // --- + // summary: Get instance's global settings for Attachment + // produces: + // - application/json + // responses: + // "200": + // "$ref": "#/responses/GeneralAttachmentSettings" + ctx.JSON(http.StatusOK, api.GeneralAttachmentSettings{ + Enabled: setting.Attachment.Enabled, + AllowedTypes: setting.Attachment.AllowedTypes, + MaxFiles: setting.Attachment.MaxFiles, + MaxSize: setting.Attachment.MaxSize, + }) +} diff --git a/routers/api/v1/swagger/cron.go b/routers/api/v1/swagger/cron.go new file mode 100644 index 000000000000..85f2ed0e3520 --- /dev/null +++ b/routers/api/v1/swagger/cron.go @@ -0,0 +1,16 @@ +// 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 swagger + +import ( + api "code.gitea.io/gitea/modules/structs" +) + +// CronList +// swagger:response CronList +type swaggerResponseCronList struct { + // in:body + Body []api.Cron `json:"body"` +} diff --git a/routers/api/v1/swagger/issue.go b/routers/api/v1/swagger/issue.go index b12ea0096a41..0f2f57202080 100644 --- a/routers/api/v1/swagger/issue.go +++ b/routers/api/v1/swagger/issue.go @@ -85,6 +85,13 @@ type swaggerIssueDeadline struct { Body api.IssueDeadline `json:"body"` } +// IssueTemplates +// swagger:response IssueTemplates +type swaggerIssueTemplates struct { + // in:body + Body []api.IssueTemplate `json:"body"` +} + // StopWatch // swagger:response StopWatch type swaggerResponseStopWatch struct { diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index d9ef05c33599..a3bb9cc65781 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -149,4 +149,10 @@ type swaggerParameterBodies struct { // in:body SubmitPullReviewOptions api.SubmitPullReviewOptions + + // in:body + MigrateRepoOptions api.MigrateRepoOptions + + // in:body + PullReviewRequestOptions api.PullReviewRequestOptions } diff --git a/routers/api/v1/swagger/settings.go b/routers/api/v1/swagger/settings.go index 45266e51df6b..4bf153cb9c52 100644 --- a/routers/api/v1/swagger/settings.go +++ b/routers/api/v1/swagger/settings.go @@ -19,3 +19,17 @@ type swaggerResponseGeneralUISettings struct { // in:body Body api.GeneralUISettings `json:"body"` } + +// GeneralAPISettings +// swagger:response GeneralAPISettings +type swaggerResponseGeneralAPISettings struct { + // in:body + Body api.GeneralAPISettings `json:"body"` +} + +// GeneralAttachmentSettings +// swagger:response GeneralAttachmentSettings +type swaggerResponseGeneralAttachmentSettings struct { + // in:body + Body api.GeneralAttachmentSettings `json:"body"` +} diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 624beff5bb32..d02b8cea21e2 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -7,7 +7,9 @@ package user import ( "errors" + "fmt" "net/http" + "strconv" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -41,7 +43,7 @@ func ListAccessTokens(ctx *context.APIContext) { // "200": // "$ref": "#/responses/AccessTokenList" - tokens, err := models.ListAccessTokens(ctx.User.ID, utils.GetListOptions(ctx)) + tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID, ListOptions: utils.GetListOptions(ctx)}) if err != nil { ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) return @@ -128,15 +130,44 @@ func DeleteAccessToken(ctx *context.APIContext) { // required: true // - name: token // in: path - // description: token to be deleted - // type: integer - // format: int64 + // description: token to be deleted, identified by ID and if not available by name + // type: string // required: true // responses: // "204": // "$ref": "#/responses/empty" + // "422": + // "$ref": "#/responses/error" + + token := ctx.Params(":id") + tokenID, _ := strconv.ParseInt(token, 0, 64) + + if tokenID == 0 { + tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{ + Name: token, + UserID: ctx.User.ID, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) + return + } + + switch len(tokens) { + case 0: + ctx.NotFound() + return + case 1: + tokenID = tokens[0].ID + default: + ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multible matches for token name '%s'", token)) + return + } + } + if tokenID == 0 { + ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil) + return + } - tokenID := ctx.ParamsInt64(":id") if err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil { if models.IsErrAccessTokenNotExist(err) { ctx.NotFound() diff --git a/routers/home.go b/routers/home.go index 5425670878ed..d37bf8e31b5d 100644 --- a/routers/home.go +++ b/routers/home.go @@ -303,10 +303,8 @@ func ExploreCode(ctx *context.Context) { repoIDs []int64 err error isAdmin bool - userID int64 ) if ctx.User != nil { - userID = ctx.User.ID isAdmin = ctx.User.IsAdmin } @@ -336,7 +334,7 @@ func ExploreCode(ctx *context.Context) { var rightRepoMap = make(map[int64]*models.Repository, len(repoMaps)) repoIDs = make([]int64, 0, len(repoMaps)) for id, repo := range repoMaps { - if repo.CheckUnitUser(userID, isAdmin, models.UnitTypeCode) { + if repo.CheckUnitUser(ctx.User, models.UnitTypeCode) { rightRepoMap[id] = repo repoIDs = append(repoIDs, id) } diff --git a/routers/init.go b/routers/init.go index 2f12058ac516..702acb726090 100644 --- a/routers/init.go +++ b/routers/init.go @@ -35,6 +35,7 @@ import ( "code.gitea.io/gitea/services/mailer" mirror_service "code.gitea.io/gitea/services/mirror" pull_service "code.gitea.io/gitea/services/pull" + "code.gitea.io/gitea/services/repository" "gitea.com/macaron/i18n" "gitea.com/macaron/macaron" @@ -58,6 +59,9 @@ func NewServices() { if err := storage.Init(); err != nil { log.Fatal("storage init failed: %v", err) } + if err := repository.NewContext(); err != nil { + log.Fatal("repository init failed: %v", err) + } mailer.NewContext() _ = cache.NewContext() notification.NewContext() @@ -113,9 +117,46 @@ func InitLocales() { }) } +// PreInstallInit preloads the configuration to check if we need to run install +func PreInstallInit(ctx context.Context) bool { + setting.NewContext() + if !setting.InstallLock { + log.Trace("AppPath: %s", setting.AppPath) + log.Trace("AppWorkPath: %s", setting.AppWorkPath) + log.Trace("Custom path: %s", setting.CustomPath) + log.Trace("Log path: %s", setting.LogRootPath) + log.Trace("Preparing to run install page") + InitLocales() + if setting.EnableSQLite3 { + log.Info("SQLite3 Supported") + } + setting.InitDBConfig() + svg.Init() + } + + return !setting.InstallLock +} + +// PostInstallInit rereads the settings and starts up the database +func PostInstallInit(ctx context.Context) { + setting.NewContext() + setting.InitDBConfig() + if setting.InstallLock { + if err := initDBEngine(ctx); err == nil { + log.Info("ORM engine initialization successful!") + } else { + log.Fatal("ORM engine initialization failed: %v", err) + } + svg.Init() + } +} + // GlobalInit is for global configuration reload-able. func GlobalInit(ctx context.Context) { setting.NewContext() + if !setting.InstallLock { + log.Fatal("Gitea is not installed") + } if err := git.Init(ctx); err != nil { log.Fatal("Git module init failed: %v", err) } @@ -130,59 +171,50 @@ func GlobalInit(ctx context.Context) { NewServices() - if setting.InstallLock { - highlight.NewContext() - external.RegisterParsers() - markup.Init() - if err := initDBEngine(ctx); err == nil { - log.Info("ORM engine initialization successful!") - } else { - log.Fatal("ORM engine initialization failed: %v", err) - } + highlight.NewContext() + external.RegisterParsers() + markup.Init() + if err := initDBEngine(ctx); err == nil { + log.Info("ORM engine initialization successful!") + } else { + log.Fatal("ORM engine initialization failed: %v", err) + } - if err := models.InitOAuth2(); err != nil { - log.Fatal("Failed to initialize OAuth2 support: %v", err) - } + if err := models.InitOAuth2(); err != nil { + log.Fatal("Failed to initialize OAuth2 support: %v", err) + } - models.NewRepoContext() + models.NewRepoContext() - // Booting long running goroutines. - cron.NewContext() - issue_indexer.InitIssueIndexer(false) - code_indexer.Init() - if err := stats_indexer.Init(); err != nil { - log.Fatal("Failed to initialize repository stats indexer queue: %v", err) - } - mirror_service.InitSyncMirrors() - webhook.InitDeliverHooks() - if err := pull_service.Init(); err != nil { - log.Fatal("Failed to initialize test pull requests queue: %v", err) - } - if err := task.Init(); err != nil { - log.Fatal("Failed to initialize task scheduler: %v", err) - } - eventsource.GetManager().Init() + // Booting long running goroutines. + cron.NewContext() + issue_indexer.InitIssueIndexer(false) + code_indexer.Init() + if err := stats_indexer.Init(); err != nil { + log.Fatal("Failed to initialize repository stats indexer queue: %v", err) } + mirror_service.InitSyncMirrors() + webhook.InitDeliverHooks() + if err := pull_service.Init(); err != nil { + log.Fatal("Failed to initialize test pull requests queue: %v", err) + } + if err := task.Init(); err != nil { + log.Fatal("Failed to initialize task scheduler: %v", err) + } + eventsource.GetManager().Init() + if setting.EnableSQLite3 { log.Info("SQLite3 Supported") } checkRunMode() - // Now because Install will re-run GlobalInit once it has set InstallLock - // we can't tell if the ssh port will remain unused until that's done. - // However, see FIXME comment in install.go - if setting.InstallLock { - if setting.SSH.StartBuiltinServer { - ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - } else { - ssh.Unused() - } - } - - if setting.InstallLock { - sso.Init() + if setting.SSH.StartBuiltinServer { + ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + } else { + ssh.Unused() } + sso.Init() svg.Init() } diff --git a/routers/install.go b/routers/install.go index 9eda18f941ba..5d0d089dc08b 100644 --- a/routers/install.go +++ b/routers/install.go @@ -5,7 +5,7 @@ package routers import ( - "errors" + "net/http" "os" "os/exec" "path/filepath" @@ -27,13 +27,15 @@ import ( const ( // tplInstall template for installation page - tplInstall base.TplName = "install" + tplInstall base.TplName = "install" + tplPostInstall base.TplName = "post-install" ) // InstallInit prepare for rendering installation page func InstallInit(ctx *context.Context) { if setting.InstallLock { - ctx.NotFound("Install", errors.New("Installation is prohibited")) + ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + ctx.HTML(200, tplPostInstall) return } @@ -71,7 +73,7 @@ func Install(ctx *context.Context) { // Application general settings form.AppName = setting.AppName form.RepoRootPath = setting.RepoRootPath - form.LFSRootPath = setting.LFS.ContentPath + form.LFSRootPath = setting.LFS.Path // Note(unknown): it's hard for Windows users change a running user, // so just use current one if config says default. @@ -172,7 +174,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { } // Test repository root path. - form.RepoRootPath = strings.Replace(form.RepoRootPath, "\\", "/", -1) + form.RepoRootPath = strings.ReplaceAll(form.RepoRootPath, "\\", "/") if err = os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil { ctx.Data["Err_RepoRootPath"] = true ctx.RenderWithErr(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form) @@ -181,7 +183,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { // Test LFS root path if not empty, empty meaning disable LFS if form.LFSRootPath != "" { - form.LFSRootPath = strings.Replace(form.LFSRootPath, "\\", "/", -1) + form.LFSRootPath = strings.ReplaceAll(form.LFSRootPath, "\\", "/") if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil { ctx.Data["Err_LFSRootPath"] = true ctx.RenderWithErr(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form) @@ -190,7 +192,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { } // Test log root path. - form.LogRootPath = strings.Replace(form.LogRootPath, "\\", "/", -1) + form.LogRootPath = strings.ReplaceAll(form.LogRootPath, "\\", "/") if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil { ctx.Data["Err_LogRootPath"] = true ctx.RenderWithErr(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form) @@ -271,6 +273,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode) cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset) cfg.Section("database").Key("PATH").SetValue(setting.Database.Path) + cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful cfg.Section("").Key("APP_NAME").SetValue(form.AppName) cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath) @@ -330,9 +333,12 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { cfg.Section("session").Key("PROVIDER").SetValue("file") - cfg.Section("log").Key("MODE").SetValue("file") + cfg.Section("log").Key("MODE").SetValue("console") cfg.Section("log").Key("LEVEL").SetValue(setting.LogLevel) cfg.Section("log").Key("ROOT_PATH").SetValue(form.LogRootPath) + cfg.Section("log").Key("REDIRECT_MACARON_LOG").SetValue("true") + cfg.Section("log").Key("MACARON").SetValue("console") + cfg.Section("log").Key("ROUTER").SetValue("console") cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") var secretKey string @@ -353,7 +359,8 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { return } - GlobalInit(graceful.GetManager().HammerContext()) + // Re-read settings + PostInstallInit(ctx.Req.Context()) // Create admin account if len(form.AdminName) > 0 { @@ -376,6 +383,11 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { u, _ = models.GetUserByName(u.Name) } + days := 86400 * setting.LogInRememberDays + ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true) + ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), + setting.CookieRememberName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true) + // Auto-login for admin if err = ctx.Session.Set("uid", u.ID); err != nil { ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) @@ -393,12 +405,18 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { } log.Info("First-time run install finished!") - // FIXME: This isn't really enough to completely take account of new configuration - // We should really be restarting: - // - On windows this is probably just a simple restart - // - On linux we can't just use graceful.RestartProcess() everything that was passed in on LISTEN_FDS - // (active or not) needs to be passed out and everything new passed out too. - // This means we need to prevent the cleanup goroutine from running prior to the second GlobalInit + ctx.Flash.Success(ctx.Tr("install.install_success")) - ctx.Redirect(form.AppURL + "user/login") + + ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + ctx.HTML(200, tplPostInstall) + + // Now get the http.Server from this request and shut it down + // NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown + srv := ctx.Req.Context().Value(http.ServerContextKey).(*http.Server) + go func() { + if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil { + log.Error("Unable to shutdown the install server! Error: %v", err) + } + }() } diff --git a/routers/org/home.go b/routers/org/home.go index fa61218d3fd1..0a9f176bdc71 100644 --- a/routers/org/home.go +++ b/routers/org/home.go @@ -32,6 +32,7 @@ func Home(ctx *context.Context) { return } + ctx.Data["PageIsUserProfile"] = true ctx.Data["Title"] = org.DisplayName() var orderBy models.SearchOrderBy @@ -119,6 +120,7 @@ func Home(ctx *context.Context) { return } + ctx.Data["Owner"] = org ctx.Data["Repos"] = repos ctx.Data["Total"] = count ctx.Data["MembersTotal"] = membersCount diff --git a/routers/private/hook.go b/routers/private/hook.go index 2bccca3e3e30..a2033fc1dd73 100644 --- a/routers/private/hook.go +++ b/routers/private/hook.go @@ -18,14 +18,13 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" pull_service "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" "gitea.com/macaron/macaron" "github.com/go-git/go-git/v5/plumbing" - "github.com/gobwas/glob" ) func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error { @@ -59,53 +58,6 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] return err } -func checkFileProtection(oldCommitID, newCommitID string, patterns []glob.Glob, repo *git.Repository, env []string) error { - - stdoutReader, stdoutWriter, err := os.Pipe() - if err != nil { - log.Error("Unable to create os.Pipe for %s", repo.Path) - return err - } - defer func() { - _ = stdoutReader.Close() - _ = stdoutWriter.Close() - }() - - // This use of ... is safe as force-pushes have already been ruled out. - err = git.NewCommand("diff", "--name-only", oldCommitID+"..."+newCommitID). - RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, - stdoutWriter, nil, nil, - func(ctx context.Context, cancel context.CancelFunc) error { - _ = stdoutWriter.Close() - - scanner := bufio.NewScanner(stdoutReader) - for scanner.Scan() { - path := strings.TrimSpace(scanner.Text()) - if len(path) == 0 { - continue - } - lpath := strings.ToLower(path) - for _, pat := range patterns { - if pat.Match(lpath) { - cancel() - return models.ErrFilePathProtected{ - Path: path, - } - } - } - } - if err := scanner.Err(); err != nil { - return err - } - _ = stdoutReader.Close() - return err - }) - if err != nil && !models.IsErrFilePathProtected(err) { - log.Error("Unable to check file protection for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) - } - return err -} - func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error { scanner := bufio.NewScanner(input) for scanner.Scan() { @@ -202,6 +154,7 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { private.GitQuarantinePath+"="+opts.GitQuarantinePath) } + // Iterate across the provided old commit IDs for i := range opts.OldCommitIDs { oldCommitID := opts.OldCommitIDs[i] newCommitID := opts.NewCommitIDs[i] @@ -224,143 +177,189 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { }) return } - if protectBranch != nil && protectBranch.IsProtected() { - // detect and prevent deletion - if newCommitID == git.EmptySHA { - log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) + + // Allow pushes to non-protected branches + if protectBranch == nil || !protectBranch.IsProtected() { + continue + } + + // This ref is a protected branch. + // + // First of all we need to enforce absolutely: + // + // 1. Detect and prevent deletion of the branch + if newCommitID == git.EmptySHA { + log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("branch %s is protected from deletion", branchName), + }) + return + } + + // 2. Disallow force pushes to protected branches + if git.EmptySHA != oldCommitID { + output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env) + if err != nil { + log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Fail to detect force push: %v", err), + }) + return + } else if len(output) > 0 { + log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("branch %s is protected from deletion", branchName), + "err": fmt.Sprintf("branch %s is protected from force push", branchName), }) return + } + } - // detect force push - if git.EmptySHA != oldCommitID { - output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env) - if err != nil { - log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) + // 3. Enforce require signed commits + if protectBranch.RequireSignedCommits { + err := verifyCommits(oldCommitID, newCommitID, gitRepo, env) + if err != nil { + if !isErrUnverifiedCommit(err) { + log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Fail to detect force push: %v", err), + "err": fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err), }) return - } else if len(output) > 0 { - log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo) - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("branch %s is protected from force push", branchName), - }) - return - } + unverifiedCommit := err.(*errUnverifiedCommit).sha + log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit), + }) + return } + } - // Require signed commits - if protectBranch.RequireSignedCommits { - err := verifyCommits(oldCommitID, newCommitID, gitRepo, env) - if err != nil { - if !isErrUnverifiedCommit(err) { - log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err), - }) - return - } - unverifiedCommit := err.(*errUnverifiedCommit).sha - log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit) - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit), + // Now there are several tests which can be overridden: + // + // 4. Check protected file patterns - this is overridable from the UI + changedProtectedfiles := false + protectedFilePath := "" + + globs := protectBranch.GetProtectedFilePatterns() + if len(globs) > 0 { + _, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, env, gitRepo) + if err != nil { + if !models.IsErrFilePathProtected(err) { + log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), }) return } + + changedProtectedfiles = true + protectedFilePath = err.(models.ErrFilePathProtected).Path } + } - // Detect Protected file pattern - globs := protectBranch.GetProtectedFilePatterns() - if len(globs) > 0 { - err := checkFileProtection(oldCommitID, newCommitID, globs, gitRepo, env) - if err != nil { - if !models.IsErrFilePathProtected(err) { - log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), - }) - return - } - protectedFilePath := err.(models.ErrFilePathProtected).Path + // 5. Check if the doer is allowed to push + canPush := false + if opts.IsDeployKey { + canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) + } else { + canPush = !changedProtectedfiles && protectBranch.CanUserPush(opts.UserID) + } + + // 6. If we're not allowed to push directly + if !canPush { + // Is this is a merge from the UI/API? + if opts.ProtectedBranchID == 0 { + // 6a. If we're not merging from the UI/API then there are two ways we got here: + // + // We are changing a protected file and we're not allowed to do that + if changedProtectedfiles { log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) ctx.JSON(http.StatusForbidden, map[string]interface{}{ "err": fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), }) return } + + // Or we're simply not able to push to this protected branch + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + }) + return + } + // 6b. Merge (from UI or API) + + // Get the PR, user and permissions for the user in the repository + pr, err := models.GetPullRequestByID(opts.ProtectedBranchID) + if err != nil { + log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err), + }) + return + } + user, err := models.GetUserByID(opts.UserID) + if err != nil { + log.Error("Unable to get User id %d Error: %v", opts.UserID, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err), + }) + return + } + perm, err := models.GetUserRepoPermission(repo, user) + if err != nil { + log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err), + }) + return } - canPush := false - if opts.IsDeployKey { - canPush = protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys) - } else { - canPush = protectBranch.CanUserPush(opts.UserID) + // Now check if the user is allowed to merge PRs for this repository + allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user) + if err != nil { + log.Error("Error calculating if allowed to merge: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Error calculating if allowed to merge: %v", err), + }) + return } - if !canPush && opts.ProtectedBranchID > 0 { - // Merge (from UI or API) - pr, err := models.GetPullRequestByID(opts.ProtectedBranchID) - if err != nil { - log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err), - }) - return - } - user, err := models.GetUserByID(opts.UserID) - if err != nil { - log.Error("Unable to get User id %d Error: %v", opts.UserID, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err), - }) - return - } - perm, err := models.GetUserRepoPermission(repo, user) - if err != nil { - log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err), - }) - return - } - allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user) - if err != nil { - log.Error("Error calculating if allowed to merge: %v", err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Error calculating if allowed to merge: %v", err), - }) - return - } - if !allowedMerge { - log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index) + + if !allowedMerge { + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + }) + return + } + + // If we're an admin for the repository we can ignore status checks, reviews and override protected files + if perm.IsAdmin() { + continue + } + + // Now if we're not an admin - we can't overwrite protected files so fail now + if changedProtectedfiles { + log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath), + }) + return + } + + // Check all status checks and reviews are ok + if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil { + if models.IsErrNotAllowedToMerge(err) { + log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error()) ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + "err": fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.ProtectedBranchID, err.Error()), }) return } - // Check all status checks and reviews is ok, unless repo admin which can bypass this. - if !perm.IsAdmin() { - if err := pull_service.CheckPRReadyToMerge(pr); err != nil { - if models.IsErrNotAllowedToMerge(err) { - log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error()) - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.ProtectedBranchID, err.Error()), - }) - return - } - log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "err": fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.ProtectedBranchID, err), - }) - } - } - } else if !canPush { - log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo) - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.ProtectedBranchID, err), }) return } @@ -376,7 +375,7 @@ func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) { repoName := ctx.Params(":repo") var repo *models.Repository - updates := make([]*repofiles.PushUpdateOptions, 0, len(opts.OldCommitIDs)) + updates := make([]*repo_service.PushUpdateOptions, 0, len(opts.OldCommitIDs)) wasEmpty := false for i := range opts.OldCommitIDs { @@ -403,7 +402,7 @@ func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) { wasEmpty = repo.IsEmpty } - option := repofiles.PushUpdateOptions{ + option := repo_service.PushUpdateOptions{ RefFullName: refFullName, OldCommitID: opts.OldCommitIDs[i], NewCommitID: opts.NewCommitIDs[i], @@ -422,7 +421,7 @@ func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) { } if repo != nil && len(updates) > 0 { - if err := repofiles.PushUpdates(repo, updates); err != nil { + if err := repo_service.PushUpdates(updates); err != nil { log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates)) for i, update := range updates { log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.BranchName()) diff --git a/routers/private/serv.go b/routers/private/serv.go index 2e79fd79acda..79683c2826de 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -46,7 +46,7 @@ func ServNoCommand(ctx *macaron.Context) { } results.Key = key - if key.Type == models.KeyTypeUser { + if key.Type == models.KeyTypeUser || key.Type == models.KeyTypePrincipal { user, err := models.GetUserByID(key.OwnerID) if err != nil { if models.IsErrUserNotExist(err) { @@ -217,6 +217,18 @@ func ServCommand(ctx *macaron.Context) { // so for now use the owner of the repository results.UserName = results.OwnerName results.UserID = repo.OwnerID + if err = repo.GetOwner(); err != nil { + log.Error("Unable to get owner for repo %-v. Error: %v", repo, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "results": results, + "type": "InternalServerError", + "err": fmt.Sprintf("Unable to get owner for repo: %s/%s.", results.OwnerName, results.RepoName), + }) + return + } + if !repo.Owner.KeepEmailPrivate { + results.UserEmail = repo.Owner.Email + } } else { // Get the user represented by the Key var err error @@ -239,6 +251,9 @@ func ServCommand(ctx *macaron.Context) { return } results.UserName = user.Name + if !user.KeepEmailPrivate { + results.UserEmail = user.Email + } } // Don't allow pushing if the repo is archived diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index d3201faec0f6..5b699abc8d11 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -7,7 +7,6 @@ package repo import ( "fmt" "net/http" - "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -17,16 +16,18 @@ import ( "code.gitea.io/gitea/modules/upload" ) -func renderAttachmentSettings(ctx *context.Context) { - ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled - ctx.Data["AttachmentStoreType"] = setting.Attachment.StoreType - ctx.Data["AttachmentAllowedTypes"] = setting.Attachment.AllowedTypes - ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize - ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles +// UploadIssueAttachment response for Issue/PR attachments +func UploadIssueAttachment(ctx *context.Context) { + uploadAttachment(ctx, setting.Attachment.AllowedTypes) } -// UploadAttachment response for uploading issue's attachment -func UploadAttachment(ctx *context.Context) { +// UploadReleaseAttachment response for uploading release attachments +func UploadReleaseAttachment(ctx *context.Context) { + uploadAttachment(ctx, setting.Repository.Release.AllowedTypes) +} + +// UploadAttachment response for uploading attachments +func uploadAttachment(ctx *context.Context, allowedTypes string) { if !setting.Attachment.Enabled { ctx.Error(404, "attachment is not enabled") return @@ -45,7 +46,7 @@ func UploadAttachment(ctx *context.Context) { buf = buf[:n] } - err = upload.VerifyAllowedContentType(buf, strings.Split(setting.Attachment.AllowedTypes, ",")) + err = upload.Verify(buf, header.Filename, allowedTypes) if err != nil { ctx.Error(400, err.Error()) return diff --git a/routers/repo/branch.go b/routers/repo/branch.go index 4d8b9158fe60..cd18f667772c 100644 --- a/routers/repo/branch.go +++ b/routers/repo/branch.go @@ -19,6 +19,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/utils" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -118,10 +119,8 @@ func RestoreBranchPost(ctx *context.Context) { } // Don't return error below this - if err := repofiles.PushUpdate( - ctx.Repo.Repository, - deletedBranch.Name, - repofiles.PushUpdateOptions{ + if err := repo_service.PushUpdate( + &repo_service.PushUpdateOptions{ RefFullName: git.BranchPrefix + deletedBranch.Name, OldCommitID: git.EmptySHA, NewCommitID: deletedBranch.Commit, @@ -157,10 +156,8 @@ func deleteBranch(ctx *context.Context, branchName string) error { } // Don't return error below this - if err := repofiles.PushUpdate( - ctx.Repo.Repository, - branchName, - repofiles.PushUpdateOptions{ + if err := repo_service.PushUpdate( + &repo_service.PushUpdateOptions{ RefFullName: git.BranchPrefix + branchName, OldCommitID: commit.ID.String(), NewCommitID: git.EmptySHA, @@ -358,7 +355,16 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) { if len(e.Message) == 0 { ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message")) } else { - ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(e.Message))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.push_rejected"), + "Summary": ctx.Tr("repo.editor.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(e.Message), + }) + if err != nil { + ctx.ServerError("UpdatePullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) } ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return diff --git a/routers/repo/compare.go b/routers/repo/compare.go index 13ac152a3027..7965a5a472b4 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/services/gitdiff" ) @@ -585,8 +586,9 @@ func CompareDiff(ctx *context.Context) { ctx.Data["RequireTribute"] = true ctx.Data["RequireSimpleMDE"] = true ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes - setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates) - renderAttachmentSettings(ctx) + setTemplateIfExists(ctx, pullRequestTemplateKey, nil, pullRequestTemplateCandidates) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) diff --git a/routers/repo/download.go b/routers/repo/download.go index 326f097cbcc0..2f1f2d3c47e5 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -34,7 +34,7 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error { name = path.Base(name) // Google Chrome dislike commas in filenames, so let's change it to a space - name = strings.Replace(name, ",", " ", -1) + name = strings.ReplaceAll(name, ",", " ") if base.IsTextFile(buf) || ctx.QueryBool("render") { cs, err := charset.DetectEncoding(buf) diff --git a/routers/repo/editor.go b/routers/repo/editor.go index f91ce1b462f4..1ee557a4fdf1 100644 --- a/routers/repo/editor.go +++ b/routers/repo/editor.go @@ -184,6 +184,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo } ctx.Data["PageIsEdit"] = true + ctx.Data["PageHasPosted"] = true ctx.Data["IsNewFile"] = isNewFile ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireSimpleMDE"] = true @@ -237,7 +238,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo FromTreePath: ctx.Repo.TreePath, TreePath: form.TreePath, Message: message, - Content: strings.Replace(form.Content, "\r", "", -1), + Content: strings.ReplaceAll(form.Content, "\r", ""), IsNewFile: isNewFile, }); err != nil { // This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile @@ -292,10 +293,28 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplEditFile, &form) } else { - ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplEditFile, &form) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.push_rejected"), + "Summary": ctx.Tr("repo.editor.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(errPushRej.Message), + }) + if err != nil { + ctx.ServerError("editFilePost.HTMLString", err) + return + } + ctx.RenderWithErr(flashError, tplEditFile, &form) } } else { - ctx.RenderWithErr(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, utils.SanitizeFlashErrorString(err.Error())), tplEditFile, &form) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.fail_to_update_file", form.TreePath), + "Summary": ctx.Tr("repo.editor.fail_to_update_file_summary"), + "Details": utils.SanitizeFlashErrorString(err.Error()), + }) + if err != nil { + ctx.ServerError("editFilePost.HTMLString", err) + return + } + ctx.RenderWithErr(flashError, tplEditFile, &form) } } @@ -463,7 +482,16 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) { if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplDeleteFile, &form) } else { - ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplDeleteFile, &form) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.push_rejected"), + "Summary": ctx.Tr("repo.editor.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(errPushRej.Message), + }) + if err != nil { + ctx.ServerError("DeleteFilePost.HTMLString", err) + return + } + ctx.RenderWithErr(flashError, tplDeleteFile, &form) } } else { ctx.ServerError("DeleteRepoFile", err) @@ -493,18 +521,12 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) { } } -func renderUploadSettings(ctx *context.Context) { - ctx.Data["RequireTribute"] = true - ctx.Data["RequireSimpleMDE"] = true - ctx.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",") - ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize - ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles -} - // UploadFile render upload file page func UploadFile(ctx *context.Context) { ctx.Data["PageIsUpload"] = true - renderUploadSettings(ctx) + ctx.Data["RequireTribute"] = true + ctx.Data["RequireSimpleMDE"] = true + upload.AddUploadContext(ctx, "repo") canCommit := renderCommitRights(ctx) treePath := cleanUploadFileName(ctx.Repo.TreePath) if treePath != ctx.Repo.TreePath { @@ -537,7 +559,9 @@ func UploadFile(ctx *context.Context) { // UploadFilePost response for uploading file func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) { ctx.Data["PageIsUpload"] = true - renderUploadSettings(ctx) + ctx.Data["RequireTribute"] = true + ctx.Data["RequireSimpleMDE"] = true + upload.AddUploadContext(ctx, "repo") canCommit := renderCommitRights(ctx) oldBranchName := ctx.Repo.BranchName @@ -659,7 +683,16 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) { if len(errPushRej.Message) == 0 { ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected_no_message"), tplUploadFile, &form) } else { - ctx.RenderWithErr(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)), tplUploadFile, &form) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.push_rejected"), + "Summary": ctx.Tr("repo.editor.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(errPushRej.Message), + }) + if err != nil { + ctx.ServerError("UploadFilePost.HTMLString", err) + return + } + ctx.RenderWithErr(flashError, tplUploadFile, &form) } } else { // os.ErrNotExist - upload file missing in the intervening time?! @@ -703,12 +736,10 @@ func UploadFileToServer(ctx *context.Context) { buf = buf[:n] } - if len(setting.Repository.Upload.AllowedTypes) > 0 { - err = upload.VerifyAllowedContentType(buf, setting.Repository.Upload.AllowedTypes) - if err != nil { - ctx.Error(400, err.Error()) - return - } + err = upload.Verify(buf, header.Filename, setting.Repository.Upload.AllowedTypes) + if err != nil { + ctx.Error(400, err.Error()) + return } name := cleanUploadFileName(header.Filename) diff --git a/routers/repo/http.go b/routers/repo/http.go index bc3b81f5116c..c7523c7932a6 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -268,6 +268,7 @@ func HTTP(ctx *context.Context) { models.EnvPusherName + "=" + authUser.Name, models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), models.EnvIsDeployKey + "=false", + models.EnvAppURL + "=" + setting.AppURL, } if !authUser.KeepEmailPrivate { @@ -323,7 +324,7 @@ func HTTP(ctx *context.Context) { } } - environ = append(environ, models.ProtectedBranchRepoID+fmt.Sprintf("=%d", repo.ID)) + environ = append(environ, models.EnvRepoID+fmt.Sprintf("=%d", repo.ID)) w := ctx.Resp r := ctx.Req.Request @@ -496,7 +497,7 @@ func getGitConfig(option, dir string) string { } func getConfigSetting(service, dir string) bool { - service = strings.Replace(service, "-", "", -1) + service = strings.ReplaceAll(service, "-", "") setting := getGitConfig("http."+service, dir) if service == "uploadpack" { @@ -575,7 +576,7 @@ func serviceRPC(h serviceHandler, service string) { defer process.GetManager().Remove(pid) if err := cmd.Run(); err != nil { - log.Error("Fail to serve RPC(%s): %v - %s", service, err, stderr.String()) + log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String()) return } } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index b3a9f664f54b..b45bddb472b7 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -11,6 +11,7 @@ import ( "fmt" "io/ioutil" "net/http" + "path" "strconv" "strings" @@ -18,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/log" @@ -25,6 +27,7 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/util" comment_service "code.gitea.io/gitea/services/comments" issue_service "code.gitea.io/gitea/services/issue" @@ -36,13 +39,15 @@ import ( const ( tplAttachment base.TplName = "repo/issue/view_content/attachments" - tplIssues base.TplName = "repo/issue/list" - tplIssueNew base.TplName = "repo/issue/new" - tplIssueView base.TplName = "repo/issue/view" + tplIssues base.TplName = "repo/issue/list" + tplIssueNew base.TplName = "repo/issue/new" + tplIssueChoose base.TplName = "repo/issue/choose" + tplIssueView base.TplName = "repo/issue/view" tplReactions base.TplName = "repo/issue/view_content/reactions" - issueTemplateKey = "IssueTemplate" + issueTemplateKey = "IssueTemplate" + issueTemplateTitleKey = "IssueTemplateTitle" ) var ( @@ -356,6 +361,7 @@ func Issues(ctx *context.Context) { } ctx.Data["Title"] = ctx.Tr("repo.issues") ctx.Data["PageIsIssueList"] = true + ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 } issues(ctx, ctx.QueryInt64("milestone"), ctx.QueryInt64("project"), util.OptionalBoolOf(isPullList)) @@ -430,14 +436,195 @@ func retrieveProjects(ctx *context.Context, repo *models.Repository) { } } +// repoReviewerSelection items to bee shown +type repoReviewerSelection struct { + IsTeam bool + Team *models.Team + User *models.User + Review *models.Review + CanChange bool + Checked bool + ItemID int64 +} + // RetrieveRepoReviewers find all reviewers of a repository -func RetrieveRepoReviewers(ctx *context.Context, repo *models.Repository, issuePosterID int64) { - var err error - ctx.Data["Reviewers"], err = repo.GetReviewers(ctx.User.ID, issuePosterID) +func RetrieveRepoReviewers(ctx *context.Context, repo *models.Repository, issue *models.Issue, canChooseReviewer bool) { + ctx.Data["CanChooseReviewer"] = canChooseReviewer + + originalAuthorReviews, err := models.GetReviewersFromOriginalAuthorsByIssueID(issue.ID) if err != nil { - ctx.ServerError("GetReviewers", err) + ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) return } + ctx.Data["OriginalReviews"] = originalAuthorReviews + + reviews, err := models.GetReviewersByIssueID(issue.ID) + if err != nil { + ctx.ServerError("GetReviewersByIssueID", err) + return + } + + if len(reviews) == 0 && !canChooseReviewer { + return + } + + var ( + pullReviews []*repoReviewerSelection + reviewersResult []*repoReviewerSelection + teamReviewersResult []*repoReviewerSelection + teamReviewers []*models.Team + reviewers []*models.User + ) + + if canChooseReviewer { + posterID := issue.PosterID + if issue.OriginalAuthorID > 0 { + posterID = 0 + } + + reviewers, err = repo.GetReviewers(ctx.User.ID, posterID) + if err != nil { + ctx.ServerError("GetReviewers", err) + return + } + + teamReviewers, err = repo.GetReviewerTeams() + if err != nil { + ctx.ServerError("GetReviewerTeams", err) + return + } + + if len(reviewers) > 0 { + reviewersResult = make([]*repoReviewerSelection, 0, len(reviewers)) + } + + if len(teamReviewers) > 0 { + teamReviewersResult = make([]*repoReviewerSelection, 0, len(teamReviewers)) + } + } + + pullReviews = make([]*repoReviewerSelection, 0, len(reviews)) + + for _, review := range reviews { + tmp := &repoReviewerSelection{ + Checked: review.Type == models.ReviewTypeRequest, + Review: review, + ItemID: review.ReviewerID, + } + if review.ReviewerTeamID > 0 { + tmp.IsTeam = true + tmp.ItemID = -review.ReviewerTeamID + } + + if ctx.Repo.IsAdmin() { + // Admin can dismiss or re-request any review requests + tmp.CanChange = true + } else if ctx.User != nil && ctx.User.ID == review.ReviewerID && review.Type == models.ReviewTypeRequest { + // A user can refuse review requests + tmp.CanChange = true + } else if (canChooseReviewer || (ctx.User != nil && ctx.User.ID == issue.PosterID)) && review.Type != models.ReviewTypeRequest && + ctx.User.ID != review.ReviewerID { + // The poster of the PR, a manager, or official reviewers can re-request review from other reviewers + tmp.CanChange = true + } + + pullReviews = append(pullReviews, tmp) + + if canChooseReviewer { + if tmp.IsTeam { + teamReviewersResult = append(teamReviewersResult, tmp) + } else { + reviewersResult = append(reviewersResult, tmp) + } + } + } + + if len(pullReviews) > 0 { + // Drop all non-existing users and teams from the reviews + currentPullReviewers := make([]*repoReviewerSelection, 0, len(pullReviews)) + for _, item := range pullReviews { + if item.Review.ReviewerID > 0 { + if err = item.Review.LoadReviewer(); err != nil { + if models.IsErrUserNotExist(err) { + continue + } + ctx.ServerError("LoadReviewer", err) + return + } + item.User = item.Review.Reviewer + } else if item.Review.ReviewerTeamID > 0 { + if err = item.Review.LoadReviewerTeam(); err != nil { + if models.IsErrTeamNotExist(err) { + continue + } + ctx.ServerError("LoadReviewerTeam", err) + return + } + item.Team = item.Review.ReviewerTeam + } else { + continue + } + + currentPullReviewers = append(currentPullReviewers, item) + } + ctx.Data["PullReviewers"] = currentPullReviewers + } + + if canChooseReviewer && reviewersResult != nil { + preadded := len(reviewersResult) + for _, reviewer := range reviewers { + found := false + reviewAddLoop: + for _, tmp := range reviewersResult[:preadded] { + if tmp.ItemID == reviewer.ID { + tmp.User = reviewer + found = true + break reviewAddLoop + } + } + + if found { + continue + } + + reviewersResult = append(reviewersResult, &repoReviewerSelection{ + IsTeam: false, + CanChange: true, + User: reviewer, + ItemID: reviewer.ID, + }) + } + + ctx.Data["Reviewers"] = reviewersResult + } + + if canChooseReviewer && teamReviewersResult != nil { + preadded := len(teamReviewersResult) + for _, team := range teamReviewers { + found := false + teamReviewAddLoop: + for _, tmp := range teamReviewersResult[:preadded] { + if tmp.ItemID == -team.ID { + tmp.Team = team + found = true + break teamReviewAddLoop + } + } + + if found { + continue + } + + teamReviewersResult = append(teamReviewersResult, &repoReviewerSelection{ + IsTeam: true, + CanChange: true, + Team: team, + ItemID: -team.ID, + }) + } + + ctx.Data["TeamReviewers"] = teamReviewersResult + } } // RetrieveRepoMetas find all the meta information of a repository @@ -515,11 +702,41 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str return string(bytes), true } -func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) { - for _, filename := range possibleFiles { - content, found := getFileContentFromDefaultBranch(ctx, filename) +func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs []string, possibleFiles []string) { + templateCandidates := make([]string, 0, len(possibleFiles)) + if ctx.Query("template") != "" { + for _, dirName := range possibleDirs { + templateCandidates = append(templateCandidates, path.Join(dirName, ctx.Query("template"))) + } + } + templateCandidates = append(templateCandidates, possibleFiles...) // Append files to the end because they should be fallback + for _, filename := range templateCandidates { + templateContent, found := getFileContentFromDefaultBranch(ctx, filename) if found { - ctx.Data[ctxDataKey] = content + var meta api.IssueTemplate + templateBody, err := markdown.ExtractMetadata(templateContent, &meta) + if err != nil { + log.Debug("could not extract metadata from %s [%s]: %v", filename, ctx.Repo.Repository.FullName(), err) + ctx.Data[ctxDataKey] = templateContent + return + } + ctx.Data[issueTemplateTitleKey] = meta.Title + ctx.Data[ctxDataKey] = templateBody + labelIDs := make([]string, 0, len(meta.Labels)) + if repoLabels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, "", models.ListOptions{}); err == nil { + for _, metaLabel := range meta.Labels { + for _, repoLabel := range repoLabels { + if strings.EqualFold(repoLabel.Name, metaLabel) { + repoLabel.IsChecked = true + labelIDs = append(labelIDs, fmt.Sprintf("%d", repoLabel.ID)) + break + } + } + } + ctx.Data["Labels"] = repoLabels + } + ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0 + ctx.Data["label_ids"] = strings.Join(labelIDs, ",") return } } @@ -529,13 +746,18 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles func NewIssue(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.issues.new") ctx.Data["PageIsIssueList"] = true + ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireSimpleMDE"] = true ctx.Data["RequireTribute"] = true ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes + title := ctx.Query("title") + ctx.Data["TitleQuery"] = title body := ctx.Query("body") ctx.Data["BodyQuery"] = body ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") milestoneID := ctx.QueryInt64("milestone") if milestoneID > 0 { @@ -562,10 +784,8 @@ func NewIssue(ctx *context.Context) { } - setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) - renderAttachmentSettings(ctx) - RetrieveRepoMetas(ctx, ctx.Repo.Repository, false) + setTemplateIfExists(ctx, issueTemplateKey, context.IssueTemplateDirCandidates, IssueTemplateCandidates) if ctx.Written() { return } @@ -575,6 +795,19 @@ func NewIssue(ctx *context.Context) { ctx.HTML(200, tplIssueNew) } +// NewIssueChooseTemplate render creating issue from template page +func NewIssueChooseTemplate(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.issues.new") + ctx.Data["PageIsIssueList"] = true + ctx.Data["milestone"] = ctx.QueryInt64("milestone") + + issueTemplates := ctx.IssueTemplatesFromDefaultBranch() + ctx.Data["NewIssueChooseTemplate"] = len(issueTemplates) > 0 + ctx.Data["IssueTemplates"] = issueTemplates + + ctx.HTML(200, tplIssueChoose) +} + // ValidateRepoMetas check and returns repository's meta informations func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull bool) ([]int64, []int64, int64, int64) { var ( @@ -676,11 +909,13 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { ctx.Data["Title"] = ctx.Tr("repo.issues.new") ctx.Data["PageIsIssueList"] = true + ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireSimpleMDE"] = true ctx.Data["ReadOnly"] = false ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes - renderAttachmentSettings(ctx) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") var ( repo = ctx.Repo.Repository @@ -744,8 +979,6 @@ func commentTag(repo *models.Repository, poster *models.User, issue *models.Issu } if perm.IsOwner() { return models.CommentTagOwner, nil - } else if poster.ID == issue.PosterID { - return models.CommentTagPoster, nil } else if perm.CanWrite(models.UnitTypeCode) { return models.CommentTagWriter, nil } @@ -816,6 +1049,7 @@ func ViewIssue(ctx *context.Context) { return } ctx.Data["PageIsIssueList"] = true + ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 } if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) { @@ -830,8 +1064,8 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RequireTribute"] = true ctx.Data["RequireSimpleMDE"] = true ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects) - - renderAttachmentSettings(ctx) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") if err = issue.LoadAttributes(); err != nil { ctx.ServerError("LoadAttributes", err) @@ -929,13 +1163,7 @@ func ViewIssue(ctx *context.Context) { } } - if canChooseReviewer { - RetrieveRepoReviewers(ctx, repo, issue.PosterID) - ctx.Data["CanChooseReviewer"] = true - } else { - ctx.Data["CanChooseReviewer"] = false - } - + RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer) if ctx.Written() { return } @@ -999,6 +1227,12 @@ func ViewIssue(ctx *context.Context) { // check if dependencies can be created across repositories ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies + if issue.ShowTag, err = commentTag(repo, issue.Poster, issue); err != nil { + ctx.ServerError("commentTag", err) + return + } + marked[issue.PosterID] = issue.ShowTag + // Render comments and and fetch participants. participants[0] = issue.Poster for _, comment = range issue.Comments { @@ -1073,14 +1307,16 @@ func ViewIssue(ctx *context.Context) { } } else if comment.Type == models.CommentTypeAssignees || comment.Type == models.CommentTypeReviewRequest { - if err = comment.LoadAssigneeUser(); err != nil { - ctx.ServerError("LoadAssigneeUser", err) + if err = comment.LoadAssigneeUserAndTeam(); err != nil { + ctx.ServerError("LoadAssigneeUserAndTeam", err) return } } else if comment.Type == models.CommentTypeRemoveDependency || comment.Type == models.CommentTypeAddDependency { if err = comment.LoadDepIssueDetails(); err != nil { - ctx.ServerError("LoadDepIssueDetails", err) - return + if !models.IsErrIssueNotExist(err) { + ctx.ServerError("LoadDepIssueDetails", err) + return + } } } else if comment.Type == models.CommentTypeCode || comment.Type == models.CommentTypeReview { comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink, @@ -1200,10 +1436,13 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull) ctx.Data["GrantedApprovals"] = cnt ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits + ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles + ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0 + ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles) } ctx.Data["WillSign"] = false if ctx.User != nil { - sign, key, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName()) + sign, key, _, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName()) ctx.Data["WillSign"] = sign ctx.Data["SigningKey"] = key if err != nil { @@ -1222,12 +1461,6 @@ func ViewIssue(ctx *context.Context) { 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 { - ctx.ServerError("GetReviewersByIssueID", err) - return - } - ctx.Data["StillCanManualMerge"] = !pull.CanAutoMerge() && !pull.IsChecking() && !pull.IsWorkInProgress() && !pull.HasMerged && !issue.IsClosed && (ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin)) && prConfig.AllowManualMerge @@ -1248,7 +1481,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["Participants"] = participants ctx.Data["NumParticipants"] = len(participants) ctx.Data["Issue"] = issue - ctx.Data["ReadOnly"] = true + ctx.Data["ReadOnly"] = false ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) @@ -1348,6 +1581,30 @@ func UpdateIssueTitle(ctx *context.Context) { }) } +// UpdateIssueRef change issue's ref (branch) +func UpdateIssueRef(ctx *context.Context) { + issue := GetActionIssue(ctx) + if ctx.Written() { + return + } + + if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) || issue.IsPull { + ctx.Error(403) + return + } + + ref := ctx.QueryTrim("ref") + + if err := issue_service.ChangeIssueRef(issue, ctx.User, ref); err != nil { + ctx.ServerError("ChangeRef", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ref": ref, + }) +} + // UpdateIssueContent change issue's content func UpdateIssueContent(ctx *context.Context) { issue := GetActionIssue(ctx) @@ -1448,109 +1705,123 @@ func UpdateIssueAssignee(ctx *context.Context) { }) } -func isLegalReviewRequest(reviewer, doer *models.User, isAdd bool, issue *models.Issue) error { - if reviewer.IsOrganization() { - return fmt.Errorf("Organization can't be added as reviewer [user_id: %d, repo_id: %d]", reviewer.ID, issue.PullRequest.BaseRepo.ID) - } - if doer.IsOrganization() { - return fmt.Errorf("Organization can't be doer to add reviewer [user_id: %d, repo_id: %d]", doer.ID, issue.PullRequest.BaseRepo.ID) - } - - permReviewer, err := models.GetUserRepoPermission(issue.Repo, reviewer) - if err != nil { - return err - } - - permDoer, err := models.GetUserRepoPermission(issue.Repo, doer) - if err != nil { - return err - } - - lastreview, err := models.GetReviewerByIssueIDAndUserID(issue.ID, reviewer.ID) - if err != nil { - return err - } - - var pemResult bool - if isAdd { - pemResult = permReviewer.CanAccessAny(models.AccessModeRead, models.UnitTypePullRequests) - if !pemResult { - return fmt.Errorf("Reviewer can't read [user_id: %d, repo_name: %s]", reviewer.ID, issue.Repo.Name) - } - - if doer.ID == issue.PosterID && lastreview != nil && lastreview.Type != models.ReviewTypeRequest { - return nil - } - - pemResult = permDoer.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests) - if !pemResult { - pemResult, err = models.IsOfficialReviewer(issue, doer) - if err != nil { - return err - } - if !pemResult { - return fmt.Errorf("Doer can't choose reviewer [user_id: %d, repo_name: %s, issue_id: %d]", doer.ID, issue.Repo.Name, issue.ID) - } - } - - if doer.ID == reviewer.ID { - return fmt.Errorf("doer can't be reviewer [user_id: %d, repo_name: %s]", doer.ID, issue.Repo.Name) - } - - if reviewer.ID == issue.PosterID { - return fmt.Errorf("poster of pr can't be reviewer [user_id: %d, repo_name: %s]", reviewer.ID, issue.Repo.Name) - } - } else { - if lastreview.Type == models.ReviewTypeRequest && lastreview.ReviewerID == doer.ID { - return nil - } - - pemResult = permDoer.IsAdmin() - if !pemResult { - return fmt.Errorf("Doer is not admin [user_id: %d, repo_name: %s]", doer.ID, issue.Repo.Name) - } - } - - return nil -} - -// updatePullReviewRequest change pull's request reviewers -func updatePullReviewRequest(ctx *context.Context) { +// UpdatePullReviewRequest add or remove review request +func UpdatePullReviewRequest(ctx *context.Context) { issues := getActionIssues(ctx) if ctx.Written() { return } reviewID := ctx.QueryInt64("id") - event := ctx.Query("is_add") + action := ctx.Query("action") - if event != "add" && event != "remove" { - ctx.ServerError("updatePullReviewRequest", fmt.Errorf("is_add should not be \"%s\"", event)) + // TODO: Not support 'clear' now + if action != "attach" && action != "detach" { + ctx.Status(403) return } for _, issue := range issues { - if issue.IsPull { + if err := issue.LoadRepo(); err != nil { + ctx.ServerError("issue.LoadRepo", err) + return + } + + if !issue.IsPull { + log.Warn( + "UpdatePullReviewRequest: refusing to add review request for non-PR issue %-v#%d", + issue.Repo, issue.Index, + ) + ctx.Status(403) + return + } + if reviewID < 0 { + // negative reviewIDs represent team requests + if err := issue.Repo.GetOwner(); err != nil { + ctx.ServerError("issue.Repo.GetOwner", err) + return + } - reviewer, err := models.GetUserByID(reviewID) + if !issue.Repo.Owner.IsOrganization() { + log.Warn( + "UpdatePullReviewRequest: refusing to add team review request for %s#%d owned by non organization UID[%d]", + issue.Repo.FullName(), issue.Index, issue.Repo.ID, + ) + ctx.Status(403) + return + } + + team, err := models.GetTeamByID(-reviewID) if err != nil { - ctx.ServerError("GetUserByID", err) + ctx.ServerError("models.GetTeamByID", err) + return + } + + if team.OrgID != issue.Repo.OwnerID { + log.Warn( + "UpdatePullReviewRequest: refusing to add team review request for UID[%d] team %s to %s#%d owned by UID[%d]", + team.OrgID, team.Name, issue.Repo.FullName(), issue.Index, issue.Repo.ID) + ctx.Status(403) return } - err = isLegalReviewRequest(reviewer, ctx.User, event == "add", issue) + err = issue_service.IsValidTeamReviewRequest(team, ctx.User, action == "attach", issue) if err != nil { - ctx.ServerError("isLegalRequestReview", err) + if models.IsErrNotValidReviewRequest(err) { + log.Warn( + "UpdatePullReviewRequest: refusing to add invalid team review request for UID[%d] team %s to %s#%d owned by UID[%d]: Error: %v", + team.OrgID, team.Name, issue.Repo.FullName(), issue.Index, issue.Repo.ID, + err, + ) + ctx.Status(403) + return + } + ctx.ServerError("IsValidTeamReviewRequest", err) return } - err = issue_service.ReviewRequest(issue, ctx.User, reviewer, event == "add") + _, err = issue_service.TeamReviewRequest(issue, ctx.User, team, action == "attach") if err != nil { - ctx.ServerError("ReviewRequest", err) + ctx.ServerError("TeamReviewRequest", err) return } - } else { - ctx.ServerError("updatePullReviewRequest", fmt.Errorf("%d in %d is not Pull Request", issue.ID, issue.Repo.ID)) + continue + } + + reviewer, err := models.GetUserByID(reviewID) + if err != nil { + if models.IsErrUserNotExist(err) { + log.Warn( + "UpdatePullReviewRequest: requested reviewer [%d] for %-v to %-v#%d is not exist: Error: %v", + reviewID, issue.Repo, issue.Index, + err, + ) + ctx.Status(403) + return + } + ctx.ServerError("GetUserByID", err) + return + } + + err = issue_service.IsValidReviewRequest(reviewer, ctx.User, action == "attach", issue, nil) + if err != nil { + if models.IsErrNotValidReviewRequest(err) { + log.Warn( + "UpdatePullReviewRequest: refusing to add invalid review request for %-v to %-v#%d: Error: %v", + reviewer, issue.Repo, issue.Index, + err, + ) + ctx.Status(403) + return + } + ctx.ServerError("isValidReviewRequest", err) + return + } + + _, err = issue_service.ReviewRequest(issue, ctx.User, reviewer, action == "attach") + if err != nil { + ctx.ServerError("ReviewRequest", err) + return } } @@ -1559,11 +1830,6 @@ func updatePullReviewRequest(ctx *context.Context) { }) } -// UpdatePullReviewRequest add or remove review request -func UpdatePullReviewRequest(ctx *context.Context) { - updatePullReviewRequest(ctx) -} - // UpdateIssueStatus change issue's status func UpdateIssueStatus(ctx *context.Context) { issues := getActionIssues(ctx) @@ -2046,7 +2312,7 @@ func GetIssueAttachments(ctx *context.Context) { issue := GetActionIssue(ctx) var attachments = make([]*api.Attachment, len(issue.Attachments)) for i := 0; i < len(issue.Attachments); i++ { - attachments[i] = issue.Attachments[i].APIFormat() + attachments[i] = convert.ToReleaseAttachment(issue.Attachments[i]) } ctx.JSON(200, attachments) } @@ -2065,7 +2331,7 @@ func GetCommentAttachments(ctx *context.Context) { return } for i := 0; i < len(comment.Attachments); i++ { - attachments = append(attachments, comment.Attachments[i].APIFormat()) + attachments = append(attachments, convert.ToReleaseAttachment(comment.Attachments[i])) } } ctx.JSON(200, attachments) diff --git a/routers/repo/lfs.go b/routers/repo/lfs.go index 8aff89dd6a3c..be95e56d3b15 100644 --- a/routers/repo/lfs.go +++ b/routers/repo/lfs.go @@ -12,7 +12,6 @@ import ( "io" "io/ioutil" "path" - "path/filepath" "sort" "strconv" "strings" @@ -28,12 +27,11 @@ import ( "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/storage" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/mcuadros/go-version" "github.com/unknwon/com" ) @@ -355,8 +353,8 @@ func LFSDelete(ctx *context.Context) { // FIXME: Warning: the LFS store is not locked - and can't be locked - there could be a race condition here // Please note a similar condition happens in models/repo.go DeleteRepository if count == 0 { - oidPath := filepath.Join(oid[0:2], oid[2:4], oid[4:]) - err = util.Remove(filepath.Join(setting.LFS.ContentPath, oidPath)) + oidPath := path.Join(oid[0:2], oid[2:4], oid[4:]) + err = storage.LFS.Delete(oidPath) if err != nil { ctx.ServerError("LFSDelete", err) return @@ -541,7 +539,7 @@ func LFSPointerFiles(ctx *context.Context) { return } ctx.Data["PageIsSettingsLFS"] = true - binVersion, err := git.BinVersion() + err := git.LoadGitVersion() if err != nil { log.Fatal("Error retrieving git version: %v", err) } @@ -586,7 +584,7 @@ func LFSPointerFiles(ctx *context.Context) { go createPointerResultsFromCatFileBatch(catFileBatchReader, &wg, pointerChan, ctx.Repo.Repository, ctx.User) go pipeline.CatFileBatch(shasToBatchReader, catFileBatchWriter, &wg, basePath) go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) - if !version.Compare(binVersion, "2.6.0", ">=") { + if git.CheckGitVersionAtLeast("2.6.0") != nil { revListReader, revListWriter := io.Pipe() shasToCheckReader, shasToCheckWriter := io.Pipe() wg.Add(2) @@ -620,7 +618,7 @@ type pointerResult struct { func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg *sync.WaitGroup, pointerChan chan<- pointerResult, repo *models.Repository, user *models.User) { defer wg.Done() defer catFileBatchReader.Close() - contentStore := lfs.ContentStore{BasePath: setting.LFS.ContentPath} + contentStore := lfs.ContentStore{ObjectStorage: storage.LFS} bufferedReader := bufio.NewReader(catFileBatchReader) buf := make([]byte, 1025) @@ -674,7 +672,11 @@ func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg result.InRepo = true } - result.Exists = contentStore.Exists(pointer) + result.Exists, err = contentStore.Exists(pointer) + if err != nil { + _ = catFileBatchReader.CloseWithError(err) + break + } if result.Exists { if !result.InRepo { diff --git a/routers/repo/migrate.go b/routers/repo/migrate.go new file mode 100644 index 000000000000..9b10970bf000 --- /dev/null +++ b/routers/repo/migrate.go @@ -0,0 +1,187 @@ +// 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. + +package repo + +import ( + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/migrations" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/task" + "code.gitea.io/gitea/modules/util" +) + +const ( + tplMigrate base.TplName = "repo/migrate/migrate" +) + +// Migrate render migration of repository page +func Migrate(ctx *context.Context) { + ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...) + serviceType := ctx.QueryInt("service_type") + if serviceType == 0 { + ctx.HTML(200, tplMigrate) + return + } + + ctx.Data["Title"] = ctx.Tr("new_migrate") + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + ctx.Data["DisableMirrors"] = setting.Repository.DisableMirrors + ctx.Data["mirror"] = ctx.Query("mirror") == "1" + ctx.Data["wiki"] = ctx.Query("wiki") == "1" + ctx.Data["milestones"] = ctx.Query("milestones") == "1" + ctx.Data["labels"] = ctx.Query("labels") == "1" + ctx.Data["issues"] = ctx.Query("issues") == "1" + ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1" + ctx.Data["releases"] = ctx.Query("releases") == "1" + ctx.Data["LFSActive"] = setting.LFS.StartServer + // Plain git should be first + ctx.Data["service"] = structs.GitServiceType(serviceType) + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.HTML(200, base.TplName("repo/migrate/"+structs.GitServiceType(serviceType).Name())) +} + +func handleMigrateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form *auth.MigrateRepoForm) { + switch { + case migrations.IsRateLimitError(err): + ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form) + case migrations.IsTwoFactorAuthError(err): + ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form) + case models.IsErrReachLimitOfRepo(err): + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) + case models.IsErrRepoAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + case models.IsErrRepoFilesAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + switch { + case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) + case setting.Repository.AllowAdoptionOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) + case setting.Repository.AllowDeleteOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) + default: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form) + } + case models.IsErrNameReserved(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) + case models.IsErrNamePatternNotAllowed(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + default: + remoteAddr, _ := auth.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword, owner) + err = util.URLSanitizedError(err, remoteAddr) + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "Bad credentials") || + strings.Contains(err.Error(), "could not read Username") { + ctx.Data["Err_Auth"] = true + ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form) + } else if strings.Contains(err.Error(), "fatal:") { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form) + } else { + ctx.ServerError(name, err) + } + } +} + +// MigratePost response for migrating from external git repository +func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { + ctx.Data["Title"] = ctx.Tr("new_migrate") + // Plain git should be first + ctx.Data["service"] = structs.GitServiceType(form.Service) + ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...) + + tpl := base.TplName("repo/migrate/" + structs.GitServiceType(form.Service).Name()) + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(200, tpl) + return + } + + remoteAddr, err := auth.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword, ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + ctx.Data["Err_CloneAddr"] = true + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.RenderWithErr(ctx.Tr("form.url_error"), tpl, &form) + case addrErr.IsPermissionDenied: + ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tpl, &form) + case addrErr.IsInvalidPath: + ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tpl, &form) + default: + ctx.ServerError("Unknown error", err) + } + } else { + ctx.ServerError("ParseRemoteAddr", err) + } + return + } + + var opts = migrations.MigrateOptions{ + OriginalURL: form.CloneAddr, + GitServiceType: structs.GitServiceType(form.Service), + CloneAddr: remoteAddr, + RepoName: form.RepoName, + Description: form.Description, + Private: form.Private || setting.Repository.ForcePrivate, + Mirror: form.Mirror && !setting.Repository.DisableMirrors, + AuthUsername: form.AuthUsername, + AuthPassword: form.AuthPassword, + AuthToken: form.AuthToken, + Wiki: form.Wiki, + Issues: form.Issues, + Milestones: form.Milestones, + Labels: form.Labels, + Comments: form.Issues || form.PullRequests, + PullRequests: form.PullRequests, + Releases: form.Releases, + } + if opts.Mirror { + opts.Issues = false + opts.Milestones = false + opts.Labels = false + opts.Comments = false + opts.PullRequests = false + opts.Releases = false + } + + err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, false) + if err != nil { + handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, &form) + return + } + + err = task.MigrateRepository(ctx.User, ctxUser, opts) + if err == nil { + ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + opts.RepoName) + return + } + + handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, &form) +} diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index f48c5de12e3a..96f5b4e5f006 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -264,6 +264,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { ctx.Data["Milestone"] = milestone issues(ctx, milestoneID, 0, util.OptionalBoolNone) + ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false) ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true) diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 03032fca2041..da3f9ebbfb73 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -22,9 +22,9 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" - "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/services/gitdiff" @@ -311,7 +311,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), pull.MergeBase, pull.GetGitRefName()) if err != nil { - if strings.Contains(err.Error(), "fatal: Not a valid object name") { + if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") { ctx.Data["IsPullRequestBroken"] = true ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 @@ -624,6 +624,20 @@ func ViewPullFiles(ctx *context.Context) { return } + if err = pull.LoadProtectedBranch(); err != nil { + ctx.ServerError("LoadProtectedBranch", err) + return + } + + if pull.ProtectedBranch != nil { + glob := pull.ProtectedBranch.GetProtectedFilePatterns() + if len(glob) != 0 { + for _, file := range diff.Files { + file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name) + } + } + } + ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 @@ -709,7 +723,16 @@ func UpdatePullRequest(ctx *context.Context) { if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil { if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) - ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.pulls.merge_conflict"), + "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"), + "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), + }) + if err != nil { + ctx.ServerError("UpdatePullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) return } @@ -793,7 +816,7 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { return } - if err := pull_service.CheckPRReadyToMerge(pr); err != nil { + if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil { if !models.IsErrNotAllowedToMerge(err) { ctx.ServerError("Merge PR status", err) return @@ -853,12 +876,30 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { return } else if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) - ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.editor.merge_conflict"), + "Summary": ctx.Tr("repo.editor.merge_conflict_summary"), + "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), + }) + if err != nil { + ctx.ServerError("MergePullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) return } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) - ctx.Flash.Error(ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA), utils.SanitizeFlashErrorString(conflictError.StdErr), utils.SanitizeFlashErrorString(conflictError.StdOut))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), + "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), + "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "
" + utils.SanitizeFlashErrorString(conflictError.StdOut), + }) + if err != nil { + ctx.ServerError("MergePullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) return } else if models.IsErrMergeUnrelatedHistories(err) { @@ -878,7 +919,16 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { if len(message) == 0 { ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) } else { - ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected", utils.SanitizeFlashErrorString(pushrejErr.Message))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.pulls.push_rejected"), + "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), + }) + if err != nil { + ctx.ServerError("MergePullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) } ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) return @@ -914,7 +964,8 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) ctx.Data["IsDiffCompare"] = true ctx.Data["RequireHighlightJS"] = true ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes - renderAttachmentSettings(ctx) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") var ( repo = ctx.Repo.Repository @@ -992,7 +1043,16 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) if len(message) == 0 { ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message")) } else { - ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected", utils.SanitizeFlashErrorString(pushrejErr.Message))) + flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{ + "Message": ctx.Tr("repo.pulls.push_rejected"), + "Summary": ctx.Tr("repo.pulls.push_rejected_summary"), + "Details": utils.SanitizeFlashErrorString(pushrejErr.Message), + }) + if err != nil { + ctx.ServerError("CompareAndPullRequest.HTMLString", err) + return + } + ctx.Flash.Error(flashError) } ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index)) return @@ -1145,10 +1205,8 @@ func CleanUpPullRequest(ctx *context.Context) { return } - if err := repofiles.PushUpdate( - pr.HeadRepo, - pr.HeadBranch, - repofiles.PushUpdateOptions{ + if err := repo_service.PushUpdate( + &repo_service.PushUpdateOptions{ RefFullName: git.BranchPrefix + pr.HeadBranch, OldCommitID: branchCommitID, NewCommitID: git.EmptySHA, diff --git a/routers/repo/release.go b/routers/repo/release.go index c93c8f5d6126..73c42ec7c46c 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/upload" releaseservice "code.gitea.io/gitea/services/release" ) @@ -56,6 +57,7 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *models.Rel func Releases(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.releases") ctx.Data["PageIsReleaseList"] = true + ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases) ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived @@ -132,7 +134,7 @@ func SingleRelease(ctx *context.Context) { writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases) ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived - release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("tag")) + release, err := models.GetRelease(ctx.Repo.Repository.ID, ctx.Params("*")) if err != nil { if models.IsErrReleaseNotExist(err) { ctx.NotFound("GetRelease", err) @@ -192,7 +194,8 @@ func NewRelease(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.new_release") ctx.Data["PageIsReleaseList"] = true ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch - renderAttachmentSettings(ctx) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "release") ctx.HTML(200, tplReleaseNew) } @@ -278,7 +281,8 @@ func EditRelease(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.edit_release") ctx.Data["PageIsReleaseList"] = true ctx.Data["PageIsEditRelease"] = true - renderAttachmentSettings(ctx) + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "release") tagName := ctx.Params("*") rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName) diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 27c8ff1e03eb..742c952f6e7f 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -7,7 +7,6 @@ package repo import ( "fmt" - "net/url" "os" "path" "strings" @@ -18,19 +17,15 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/task" - "code.gitea.io/gitea/modules/util" repo_service "code.gitea.io/gitea/services/repository" "github.com/unknwon/com" ) const ( - tplCreate base.TplName = "repo/create" - tplMigrate base.TplName = "repo/migrate" + tplCreate base.TplName = "repo/create" + tplAlertDetails base.TplName = "base/alert_details" ) // MustBeNotEmpty render when a repo is a empty git dir @@ -146,7 +141,7 @@ func Create(ctx *context.Context) { templateID := ctx.QueryInt64("template_id") if templateID > 0 { templateRepo, err := models.GetRepositoryByID(templateID) - if err == nil && templateRepo.CheckUnitUser(ctxUser.ID, ctxUser.IsAdmin, models.UnitTypeCode) { + if err == nil && templateRepo.CheckUnitUser(ctxUser, models.UnitTypeCode) { ctx.Data["repo_template"] = templateID ctx.Data["repo_template_name"] = templateRepo.Name } @@ -166,6 +161,18 @@ func handleCreateError(ctx *context.Context, owner *models.User, err error, name case models.IsErrRepoAlreadyExist(err): ctx.Data["Err_RepoName"] = true ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + case models.IsErrRepoFilesAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + switch { + case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) + case setting.Repository.AllowAdoptionOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) + case setting.Repository.AllowDeleteOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) + default: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form) + } case models.IsErrNameReserved(err): ctx.Data["Err_RepoName"] = true ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) @@ -244,6 +251,8 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { IsPrivate: form.Private || setting.Repository.ForcePrivate, DefaultBranch: form.DefaultBranch, AutoInit: form.AutoInit, + IsTemplate: form.Template, + TrustModel: models.ToTrustModel(form.TrustModel), }) if err == nil { log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) @@ -255,148 +264,6 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) } -// Migrate render migration of repository page -func Migrate(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - ctx.Data["private"] = getRepoPrivate(ctx) - ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["DisableMirrors"] = setting.Repository.DisableMirrors - ctx.Data["mirror"] = ctx.Query("mirror") == "1" - ctx.Data["wiki"] = ctx.Query("wiki") == "1" - ctx.Data["milestones"] = ctx.Query("milestones") == "1" - ctx.Data["labels"] = ctx.Query("labels") == "1" - ctx.Data["issues"] = ctx.Query("issues") == "1" - ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1" - ctx.Data["releases"] = ctx.Query("releases") == "1" - ctx.Data["LFSActive"] = setting.LFS.StartServer - - ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - ctx.HTML(200, tplMigrate) -} - -func handleMigrateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form *auth.MigrateRepoForm) { - switch { - case migrations.IsRateLimitError(err): - ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form) - case migrations.IsTwoFactorAuthError(err): - ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form) - case models.IsErrReachLimitOfRepo(err): - ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) - case models.IsErrRepoAlreadyExist(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) - case models.IsErrNameReserved(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) - default: - remoteAddr, _ := form.ParseRemoteAddr(owner) - err = util.URLSanitizedError(err, remoteAddr) - if strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "Bad credentials") || - strings.Contains(err.Error(), "could not read Username") { - ctx.Data["Err_Auth"] = true - ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form) - } else if strings.Contains(err.Error(), "fatal:") { - ctx.Data["Err_CloneAddr"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form) - } else { - ctx.ServerError(name, err) - } - } -} - -// MigratePost response for migrating from external git repository -func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - - ctxUser := checkContextUser(ctx, form.UID) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - if ctx.HasError() { - ctx.HTML(200, tplMigrate) - return - } - - remoteAddr, err := form.ParseRemoteAddr(ctx.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - ctx.Data["Err_CloneAddr"] = true - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form) - case addrErr.IsPermissionDenied: - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form) - case addrErr.IsInvalidPath: - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form) - default: - ctx.ServerError("Unknown error", err) - } - } else { - ctx.ServerError("ParseRemoteAddr", err) - } - return - } - - var gitServiceType = structs.PlainGitService - u, err := url.Parse(form.CloneAddr) - if err == nil && strings.EqualFold(u.Host, "github.com") { - gitServiceType = structs.GithubService - } - - var opts = migrations.MigrateOptions{ - OriginalURL: form.CloneAddr, - GitServiceType: gitServiceType, - CloneAddr: remoteAddr, - RepoName: form.RepoName, - Description: form.Description, - Private: form.Private || setting.Repository.ForcePrivate, - Mirror: form.Mirror && !setting.Repository.DisableMirrors, - AuthUsername: form.AuthUsername, - AuthPassword: form.AuthPassword, - Wiki: form.Wiki, - Issues: form.Issues, - Milestones: form.Milestones, - Labels: form.Labels, - Comments: true, - PullRequests: form.PullRequests, - Releases: form.Releases, - } - if opts.Mirror { - opts.Issues = false - opts.Milestones = false - opts.Labels = false - opts.Comments = false - opts.PullRequests = false - opts.Releases = false - } - - err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName) - if err != nil { - handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) - return - } - - err = task.MigrateRepository(ctx.User, ctxUser, opts) - if err == nil { - ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + opts.RepoName) - return - } - - handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) -} - // Action response for actions to a repository func Action(ctx *context.Context) { var err error @@ -524,7 +391,7 @@ func Download(ctx *context.Context) { archivePath = path.Join(archivePath, base.ShortSha(commit.ID.String())+ext) if !com.IsFile(archivePath) { - if err := commit.CreateArchive(archivePath, git.CreateArchiveOpts{ + if err := commit.CreateArchive(ctx.Req.Context(), archivePath, git.CreateArchiveOpts{ Format: archiveType, Prefix: setting.Repository.PrefixArchiveFiles, }); err != nil { diff --git a/routers/repo/setting.go b/routers/repo/setting.go index b52614b095f3..9cb657a886e7 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -30,7 +30,6 @@ import ( mirror_service "code.gitea.io/gitea/services/mirror" repo_service "code.gitea.io/gitea/services/repository" - "github.com/unknwon/com" "mvdan.cc/xurls/v2" ) @@ -51,6 +50,11 @@ func Settings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsOptions"] = true ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate + + signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath()) + ctx.Data["SigningKeyAvailable"] = len(signing) > 0 + ctx.Data["SigningSettings"] = setting.Repository.Signing + ctx.HTML(200, tplSettingsOptions) } @@ -83,6 +87,18 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) case models.IsErrNameReserved(err): ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form) + case models.IsErrRepoFilesAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + switch { + case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) + case setting.Repository.AllowAdoptionOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) + case setting.Repository.AllowDeleteOfUnadoptedRepositories: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) + default: + ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) + } case models.IsErrNamePatternNotAllowed(err): ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) default: @@ -185,8 +201,8 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { address = u.String() - if err := mirror_service.SaveAddress(ctx.Repo.Mirror, address); err != nil { - ctx.ServerError("SaveAddress", err) + if err := mirror_service.UpdateAddress(ctx.Repo.Mirror, address); err != nil { + ctx.ServerError("UpdateAddress", err) return } @@ -320,6 +336,26 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.RepoLink + "/settings") + case "signing": + changed := false + + trustModel := models.ToTrustModel(form.TrustModel) + if trustModel != repo.TrustModel { + repo.TrustModel = trustModel + changed = true + } + + if changed { + if err := models.UpdateRepository(repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + } + log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings") + case "admin": if !ctx.User.IsAdmin { ctx.Error(403) @@ -850,6 +886,9 @@ func DeployKeysPost(ctx *context.Context, form auth.AddKeyForm) { case models.IsErrKeyNameAlreadyUsed(err): ctx.Data["Err_Title"] = true ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + case models.IsErrDeployKeyNameAlreadyUsed(err): + ctx.Data["Err_Title"] = true + ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) default: ctx.ServerError("AddDeployKey", err) } @@ -890,7 +929,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm) error { // No avatar is uploaded and we not removing it here. // No random avatar generated here. // Just exit, no action. - if !com.IsFile(ctxRepo.CustomAvatarPath()) { + if ctxRepo.CustomAvatarRelativePath() == "" { log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID) } return nil @@ -902,7 +941,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm) error { } defer r.Close() - if form.Avatar.Size > setting.AvatarMaxFileSize { + if form.Avatar.Size > setting.Avatar.MaxFileSize { return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) } diff --git a/routers/repo/setting_protected_branch.go b/routers/repo/setting_protected_branch.go index ab0fd77eee25..f864e8a75c1f 100644 --- a/routers/repo/setting_protected_branch.go +++ b/routers/repo/setting_protected_branch.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + pull_service "code.gitea.io/gitea/services/pull" ) // ProtectedBranch render the page to protect the repository @@ -262,6 +263,10 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) ctx.ServerError("UpdateProtectBranch", err) return } + if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil { + ctx.ServerError("CheckPrsForBaseBranch", err) + return + } ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch)) ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch)) } else { diff --git a/routers/repo/settings_test.go b/routers/repo/settings_test.go index 97c3fb047143..679bb0d33ce1 100644 --- a/routers/repo/settings_test.go +++ b/routers/repo/settings_test.go @@ -50,7 +50,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) { addKeyForm := auth.AddKeyForm{ Title: "read-only", - Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", + Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", } DeployKeysPost(ctx, addKeyForm) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) @@ -78,7 +78,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { addKeyForm := auth.AddKeyForm{ Title: "read-write", - Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n", + Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", IsWritable: true, } DeployKeysPost(ctx, addKeyForm) diff --git a/routers/repo/view.go b/routers/repo/view.go index 3074ab7aaea0..2df5b30ce8f9 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -34,7 +34,7 @@ const ( tplRepoHome base.TplName = "repo/home" tplWatchers base.TplName = "repo/watchers" tplForks base.TplName = "repo/forks" - tplMigrating base.TplName = "repo/migrating" + tplMigrating base.TplName = "repo/migrate/migrating" ) type namedBlob struct { @@ -332,8 +332,8 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeDocumentMetas())) } else { ctx.Data["IsRenderedHTML"] = true - ctx.Data["FileContent"] = strings.Replace( - gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, -1, + ctx.Data["FileContent"] = strings.ReplaceAll( + gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, ) } } @@ -365,6 +365,8 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } + + ctx.Data["SSHDomain"] = setting.SSH.Domain } func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) { @@ -469,8 +471,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeDocumentMetas())) } else if readmeExist { ctx.Data["IsRenderedHTML"] = true - ctx.Data["FileContent"] = strings.Replace( - gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, -1, + ctx.Data["FileContent"] = strings.ReplaceAll( + gotemplate.HTMLEscapeString(string(buf)), "\n", `
`, ) } else { buf = charset.ToUTF8WithFallback(buf) diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go index bec401021cc5..13b95c8076c2 100644 --- a/routers/repo/webhook.go +++ b/routers/repo/webhook.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -1052,7 +1053,7 @@ func TestWebhook(ctx *context.Context) { } } - apiUser := ctx.User.APIFormat() + apiUser := convert.ToUser(ctx.User, true, true) p := &api.PushPayload{ Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch, Before: commit.ID.String(), diff --git a/routers/routes/routes.go b/routers/routes/routes.go index bdb82db6f504..7f43b3b2b131 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -7,8 +7,13 @@ package routes import ( "bytes" "encoding/gob" + "errors" + "fmt" + "io" "net/http" + "os" "path" + "strings" "text/template" "time" @@ -21,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/routers" @@ -107,6 +113,74 @@ func RouterHandler(level log.Level) func(ctx *macaron.Context) { } } +func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) macaron.Handler { + if storageSetting.ServeDirect { + return func(ctx *macaron.Context) { + req := ctx.Req.Request + if req.Method != "GET" && req.Method != "HEAD" { + return + } + + if !strings.HasPrefix(req.RequestURI, "/"+prefix) { + return + } + + rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) + u, err := objStore.URL(rPath, path.Base(rPath)) + if err != nil { + if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { + log.Warn("Unable to find %s %s", prefix, rPath) + ctx.Error(404, "file not found") + return + } + log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err) + ctx.Error(500, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath)) + return + } + http.Redirect( + ctx.Resp, + req, + u.String(), + 301, + ) + } + } + + return func(ctx *macaron.Context) { + req := ctx.Req.Request + if req.Method != "GET" && req.Method != "HEAD" { + return + } + + if !strings.HasPrefix(req.RequestURI, "/"+prefix) { + return + } + + rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) + rPath = strings.TrimPrefix(rPath, "/") + //If we have matched and access to release or issue + fr, err := objStore.Open(rPath) + if err != nil { + if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { + log.Warn("Unable to find %s %s", prefix, rPath) + ctx.Error(404, "file not found") + return + } + log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) + ctx.Error(500, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath)) + return + } + defer fr.Close() + + _, err = io.Copy(ctx.Resp, fr) + if err != nil { + log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err) + ctx.Error(500, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath)) + return + } + } +} + // NewMacaron initializes Macaron instance. func NewMacaron() *macaron.Macaron { gob.Register(&u2f.Challenge{}) @@ -149,24 +223,12 @@ func NewMacaron() *macaron.Macaron { ExpiresAfter: setting.StaticCacheTime, }, )) - m.Use(public.StaticHandler( - setting.AvatarUploadPath, - &public.Options{ - Prefix: "avatars", - SkipLogging: setting.DisableRouterLog, - ExpiresAfter: setting.StaticCacheTime, - }, - )) - m.Use(public.StaticHandler( - setting.RepositoryAvatarUploadPath, - &public.Options{ - Prefix: "repo-avatars", - SkipLogging: setting.DisableRouterLog, - ExpiresAfter: setting.StaticCacheTime, - }, - )) m.Use(templates.HTMLRenderer()) + + m.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) + m.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) + mailer.InitMailRender(templates.Mailer()) localeNames, err := options.Dir("locale") @@ -239,6 +301,15 @@ func NewMacaron() *macaron.Macaron { return m } +// RegisterInstallRoute registers the install routes +func RegisterInstallRoute(m *macaron.Macaron) { + m.Combo("/", routers.InstallInit).Get(routers.Install). + Post(binding.BindIgnErr(auth.InstallForm{}), routers.InstallPost) + m.NotFound(func(ctx *context.Context) { + ctx.Redirect(setting.AppURL, 302) + }) +} + // RegisterRoutes routes routes to Macaron func RegisterRoutes(m *macaron.Macaron) { reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) @@ -402,6 +473,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/keys/delete", userSetting.DeleteKey) m.Get("/organization", userSetting.Organization) m.Get("/repos", userSetting.Repos) + m.Post("/repos/unadopted", userSetting.AdoptOrDeleteRepository) }, reqSignIn, func(ctx *context.Context) { ctx.Data["PageIsUserSettings"] = true ctx.Data["AllThemes"] = setting.UI.Themes @@ -461,6 +533,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/repos", func() { m.Get("", admin.Repos) + m.Combo("/unadopted").Get(admin.UnadoptedRepos).Post(admin.AdoptOrDeleteRepository) m.Post("/delete", admin.DeleteRepo) }) @@ -510,11 +583,6 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/attachments/:uuid", repo.GetAttachment) }, ignSignIn) - m.Group("/attachments", func() { - m.Post("", repo.UploadAttachment) - m.Post("/delete", repo.DeleteAttachment) - }, reqSignIn) - m.Group("/:username", func() { m.Post("/action/:action", user.Action) }, reqSignIn) @@ -723,8 +791,11 @@ func RegisterRoutes(m *macaron.Macaron) { // Grouping for those endpoints that do require authentication m.Group("/:username/:reponame", func() { m.Group("/issues", func() { - m.Combo("/new").Get(context.RepoRef(), repo.NewIssue). - Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) + m.Group("/new", func() { + m.Combo("").Get(context.RepoRef(), repo.NewIssue). + Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) + m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) + }) }, context.RepoMustNotBeArchived(), reqRepoIssueReader) // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. // So they can apply their own enable/disable logic on routers. @@ -733,6 +804,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) + m.Post("/ref", repo.UpdateIssueRef) m.Group("/dependency", func() { m.Post("/add", repo.AddDependency) m.Post("/delete", repo.RemoveDependency) @@ -748,8 +820,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction) m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue) m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue) - m.Get("/attachments", repo.GetIssueAttachments) }, context.RepoMustNotBeArchived()) + m.Group("/:index", func() { + m.Get("/attachments", repo.GetIssueAttachments) + m.Get("/attachments/:uuid", repo.GetAttachment) + }) m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) @@ -758,13 +833,17 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest) m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation) + m.Post("/attachments", repo.UploadIssueAttachment) + m.Post("/attachments/remove", repo.DeleteAttachment) }, context.RepoMustNotBeArchived()) m.Group("/comments/:id", func() { m.Post("", repo.UpdateCommentContent) m.Post("/delete", repo.DeleteComment) m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction) - m.Get("/attachments", repo.GetCommentAttachments) }, context.RepoMustNotBeArchived()) + m.Group("/comments/:id", func() { + m.Get("/attachments", repo.GetCommentAttachments) + }) m.Group("/labels", func() { m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) @@ -818,13 +897,16 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/:username/:reponame", func() { m.Group("/releases", func() { m.Get("/", repo.Releases) - m.Get("/tag/:tag", repo.SingleRelease) + m.Get("/tag/*", repo.SingleRelease) m.Get("/latest", repo.LatestRelease) - }, repo.MustBeNotEmpty, context.RepoRef()) + m.Get("/attachments/:uuid", repo.GetAttachment) + }, repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag)) m.Group("/releases", func() { m.Get("/new", repo.NewRelease) m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) m.Post("/delete", repo.DeleteRelease) + m.Post("/attachments", repo.UploadReleaseAttachment) + m.Post("/attachments/remove", repo.DeleteAttachment) }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) diff --git a/routers/user/auth.go b/routers/user/auth.go index 4e6ac9c87f88..32b031fc7417 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/eventsource" + "code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/recaptcha" @@ -896,15 +897,21 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha { var valid bool + var err error switch setting.Service.CaptchaType { case setting.ImageCaptcha: valid = cpt.VerifyReq(ctx.Req) case setting.ReCaptcha: - valid, _ = recaptcha.Verify(form.GRecaptchaResponse) + valid, err = recaptcha.Verify(ctx.Req.Context(), form.GRecaptchaResponse) + case setting.HCaptcha: + valid, err = hcaptcha.Verify(ctx.Req.Context(), form.HcaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return } + if err != nil { + log.Debug("%s", err.Error()) + } if !valid { ctx.Data["Err_Captcha"] = true @@ -1040,6 +1047,7 @@ func SignUp(ctx *context.Context) { ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["PageIsSignUp"] = true //Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true @@ -1058,6 +1066,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["PageIsSignUp"] = true //Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true @@ -1073,15 +1082,21 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo if setting.Service.EnableCaptcha { var valid bool + var err error switch setting.Service.CaptchaType { case setting.ImageCaptcha: valid = cpt.VerifyReq(ctx.Req) case setting.ReCaptcha: - valid, _ = recaptcha.Verify(form.GRecaptchaResponse) + valid, err = recaptcha.Verify(ctx.Req.Context(), form.GRecaptchaResponse) + case setting.HCaptcha: + valid, err = hcaptcha.Verify(ctx.Req.Context(), form.HcaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return } + if err != nil { + log.Debug("%s", err.Error()) + } if !valid { ctx.Data["Err_Captcha"] = true @@ -1110,6 +1125,17 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(password.BuildComplexityError(ctx), tplSignUp, &form) return } + pwned, err := password.IsPwned(ctx.Req.Context(), form.Password) + if pwned { + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(errMsg, tplSignUp, &form) + return + } u := &models.User{ Name: form.UserName, @@ -1409,6 +1435,16 @@ func ResetPasswdPost(ctx *context.Context) { ctx.Data["Err_Password"] = true ctx.RenderWithErr(password.BuildComplexityError(ctx), tplResetPassword, nil) return + } else if pwned, err := password.IsPwned(ctx.Req.Context(), passwd); pwned || err != nil { + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.Data["IsResetForm"] = true + ctx.Data["Err_Password"] = true + ctx.RenderWithErr(errMsg, tplResetPassword, nil) + return } // Handle two-factor @@ -1443,7 +1479,6 @@ func ResetPasswdPost(ctx *context.Context) { } } } - var err error if u.Rands, err = models.GetUserSalt(); err != nil { ctx.ServerError("UpdateUser", err) diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index ba2c8be8c24c..39e75f202dd7 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/setting" @@ -330,6 +331,7 @@ func RegisterOpenID(ctx *context.Context) { ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["OpenID"] = oid userName, _ := ctx.Session.Get("openid_determined_username").(string) @@ -359,24 +361,34 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["OpenID"] = oid if setting.Service.EnableCaptcha { var valid bool + var err error switch setting.Service.CaptchaType { case setting.ImageCaptcha: valid = cpt.VerifyReq(ctx.Req) case setting.ReCaptcha: - err := ctx.Req.ParseForm() - if err != nil { + if err := ctx.Req.ParseForm(); err != nil { ctx.ServerError("", err) return } - valid, _ = recaptcha.Verify(form.GRecaptchaResponse) + valid, err = recaptcha.Verify(ctx.Req.Context(), form.GRecaptchaResponse) + case setting.HCaptcha: + if err := ctx.Req.ParseForm(); err != nil { + ctx.ServerError("", err) + return + } + valid, err = hcaptcha.Verify(ctx.Req.Context(), form.HcaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return } + if err != nil { + log.Debug("%s", err.Error()) + } if !valid { ctx.Data["Err_Captcha"] = true diff --git a/routers/user/notification.go b/routers/user/notification.go index 9724c8108830..34939d14554f 100644 --- a/routers/user/notification.go +++ b/routers/user/notification.go @@ -174,6 +174,7 @@ func NotificationStatusPost(c *context.Context) { if c.Written() { return } + c.Data["Link"] = fmt.Sprintf("%snotifications", setting.AppURL) c.HTML(http.StatusOK, tplNotificationDiv) } diff --git a/routers/user/oauth.go b/routers/user/oauth.go index a9e089b39fd5..12665e94dbe7 100644 --- a/routers/user/oauth.go +++ b/routers/user/oauth.go @@ -7,6 +7,7 @@ package user import ( "encoding/base64" "fmt" + "html" "net/url" "strings" @@ -271,8 +272,8 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) { ctx.Data["Application"] = app ctx.Data["RedirectURI"] = form.RedirectURI ctx.Data["State"] = form.State - ctx.Data["ApplicationUserLink"] = "@" + app.User.Name + "" - ctx.Data["ApplicationRedirectDomainHTML"] = "" + form.RedirectURI + "" + ctx.Data["ApplicationUserLink"] = "@" + html.EscapeString(app.User.Name) + "" + ctx.Data["ApplicationRedirectDomainHTML"] = "" + html.EscapeString(form.RedirectURI) + "" // TODO document SESSION <=> FORM err = ctx.Session.Set("client_id", app.ClientID) if err != nil { diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index 27f0bf1c86b4..99e20177bc98 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -54,6 +54,13 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) { ctx.Flash.Error(ctx.Tr("form.password_not_match")) } else if !password.IsComplexEnough(form.Password) { ctx.Flash.Error(password.BuildComplexityError(ctx)) + } else if pwned, err := password.IsPwned(ctx.Req.Context(), form.Password); pwned || err != nil { + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.Flash.Error(errMsg) } else { var err error if ctx.User.Salt, err = models.GetUserSalt(); err != nil { diff --git a/routers/user/setting/adopt.go b/routers/user/setting/adopt.go new file mode 100644 index 000000000000..6ff07d6daa91 --- /dev/null +++ b/routers/user/setting/adopt.go @@ -0,0 +1,56 @@ +// 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 setting + +import ( + "path/filepath" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + "github.com/unknwon/com" +) + +// AdoptOrDeleteRepository adopts or deletes a repository +func AdoptOrDeleteRepository(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsRepos"] = true + allowAdopt := ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories + ctx.Data["allowAdopt"] = allowAdopt + allowDelete := ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories + ctx.Data["allowDelete"] = allowDelete + + dir := ctx.Query("id") + action := ctx.Query("action") + + ctxUser := ctx.User + root := filepath.Join(models.UserPath(ctxUser.LowerName)) + + // check not a repo + if has, err := models.IsRepositoryExist(ctxUser, dir); err != nil { + ctx.ServerError("IsRepositoryExist", err) + return + } else if has || !com.IsDir(filepath.Join(root, dir+".git")) { + // Fallthrough to failure mode + } else if action == "adopt" && allowAdopt { + if _, err := repository.AdoptRepository(ctxUser, ctxUser, models.CreateRepoOptions{ + Name: dir, + IsPrivate: true, + }); err != nil { + ctx.ServerError("repository.AdoptRepository", err) + return + } + ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) + } else if action == "delete" && allowDelete { + if err := repository.DeleteUnadoptedRepository(ctxUser, ctxUser, dir); err != nil { + ctx.ServerError("repository.AdoptRepository", err) + return + } + ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir)) + } + + ctx.Redirect(setting.AppSubURL + "/user/settings/repos") +} diff --git a/routers/user/setting/applications.go b/routers/user/setting/applications.go index febb5b0c19b3..04f9d9f7f9b9 100644 --- a/routers/user/setting/applications.go +++ b/routers/user/setting/applications.go @@ -80,7 +80,7 @@ func DeleteApplication(ctx *context.Context) { } func loadApplicationsData(ctx *context.Context) { - tokens, err := models.ListAccessTokens(ctx.User.ID, models.ListOptions{}) + tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID}) if err != nil { ctx.ServerError("ListAccessTokens", err) return diff --git a/routers/user/setting/keys.go b/routers/user/setting/keys.go index a7978fe14edf..6a39666e944b 100644 --- a/routers/user/setting/keys.go +++ b/routers/user/setting/keys.go @@ -22,6 +22,8 @@ func Keys(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled + ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer + ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled loadKeysData(ctx) @@ -32,6 +34,9 @@ func Keys(ctx *context.Context) { func KeysPost(ctx *context.Context, form auth.AddKeyForm) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsKeys"] = true + ctx.Data["DisableSSH"] = setting.SSH.Disabled + ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer + ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled if ctx.HasError() { loadKeysData(ctx) @@ -40,6 +45,32 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) { return } switch form.Type { + case "principal": + content, err := models.CheckPrincipalKeyString(ctx.User, form.Content) + if err != nil { + if models.IsErrSSHDisabled(err) { + ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) + } else { + ctx.Flash.Error(ctx.Tr("form.invalid_ssh_principal", err.Error())) + } + ctx.Redirect(setting.AppSubURL + "/user/settings/keys") + return + } + if _, err = models.AddPrincipalKey(ctx.User.ID, content, 0); err != nil { + ctx.Data["HasPrincipalError"] = true + switch { + case models.IsErrKeyAlreadyExist(err), models.IsErrKeyNameAlreadyUsed(err): + loadKeysData(ctx) + + ctx.Data["Err_Content"] = true + ctx.RenderWithErr(ctx.Tr("settings.ssh_principal_been_used"), tplSettingsKeys, &form) + default: + ctx.ServerError("AddPrincipalKey", err) + } + return + } + ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) + ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": keys, err := models.AddGPGKey(ctx.User.ID, form.Content) if err != nil { @@ -134,6 +165,12 @@ func DeleteKey(ctx *context.Context) { } else { ctx.Flash.Success(ctx.Tr("settings.ssh_key_deletion_success")) } + case "principal": + if err := models.DeletePublicKey(ctx.User, ctx.QueryInt64("id")); err != nil { + ctx.Flash.Error("DeletePublicKey: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("settings.ssh_principal_deletion_success")) + } default: ctx.Flash.Warning("Function not implemented") ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -157,4 +194,11 @@ func loadKeysData(ctx *context.Context) { return } ctx.Data["GPGKeys"] = gpgkeys + + principals, err := models.ListPrincipalKeys(ctx.User.ID, models.ListOptions{}) + if err != nil { + ctx.ServerError("ListPrincipalKeys", err) + return + } + ctx.Data["Principals"] = principals } diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go index ba9ba2b257fa..1cb00aa77fab 100644 --- a/routers/user/setting/profile.go +++ b/routers/user/setting/profile.go @@ -9,6 +9,8 @@ import ( "errors" "fmt" "io/ioutil" + "os" + "path/filepath" "strings" "code.gitea.io/gitea/models" @@ -18,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/unknwon/com" "github.com/unknwon/i18n" ) @@ -131,7 +132,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo } defer fr.Close() - if form.Avatar.Size > setting.AvatarMaxFileSize { + if form.Avatar.Size > setting.Avatar.MaxFileSize { return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) } @@ -145,7 +146,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo if err = ctxUser.UploadAvatar(data); err != nil { return fmt.Errorf("UploadAvatar: %v", err) } - } else if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) { + } else if ctxUser.UseCustomAvatar && ctxUser.Avatar == "" { // No avatar is uploaded but setting has been changed to enable, // generate a random one when needed. if err := ctxUser.GenerateRandomAvatar(); err != nil { @@ -197,32 +198,96 @@ func Organization(ctx *context.Context) { func Repos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsRepos"] = true - ctxUser := ctx.User + ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories + ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories - var err error - if err = ctxUser.GetRepositories(models.ListOptions{Page: 1, PageSize: setting.UI.User.RepoPagingNum}); err != nil { - ctx.ServerError("GetRepositories", err) - return + opts := models.ListOptions{ + PageSize: setting.UI.Admin.UserPagingNum, + Page: ctx.QueryInt("page"), + } + + if opts.Page <= 0 { + opts.Page = 1 } - repos := ctxUser.Repos + start := (opts.Page - 1) * opts.PageSize + end := start + opts.PageSize - for i := range repos { - if repos[i].IsFork { - err := repos[i].GetBaseRepo() + adoptOrDelete := ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories) + + ctxUser := ctx.User + count := 0 + + if adoptOrDelete { + repoNames := make([]string, 0, setting.UI.Admin.UserPagingNum) + repos := map[string]*models.Repository{} + // We're going to iterate by pagesize. + root := filepath.Join(models.UserPath(ctxUser.Name)) + if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { - ctx.ServerError("GetBaseRepo", err) - return + return err } - err = repos[i].BaseRepo.GetOwner() - if err != nil { - ctx.ServerError("GetOwner", err) - return + if !info.IsDir() || path == root { + return nil } + name := info.Name() + if !strings.HasSuffix(name, ".git") { + return filepath.SkipDir + } + name = name[:len(name)-4] + if models.IsUsableRepoName(name) != nil || strings.ToLower(name) != name { + return filepath.SkipDir + } + if count >= start && count < end { + repoNames = append(repoNames, name) + } + count++ + return filepath.SkipDir + }); err != nil { + ctx.ServerError("filepath.Walk", err) + return } - } - ctx.Data["Owner"] = ctxUser - ctx.Data["Repos"] = repos + if err := ctxUser.GetRepositories(models.ListOptions{Page: 1, PageSize: setting.UI.Admin.UserPagingNum}, repoNames...); err != nil { + ctx.ServerError("GetRepositories", err) + return + } + for _, repo := range ctxUser.Repos { + if repo.IsFork { + if err := repo.GetBaseRepo(); err != nil { + ctx.ServerError("GetBaseRepo", err) + return + } + } + repos[repo.LowerName] = repo + } + ctx.Data["Dirs"] = repoNames + ctx.Data["ReposMap"] = repos + } else { + var err error + var count64 int64 + ctxUser.Repos, count64, err = models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts}) + if err != nil { + ctx.ServerError("GetRepositories", err) + return + } + count = int(count64) + repos := ctxUser.Repos + + for i := range repos { + if repos[i].IsFork { + if err := repos[i].GetBaseRepo(); err != nil { + ctx.ServerError("GetBaseRepo", err) + return + } + } + } + + ctx.Data["Repos"] = repos + } + ctx.Data["Owner"] = ctxUser + pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager ctx.HTML(200, tplSettingsRepositories) } diff --git a/routers/user/setting/security.go b/routers/user/setting/security.go index c7c3226c9b23..787ac922ec4c 100644 --- a/routers/user/setting/security.go +++ b/routers/user/setting/security.go @@ -71,7 +71,7 @@ func loadSecurityData(ctx *context.Context) { ctx.Data["RequireU2F"] = true } - tokens, err := models.ListAccessTokens(ctx.User.ID, models.ListOptions{}) + tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID}) if err != nil { ctx.ServerError("ListAccessTokens", err) return diff --git a/routers/user/setting/security_twofa.go b/routers/user/setting/security_twofa.go index ed87f004361e..4ee698e15e66 100644 --- a/routers/user/setting/security_twofa.go +++ b/routers/user/setting/security_twofa.go @@ -93,7 +93,7 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool { } } // Filter unsafe character ':' in issuer - issuer := strings.Replace(setting.AppName+" ("+setting.Domain+")", ":", "", -1) + issuer := strings.ReplaceAll(setting.AppName+" ("+setting.Domain+")", ":", "") if otpKey == nil { otpKey, err = totp.Generate(totp.GenerateOpts{ SecretSize: 40, diff --git a/routers/utils/utils.go b/routers/utils/utils.go index 7c845f8763ce..f15bc1e62e4b 100644 --- a/routers/utils/utils.go +++ b/routers/utils/utils.go @@ -41,13 +41,7 @@ func IsValidSlackChannel(channelName string) bool { // SanitizeFlashErrorString will sanitize a flash error string func SanitizeFlashErrorString(x string) string { - runes := []rune(x) - - if len(runes) > 512 { - x = "..." + string(runes[len(runes)-512:]) - } - - return strings.Replace(html.EscapeString(x), "\n", "
", -1) + return strings.ReplaceAll(html.EscapeString(x), "\n", "
") } // IsExternalURL checks if rawURL points to an external URL like http://example.com diff --git a/semantic.json b/semantic.json index a103afccb3be..3b927d58f404 100644 --- a/semantic.json +++ b/semantic.json @@ -1,5 +1,5 @@ { - "version": "2.8.6", + "version": "2.8.7", "base": "node_modules/fomantic-ui", "paths": { "source": { diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 538f613b0458..7acf8324c3a5 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -53,6 +53,7 @@ const ( DiffFileChange DiffFileDel DiffFileRename + DiffFileCopy ) // DiffLineExpandDirection represents the DiffLineSection expand direction @@ -180,64 +181,60 @@ var ( removedCodePrefix = []byte(``) codeTagSuffix = []byte(``) ) -var addSpanRegex = regexp.MustCompile(`]?$`) + +// shouldWriteInline represents combinations where we manually write inline changes +func shouldWriteInline(diff diffmatchpatch.Diff, lineType DiffLineType) bool { + if true && + diff.Type == diffmatchpatch.DiffEqual || + diff.Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd || + diff.Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel { + return true + } + return false +} func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML { buf := bytes.NewBuffer(nil) - var addSpan string - for i := range diffs { - switch { - case diffs[i].Type == diffmatchpatch.DiffEqual: - // Looking for the case where our 3rd party diff library previously detected a string difference - // in the middle of a span class because we highlight them first. This happens when added/deleted code - // also changes the chroma class name, either partially or fully. If found, just move the openining span code forward into the next section - // see TestDiffToHTML for examples - if len(addSpan) > 0 { - diffs[i].Text = addSpan + diffs[i].Text - addSpan = "" + match := "" + + for _, diff := range diffs { + if shouldWriteInline(diff, lineType) { + if len(match) > 0 { + diff.Text = match + diff.Text + match = "" } - m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text) + // Chroma HTML syntax highlighting is done before diffing individual lines in order to maintain consistency. + // Since inline changes might split in the middle of a chroma span tag, make we manually put it back together + // before writing so we don't try insert added/removed code spans in the middle of an existing chroma span + // and create broken HTML. + m := trailingSpanRegex.FindStringSubmatchIndex(diff.Text) if m != nil { - addSpan = diffs[i].Text[m[0]:m[1]] - buf.WriteString(strings.TrimSuffix(diffs[i].Text, addSpan)) - } else { - addSpan = "" - buf.WriteString(getLineContent(diffs[i].Text)) + match = diff.Text[m[0]:m[1]] + diff.Text = strings.TrimSuffix(diff.Text, match) } - case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd: - if len(addSpan) > 0 { - diffs[i].Text = addSpan + diffs[i].Text - addSpan = "" - } - // Print existing closing span first before opening added-code span so it doesn't unintentionally close it - if strings.HasPrefix(diffs[i].Text, "") { + // Print an existing closing span first before opening added/remove-code span so it doesn't unintentionally close it + if strings.HasPrefix(diff.Text, "") { buf.WriteString("") - diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "") + diff.Text = strings.TrimPrefix(diff.Text, "") } - m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text) - if m != nil { - addSpan = diffs[i].Text[m[0]:m[1]] - diffs[i].Text = strings.TrimSuffix(diffs[i].Text, addSpan) + // If we weren't able to fix it then this should avoid broken HTML by not inserting more spans below + // The previous/next diff section will contain the rest of the tag that is missing here + if strings.Count(diff.Text, "<") != strings.Count(diff.Text, ">") { + buf.WriteString(diff.Text) + continue } + } + switch { + case diff.Type == diffmatchpatch.DiffEqual: + buf.WriteString(diff.Text) + case diff.Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd: buf.Write(addedCodePrefix) - buf.WriteString(getLineContent(diffs[i].Text)) + buf.WriteString(diff.Text) buf.Write(codeTagSuffix) - case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel: - if len(addSpan) > 0 { - diffs[i].Text = addSpan + diffs[i].Text - addSpan = "" - } - if strings.HasPrefix(diffs[i].Text, "") { - buf.WriteString("") - diffs[i].Text = strings.TrimPrefix(diffs[i].Text, "") - } - m := addSpanRegex.FindStringSubmatchIndex(diffs[i].Text) - if m != nil { - addSpan = diffs[i].Text[m[0]:m[1]] - diffs[i].Text = strings.TrimSuffix(diffs[i].Text, addSpan) - } + case diff.Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel: buf.Write(removedCodePrefix) - buf.WriteString(getLineContent(diffs[i].Text)) + buf.WriteString(diff.Text) buf.Write(codeTagSuffix) } } @@ -350,6 +347,7 @@ type DiffFile struct { IsSubmodule bool Sections []*DiffSection IsIncomplete bool + IsProtected bool } // GetType returns type of diff file. @@ -439,91 +437,254 @@ func (diff *Diff) LoadComments(issue *models.Issue, currentUser *models.User) er const cmdDiffHead = "diff --git " -// ParsePatch builds a Diff object from a io.Reader and some -// parameters. -// TODO: move this function to gogits/git-module +// ParsePatch builds a Diff object from a io.Reader and some parameters. func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*Diff, error) { - var ( - diff = &Diff{Files: make([]*DiffFile, 0)} - curFile = &DiffFile{} - curSection = &DiffSection{ - Lines: make([]*DiffLine, 0, 10), + var curFile *DiffFile + + diff := &Diff{Files: make([]*DiffFile, 0)} + + sb := strings.Builder{} + + // OK let's set a reasonable buffer size. + // This should be let's say at least the size of maxLineCharacters or 4096 whichever is larger. + readerSize := maxLineCharacters + if readerSize < 4096 { + readerSize = 4096 + } + + input := bufio.NewReaderSize(reader, readerSize) + line, err := input.ReadString('\n') + if err != nil { + if err == io.EOF { + return diff, nil + } + return diff, err + } +parsingLoop: + for { + // 1. A patch file always begins with `diff --git ` + `a/path b/path` (possibly quoted) + // if it does not we have bad input! + if !strings.HasPrefix(line, cmdDiffHead) { + return diff, fmt.Errorf("Invalid first file line: %s", line) } - leftLine, rightLine int - lineCount int - curFileLinesCount int - curFileLFSPrefix bool - ) + // TODO: Handle skipping first n files + if len(diff.Files) >= maxFiles { + diff.IsIncomplete = true + _, err := io.Copy(ioutil.Discard, reader) + if err != nil { + // By the definition of io.Copy this never returns io.EOF + return diff, fmt.Errorf("Copy: %v", err) + } + break parsingLoop + } - input := bufio.NewReader(reader) - isEOF := false - for !isEOF { - var linebuf bytes.Buffer + curFile = createDiffFile(diff, line) + diff.Files = append(diff.Files, curFile) + + // 2. It is followed by one or more extended header lines: + // + // old mode + // new mode + // deleted file mode + // new file mode + // copy from + // copy to + // rename from + // rename to + // similarity index + // dissimilarity index + // index .. + // + // * 6-digit octal numbers including the file type and file permission bits. + // * does not include the a/ and b/ prefixes + // * percentage of unchanged lines for similarity, percentage of changed + // lines dissimilarity as integer rounded down with terminal %. 100% => equal files. + // * The index line includes the blob object names before and after the change. + // The is included if the file mode does not change; otherwise, separate + // lines indicate the old and the new mode. + // 3. Following this header the "standard unified" diff format header may be encountered: (but not for every case...) + // + // --- a/ + // +++ b/ + // + // With multiple hunks + // + // @@ @@ + // +added line + // -removed line + // unchanged line + // + // 4. Binary files get: + // + // Binary files a/ and b/ differ + // + // but one of a/ and b/ could be /dev/null. + curFileLoop: for { - b, err := input.ReadByte() + line, err = input.ReadString('\n') if err != nil { - if err == io.EOF { - isEOF = true - break - } else { - return nil, fmt.Errorf("ReadByte: %v", err) + if err != io.EOF { + return diff, err } + break parsingLoop } - if b == '\n' { - break - } - if linebuf.Len() < maxLineCharacters { - linebuf.WriteByte(b) - } else if linebuf.Len() == maxLineCharacters { - curFile.IsIncomplete = true + switch { + case strings.HasPrefix(line, cmdDiffHead): + break curFileLoop + case strings.HasPrefix(line, "old mode ") || + strings.HasPrefix(line, "new mode "): + if strings.HasSuffix(line, " 160000\n") { + curFile.IsSubmodule = true + } + case strings.HasPrefix(line, "copy from "): + curFile.IsRenamed = true + curFile.Type = DiffFileCopy + case strings.HasPrefix(line, "copy to "): + curFile.IsRenamed = true + curFile.Type = DiffFileCopy + case strings.HasPrefix(line, "new file"): + curFile.Type = DiffFileAdd + curFile.IsCreated = true + if strings.HasSuffix(line, " 160000\n") { + curFile.IsSubmodule = true + } + case strings.HasPrefix(line, "deleted"): + curFile.Type = DiffFileDel + curFile.IsDeleted = true + if strings.HasSuffix(line, " 160000\n") { + curFile.IsSubmodule = true + } + case strings.HasPrefix(line, "index"): + if strings.HasSuffix(line, " 160000\n") { + curFile.IsSubmodule = true + } + case strings.HasPrefix(line, "similarity index 100%"): + curFile.Type = DiffFileRename + case strings.HasPrefix(line, "Binary"): + curFile.IsBin = true + case strings.HasPrefix(line, "--- "): + // Do nothing with this line + case strings.HasPrefix(line, "+++ "): + // Do nothing with this line + lineBytes, isFragment, err := parseHunks(curFile, maxLines, maxLineCharacters, input) + diff.TotalAddition += curFile.Addition + diff.TotalDeletion += curFile.Deletion + if err != nil { + if err != io.EOF { + return diff, err + } + break parsingLoop + } + sb.Reset() + _, _ = sb.Write(lineBytes) + for isFragment { + lineBytes, isFragment, err = input.ReadLine() + if err != nil { + // Now by the definition of ReadLine this cannot be io.EOF + return diff, fmt.Errorf("Unable to ReadLine: %v", err) + } + _, _ = sb.Write(lineBytes) + } + line = sb.String() + sb.Reset() + + break curFileLoop } } - line := linebuf.String() - if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 { - continue + } + + // FIXME: There are numerous issues with this: + // - we might want to consider detecting encoding while parsing but... + // - we're likely to fail to get the correct encoding here anyway as we won't have enough information + // - and this doesn't really account for changes in encoding + var buf bytes.Buffer + for _, f := range diff.Files { + buf.Reset() + for _, sec := range f.Sections { + for _, l := range sec.Lines { + if l.Type == DiffLineSection { + continue + } + buf.WriteString(l.Content[1:]) + buf.WriteString("\n") + } + } + charsetLabel, err := charset.DetectEncoding(buf.Bytes()) + if charsetLabel != "UTF-8" && err == nil { + encoding, _ := stdcharset.Lookup(charsetLabel) + if encoding != nil { + d := encoding.NewDecoder() + for _, sec := range f.Sections { + for _, l := range sec.Lines { + if l.Type == DiffLineSection { + continue + } + if c, _, err := transform.String(d, l.Content[1:]); err == nil { + l.Content = l.Content[0:1] + c + } + } + } + } } + } - trimLine := strings.Trim(line, "+- ") + diff.NumFiles = len(diff.Files) + return diff, nil +} - if trimLine == models.LFSMetaFileIdentifier { - curFileLFSPrefix = true - } +func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) { + sb := strings.Builder{} - if curFileLFSPrefix && strings.HasPrefix(trimLine, models.LFSMetaFileOidPrefix) { - oid := strings.TrimPrefix(trimLine, models.LFSMetaFileOidPrefix) + var ( + curSection *DiffSection + curFileLinesCount int + curFileLFSPrefix bool + ) - if len(oid) == 64 { - m := &models.LFSMetaObject{Oid: oid} - count, err := models.Count(m) + leftLine, rightLine := 1, 1 - if err == nil && count > 0 { - curFile.IsBin = true - curFile.IsLFSFile = true - curSection.Lines = nil - } + for { + sb.Reset() + lineBytes, isFragment, err = input.ReadLine() + if err != nil { + if err == io.EOF { + return } + err = fmt.Errorf("Unable to ReadLine: %v", err) + return } + if lineBytes[0] == 'd' { + // End of hunks + return + } + + switch lineBytes[0] { + case '@': + if curFileLinesCount >= maxLines { + curFile.IsIncomplete = true + continue + } - curFileLinesCount++ - lineCount++ + _, _ = sb.Write(lineBytes) + for isFragment { + // This is very odd indeed - we're in a section header and the line is too long + // This really shouldn't happen... + lineBytes, isFragment, err = input.ReadLine() + if err != nil { + // Now by the definition of ReadLine this cannot be io.EOF + err = fmt.Errorf("Unable to ReadLine: %v", err) + return + } + _, _ = sb.Write(lineBytes) + } + line := sb.String() - // Diff data too large, we only show the first about maxLines lines - if curFileLinesCount >= maxLines { - curFile.IsIncomplete = true - } - switch { - case line[0] == ' ': - diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine} - leftLine++ - rightLine++ - curSection.Lines = append(curSection.Lines, diffLine) - curSection.FileName = curFile.Name - continue - case line[0] == '@': + // Create a new section to represent this hunk curSection = &DiffSection{} curFile.Sections = append(curFile.Sections, curSection) + lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1) diffLine := &DiffLine{ Type: DiffLineSection, @@ -536,136 +697,132 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D leftLine = lineSectionInfo.LeftIdx rightLine = lineSectionInfo.RightIdx continue - case line[0] == '+': + case '\\': + if curFileLinesCount >= maxLines { + curFile.IsIncomplete = true + continue + } + // This is used only to indicate that the current file does not have a terminal newline + if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) { + err = fmt.Errorf("Unexpected line in hunk: %s", string(lineBytes)) + return + } + // Technically this should be the end the file! + // FIXME: we should be putting a marker at the end of the file if there is no terminal new line + continue + case '+': + curFileLinesCount++ curFile.Addition++ - diff.TotalAddition++ - diffLine := &DiffLine{Type: DiffLineAdd, Content: line, RightIdx: rightLine} + if curFileLinesCount >= maxLines { + curFile.IsIncomplete = true + continue + } + diffLine := &DiffLine{Type: DiffLineAdd, RightIdx: rightLine} rightLine++ curSection.Lines = append(curSection.Lines, diffLine) - curSection.FileName = curFile.Name - continue - case line[0] == '-': + case '-': + curFileLinesCount++ curFile.Deletion++ - diff.TotalDeletion++ - diffLine := &DiffLine{Type: DiffLineDel, Content: line, LeftIdx: leftLine} + if curFileLinesCount >= maxLines { + curFile.IsIncomplete = true + continue + } + diffLine := &DiffLine{Type: DiffLineDel, LeftIdx: leftLine} if leftLine > 0 { leftLine++ } curSection.Lines = append(curSection.Lines, diffLine) - curSection.FileName = curFile.Name - case strings.HasPrefix(line, "Binary"): - curFile.IsBin = true - continue + case ' ': + curFileLinesCount++ + if curFileLinesCount >= maxLines { + curFile.IsIncomplete = true + continue + } + diffLine := &DiffLine{Type: DiffLinePlain, LeftIdx: leftLine, RightIdx: rightLine} + leftLine++ + rightLine++ + curSection.Lines = append(curSection.Lines, diffLine) + default: + // This is unexpected + err = fmt.Errorf("Unexpected line in hunk: %s", string(lineBytes)) + return } - // Get new file. - if strings.HasPrefix(line, cmdDiffHead) { - if len(diff.Files) >= maxFiles { - diff.IsIncomplete = true - _, err := io.Copy(ioutil.Discard, reader) + line := string(lineBytes) + if isFragment { + curFile.IsIncomplete = true + for isFragment { + lineBytes, isFragment, err = input.ReadLine() if err != nil { - return nil, fmt.Errorf("Copy: %v", err) + // Now by the definition of ReadLine this cannot be io.EOF + err = fmt.Errorf("Unable to ReadLine: %v", err) + return } - break } + } + curSection.Lines[len(curSection.Lines)-1].Content = line - // Note: In case file name is surrounded by double quotes (it happens only in git-shell). - // e.g. diff --git "a/xxx" "b/xxx" - var a string - var b string - - rd := strings.NewReader(line[len(cmdDiffHead):]) - char, _ := rd.ReadByte() - _ = rd.UnreadByte() - if char == '"' { - fmt.Fscanf(rd, "%q ", &a) - } else { - fmt.Fscanf(rd, "%s ", &a) - } - char, _ = rd.ReadByte() - _ = rd.UnreadByte() - if char == '"' { - fmt.Fscanf(rd, "%q", &b) - } else { - fmt.Fscanf(rd, "%s", &b) - } - a = a[2:] - b = b[2:] - - curFile = &DiffFile{ - Name: b, - OldName: a, - Index: len(diff.Files) + 1, - Type: DiffFileChange, - Sections: make([]*DiffSection, 0, 10), - IsRenamed: a != b, - } - diff.Files = append(diff.Files, curFile) - curFileLinesCount = 0 - leftLine = 1 - rightLine = 1 - curFileLFSPrefix = false - - // Check file diff type and is submodule. - for { - line, err := input.ReadString('\n') - if err != nil { - if err == io.EOF { - isEOF = true - } else { - return nil, fmt.Errorf("ReadString: %v", err) - } - } + // handle LFS + if line[1:] == models.LFSMetaFileIdentifier { + curFileLFSPrefix = true + } else if curFileLFSPrefix && strings.HasPrefix(line[1:], models.LFSMetaFileOidPrefix) { + oid := strings.TrimPrefix(line[1:], models.LFSMetaFileOidPrefix) + if len(oid) == 64 { + m := &models.LFSMetaObject{Oid: oid} + count, err := models.Count(m) - switch { - case strings.HasPrefix(line, "new file"): - curFile.Type = DiffFileAdd - curFile.IsCreated = true - case strings.HasPrefix(line, "deleted"): - curFile.Type = DiffFileDel - curFile.IsDeleted = true - case strings.HasPrefix(line, "index"): - curFile.Type = DiffFileChange - case strings.HasPrefix(line, "similarity index 100%"): - curFile.Type = DiffFileRename - } - if curFile.Type > 0 { - if strings.HasSuffix(line, " 160000\n") { - curFile.IsSubmodule = true - } - break + if err == nil && count > 0 { + curFile.IsBin = true + curFile.IsLFSFile = true + curSection.Lines = nil } } } } +} - // FIXME: detect encoding while parsing. - var buf bytes.Buffer - for _, f := range diff.Files { - buf.Reset() - for _, sec := range f.Sections { - for _, l := range sec.Lines { - buf.WriteString(l.Content) - buf.WriteString("\n") - } - } - charsetLabel, err := charset.DetectEncoding(buf.Bytes()) - if charsetLabel != "UTF-8" && err == nil { - encoding, _ := stdcharset.Lookup(charsetLabel) - if encoding != nil { - d := encoding.NewDecoder() - for _, sec := range f.Sections { - for _, l := range sec.Lines { - if c, _, err := transform.String(d, l.Content); err == nil { - l.Content = c - } - } - } - } +func createDiffFile(diff *Diff, line string) *DiffFile { + // The a/ and b/ filenames are the same unless rename/copy is involved. + // Especially, even for a creation or a deletion, /dev/null is not used + // in place of the a/ or b/ filenames. + // + // When rename/copy is involved, file1 and file2 show the name of the + // source file of the rename/copy and the name of the file that rename/copy + // produces, respectively. + // + // Path names are quoted if necessary. + // + // This means that you should always be able to determine the file name even when there + // there is potential ambiguity... + // + // but we can be simpler with our heuristics by just forcing git to prefix things nicely + curFile := &DiffFile{ + Index: len(diff.Files) + 1, + Type: DiffFileChange, + Sections: make([]*DiffSection, 0, 10), + } + + rd := strings.NewReader(line[len(cmdDiffHead):] + " ") + curFile.Type = DiffFileChange + curFile.OldName = readFileName(rd) + curFile.Name = readFileName(rd) + curFile.IsRenamed = curFile.Name != curFile.OldName + return curFile +} + +func readFileName(rd *strings.Reader) string { + var name string + char, _ := rd.ReadByte() + _ = rd.UnreadByte() + if char == '"' { + fmt.Fscanf(rd, "%q ", &name) + if name[0] == '\\' { + name = name[1:] } + } else { + fmt.Fscanf(rd, "%s ", &name) } - diff.NumFiles = len(diff.Files) - return diff, nil + return name[2:] } // GetDiffRange builds a Diff between two commits of a repository. @@ -695,14 +852,21 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID defer cancel() var cmd *exec.Cmd if (len(beforeCommitID) == 0 || beforeCommitID == git.EmptySHA) && commit.ParentCount() == 0 { - cmd = exec.CommandContext(ctx, git.GitExecutable, "show", afterCommitID) + diffArgs := []string{"diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"} + if len(whitespaceBehavior) != 0 { + diffArgs = append(diffArgs, whitespaceBehavior) + } + // append empty tree ref + diffArgs = append(diffArgs, "4b825dc642cb6eb9a060e54bf8d69288fbee4904") + diffArgs = append(diffArgs, afterCommitID) + cmd = exec.CommandContext(ctx, git.GitExecutable, diffArgs...) } else { actualBeforeCommitID := beforeCommitID if len(actualBeforeCommitID) == 0 { parentCommit, _ := commit.Parent(0) actualBeforeCommitID = parentCommit.ID.String() } - diffArgs := []string{"diff", "-M"} + diffArgs := []string{"diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"} if len(whitespaceBehavior) != 0 { diffArgs = append(diffArgs, whitespaceBehavior) } diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 9826ece2dc5c..e7eeca70045d 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -6,6 +6,7 @@ package gitdiff import ( + "encoding/json" "fmt" "html/template" "strings" @@ -14,11 +15,9 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" - - "gopkg.in/ini.v1" - dmp "github.com/sergi/go-diff/diffmatchpatch" "github.com/stretchr/testify/assert" + "gopkg.in/ini.v1" ) func assertEqual(t *testing.T, s1 string, s2 template.HTML) { @@ -51,7 +50,7 @@ func TestDiffToHTML(t *testing.T) { {Type: dmp.DiffEqual, Text: " {"}, }, DiffLineAdd)) - assertEqual(t, "tagURL := fmt.Sprintf("## [%s](%s/%s/%s/%s?q=&type=all&state=closed&milestone=%d) - %s", ge.Milestone\", ge.BaseURL, ge.Owner, ge.Repo, from, milestoneID, time.Now().Format("2006-01-02"))", diffToHTML("", []dmp.Diff{ + assertEqual(t, "tagURL := fmt.Sprintf("## [%s](%s/%s/%s/%s?q=&type=all&state=closed&milestone=%d) - %s", ge.Milestone\", ge.BaseURL, ge.Owner, ge.Repo, from, milestoneID, time.Now().Format("2006-01-02"))", diffToHTML("", []dmp.Diff{ {Type: dmp.DiffEqual, Text: "tagURL := fmt.Sprintf("## [%s](%s/%s/%s/%s?q=&type=all&state=closed&milestone=%d) - %s", ge.Milestone\""}, {Type: dmp.DiffInsert, Text: "f\">getGiteaTagURL(client"}, @@ -61,7 +60,7 @@ func TestDiffToHTML(t *testing.T) { {Type: dmp.DiffEqual, Text: ")"}, }, DiffLineDel)) - assertEqual(t, "r.WrapperRenderer(w, language, true, attrs, false)", diffToHTML("", []dmp.Diff{ + assertEqual(t, "r.WrapperRenderer(w, language, true, attrs, false)", diffToHTML("", []dmp.Diff{ {Type: dmp.DiffEqual, Text: "r.WrapperRenderer(w, "}, {Type: dmp.DiffDelete, Text: "language, true, attrs"}, {Type: dmp.DiffEqual, Text: ", false)"}, }, DiffLineAdd)) + + assertEqual(t, "print("// ", sys.argv)", diffToHTML("", []dmp.Diff{ + {Type: dmp.DiffEqual, Text: "print"}, + {Type: dmp.DiffInsert, Text: "("}, + {Type: dmp.DiffEqual, Text: ""// ", sys.argv"}, + {Type: dmp.DiffInsert, Text: ")"}, + }, DiffLineAdd)) } -func TestParsePatch(t *testing.T) { +func TestParsePatch_singlefile(t *testing.T) { + type testcase struct { + name string + gitdiff string + wantErr bool + addition int + deletion int + oldFilename string + filename string + } + + tests := []testcase{ + { + name: "readme.md2readme.md", + gitdiff: `diff --git "\\a/README.md" "\\b/README.md" +--- "\\a/README.md" ++++ "\\b/README.md" +@@ -1,3 +1,6 @@ + # gitea-github-migrator ++ ++ Build Status +- Latest Release + Docker Pulls ++ cut off ++ cut off +`, + addition: 4, + deletion: 1, + filename: "README.md", + oldFilename: "README.md", + }, + { + name: "A \\ B", + gitdiff: `diff --git "a/A \\ B" "b/A \\ B" +--- "a/A \\ B" ++++ "b/A \\ B" +@@ -1,3 +1,6 @@ + # gitea-github-migrator ++ ++ Build Status +- Latest Release + Docker Pulls ++ cut off ++ cut off`, + addition: 4, + deletion: 1, + filename: "A \\ B", + oldFilename: "A \\ B", + }, + { + name: "really weird filename", + gitdiff: `diff --git "\\a/a b/file b/a a/file" "\\b/a b/file b/a a/file" +index d2186f1..f5c8ed2 100644 +--- "\\a/a b/file b/a a/file" ++++ "\\b/a b/file b/a a/file" +@@ -1,3 +1,2 @@ + Create a weird file. + +-and what does diff do here? +\ No newline at end of file`, + addition: 0, + deletion: 1, + filename: "a b/file b/a a/file", + oldFilename: "a b/file b/a a/file", + }, + { + name: "delete file with blanks", + gitdiff: `diff --git "\\a/file with blanks" "\\b/file with blanks" +deleted file mode 100644 +index 898651a..0000000 +--- "\\a/file with blanks" ++++ /dev/null +@@ -1,5 +0,0 @@ +-a blank file +- +-has a couple o line +- +-the 5th line is the last +`, + addition: 0, + deletion: 5, + filename: "file with blanks", + oldFilename: "file with blanks", + }, + { + name: "rename a—as", + gitdiff: `diff --git "a/\360\243\220\265b\342\200\240vs" "b/a\342\200\224as" +similarity index 100% +rename from "\360\243\220\265b\342\200\240vs" +rename to "a\342\200\224as" +`, + addition: 0, + deletion: 0, + oldFilename: "𣐵b†vs", + filename: "a—as", + }, + { + name: "rename with spaces", + gitdiff: `diff --git "\\a/a b/file b/a a/file" "\\b/a b/a a/file b/b file" +similarity index 100% +rename from a b/file b/a a/file +rename to a b/a a/file b/b file +`, + oldFilename: "a b/file b/a a/file", + filename: "a b/a a/file b/b file", + }, + { + name: "minuses-and-pluses", + gitdiff: `diff --git a/minuses-and-pluses b/minuses-and-pluses +index 6961180..9ba1a00 100644 +--- a/minuses-and-pluses ++++ b/minuses-and-pluses +@@ -1,4 +1,4 @@ +--- 1st line +-++ 2nd line +--- 3rd line +-++ 4th line ++++ 1st line ++-- 2nd line ++++ 3rd line ++-- 4th line +`, + oldFilename: "minuses-and-pluses", + filename: "minuses-and-pluses", + addition: 4, + deletion: 4, + }, + } + + for _, testcase := range tests { + t.Run(testcase.name, func(t *testing.T) { + got, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff)) + if (err != nil) != testcase.wantErr { + t.Errorf("ParsePatch() error = %v, wantErr %v", err, testcase.wantErr) + return + } + gotMarshaled, _ := json.MarshalIndent(got, " ", " ") + if got.NumFiles != 1 { + t.Errorf("ParsePath() did not receive 1 file:\n%s", string(gotMarshaled)) + return + } + if got.TotalAddition != testcase.addition { + t.Errorf("ParsePath() does not have correct totalAddition %d, wanted %d", got.TotalAddition, testcase.addition) + } + if got.TotalDeletion != testcase.deletion { + t.Errorf("ParsePath() did not have correct totalDeletion %d, wanted %d", got.TotalDeletion, testcase.deletion) + } + file := got.Files[0] + if file.Addition != testcase.addition { + t.Errorf("ParsePath() does not have correct file addition %d, wanted %d", file.Addition, testcase.addition) + } + if file.Deletion != testcase.deletion { + t.Errorf("ParsePath() did not have correct file deletion %d, wanted %d", file.Deletion, testcase.deletion) + } + if file.OldName != testcase.oldFilename { + t.Errorf("ParsePath() did not have correct OldName %s, wanted %s", file.OldName, testcase.oldFilename) + } + if file.Name != testcase.filename { + t.Errorf("ParsePath() did not have correct Name %s, wanted %s", file.Name, testcase.filename) + } + }) + } + var diff = `diff --git "a/README.md" "b/README.md" --- a/README.md +++ b/README.md diff --git a/services/issue/assignee.go b/services/issue/assignee.go index d63c7bf032e6..f24a242f6b7d 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -6,6 +6,7 @@ package issue import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" ) @@ -52,9 +53,8 @@ func ToggleAssignee(issue *models.Issue, doer *models.User, assigneeID int64) (r return } -// ReviewRequest add or remove a review for this PR, and make comment for it. -func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User, isAdd bool) (err error) { - var comment *models.Comment +// ReviewRequest add or remove a review request from a user for this PR, and make comment for it. +func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User, isAdd bool) (comment *models.Comment, err error) { if isAdd { comment, err = models.AddReviewRequest(issue, reviewer, doer) } else { @@ -62,12 +62,201 @@ func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User } if err != nil { - return + return nil, err } if comment != nil { notification.NotifyPullReviewRequest(doer, issue, reviewer, isAdd, comment) } + return +} + +// IsValidReviewRequest Check permission for ReviewRequest +func IsValidReviewRequest(reviewer, doer *models.User, isAdd bool, issue *models.Issue, permDoer *models.Permission) error { + if reviewer.IsOrganization() { + return models.ErrNotValidReviewRequest{ + Reason: "Organization can't be added as reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + if doer.IsOrganization() { + return models.ErrNotValidReviewRequest{ + Reason: "Organization can't be doer to add reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + + permReviewer, err := models.GetUserRepoPermission(issue.Repo, reviewer) + if err != nil { + return err + } + + if permDoer == nil { + permDoer = new(models.Permission) + *permDoer, err = models.GetUserRepoPermission(issue.Repo, doer) + if err != nil { + return err + } + } + + lastreview, err := models.GetReviewByIssueIDAndUserID(issue.ID, reviewer.ID) + if err != nil && !models.IsErrReviewNotExist(err) { + return err + } + + var pemResult bool + if isAdd { + pemResult = permReviewer.CanAccessAny(models.AccessModeRead, models.UnitTypePullRequests) + if !pemResult { + return models.ErrNotValidReviewRequest{ + Reason: "Reviewer can't read", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + + if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != models.ReviewTypeRequest { + return nil + } + + pemResult = permDoer.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests) + if !pemResult { + pemResult, err = models.IsOfficialReviewer(issue, doer) + if err != nil { + return err + } + if !pemResult { + return models.ErrNotValidReviewRequest{ + Reason: "Doer can't choose reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + } + + if doer.ID == reviewer.ID { + return models.ErrNotValidReviewRequest{ + Reason: "doer can't be reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + + if reviewer.ID == issue.PosterID && issue.OriginalAuthorID == 0 { + return models.ErrNotValidReviewRequest{ + Reason: "poster of pr can't be reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + } else { + if lastreview != nil && lastreview.Type == models.ReviewTypeRequest && lastreview.ReviewerID == doer.ID { + return nil + } + + pemResult = permDoer.IsAdmin() + if !pemResult { + return models.ErrNotValidReviewRequest{ + Reason: "Doer is not admin", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + } + + return nil +} + +// IsValidTeamReviewRequest Check permission for ReviewRequest Team +func IsValidTeamReviewRequest(reviewer *models.Team, doer *models.User, isAdd bool, issue *models.Issue) error { + if doer.IsOrganization() { + return models.ErrNotValidReviewRequest{ + Reason: "Organization can't be doer to add reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + + permission, err := models.GetUserRepoPermission(issue.Repo, doer) + if err != nil { + log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index) + return err + } + + if isAdd { + if issue.Repo.IsPrivate { + hasTeam := models.HasTeamRepo(reviewer.OrgID, reviewer.ID, issue.RepoID) + + if !hasTeam { + return models.ErrNotValidReviewRequest{ + Reason: "Reviewing team can't read repo", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + } + + doerCanWrite := permission.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests) + if !doerCanWrite { + official, err := models.IsOfficialReviewer(issue, doer) + if err != nil { + log.Error("Unable to Check if IsOfficialReviewer for %-v in %-v#%d", doer, issue.Repo, issue.Index) + return err + } + if !official { + return models.ErrNotValidReviewRequest{ + Reason: "Doer can't choose reviewer", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + } + } else if !permission.IsAdmin() { + return models.ErrNotValidReviewRequest{ + Reason: "Only admin users can remove team requests. Doer is not admin", + UserID: doer.ID, + RepoID: issue.Repo.ID, + } + } + return nil } + +// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. +func TeamReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.Team, isAdd bool) (comment *models.Comment, err error) { + if isAdd { + comment, err = models.AddTeamReviewRequest(issue, reviewer, doer) + } else { + comment, err = models.RemoveTeamReviewRequest(issue, reviewer, doer) + } + + if err != nil { + return + } + + if comment == nil || !isAdd { + return + } + + // notify all user in this team + if err = comment.LoadIssue(); err != nil { + return + } + + if err = reviewer.GetMembers(&models.SearchMembersOptions{}); err != nil { + return + } + + for _, member := range reviewer.Members { + if member.ID == comment.Issue.PosterID { + continue + } + comment.AssigneeID = member.ID + notification.NotifyPullReviewRequest(doer, issue, member, isAdd, comment) + } + + return +} diff --git a/services/issue/issue.go b/services/issue/issue.go index 64d69119b762..0f90a2bcd05a 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -42,6 +42,20 @@ func ChangeTitle(issue *models.Issue, doer *models.User, title string) (err erro return nil } +// ChangeIssueRef changes the branch of this issue, as the given user. +func ChangeIssueRef(issue *models.Issue, doer *models.User, ref string) error { + oldRef := issue.Ref + issue.Ref = ref + + if err := issue.ChangeRef(doer, oldRef); err != nil { + return err + } + + notification.NotifyIssueChangeRef(doer, issue, oldRef) + + return nil +} + // UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s) // Deleting is done the GitHub way (quote from their api documentation): // https://developer.github.com/v3/issues/#edit-an-issue diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index 1030a9548e69..01c198984b5d 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -118,7 +118,7 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int visited[id] = true } } - recipients, err := models.GetMaileableUsersByIDs(unique) + recipients, err := models.GetMaileableUsersByIDs(unique, fromMention) if err != nil { return err } diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 55182f55cf5d..f278c853aebd 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -27,7 +27,7 @@ func MailNewRelease(rel *models.Release) { return } - recipients, err := models.GetMaileableUsersByIDs(watcherIDList) + recipients, err := models.GetMaileableUsersByIDs(watcherIDList, false) if err != nil { log.Error("models.GetMaileableUsersByIDs: %v", err) return diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 75054b690f86..22aed2c3a480 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/mcuadros/go-version" "github.com/unknwon/com" ) @@ -43,11 +42,11 @@ func readAddress(m *models.Mirror) { func remoteAddress(repoPath string) (string, error) { var cmd *git.Command - binVersion, err := git.BinVersion() + err := git.LoadGitVersion() if err != nil { return "", err } - if version.Compare(binVersion, "2.7", ">=") { + if git.CheckGitVersionAtLeast("2.7") == nil { cmd = git.NewCommand("remote", "get-url", "origin") } else { cmd = git.NewCommand("config", "--get", "remote.origin.url") @@ -90,8 +89,8 @@ func AddressNoCredentials(m *models.Mirror) string { return u.String() } -// SaveAddress writes new address to Git repository config. -func SaveAddress(m *models.Mirror, addr string) error { +// UpdateAddress writes new address to Git repository and database +func UpdateAddress(m *models.Mirror, addr string) error { repoPath := m.Repo.RepoPath() // Remove old origin _, err := git.NewCommand("remote", "rm", "origin").RunInDir(repoPath) @@ -118,7 +117,9 @@ func SaveAddress(m *models.Mirror, addr string) error { return err } } - return nil + + m.Repo.OriginalURL = addr + return models.UpdateRepositoryCols(m.Repo, "original_url") } // gitShortEmptySha Git short empty SHA diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go index 25e499ad788a..79e18885b338 100644 --- a/services/mirror/mirror_test.go +++ b/services/mirror/mirror_test.go @@ -10,8 +10,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" + migration "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/structs" release_service "code.gitea.io/gitea/services/release" "github.com/stretchr/testify/assert" @@ -28,7 +28,7 @@ func TestRelease_MirrorDelete(t *testing.T) { repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repoPath := models.RepoPath(user.Name, repo.Name) - opts := structs.MigrateRepoOption{ + opts := migration.MigrateOptions{ RepoName: "test_mirror", Description: "Test mirror", Private: false, diff --git a/services/pull/check.go b/services/pull/check.go index 2962528933b5..dd1e087d1896 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -62,7 +62,7 @@ func checkAndUpdateStatus(pr *models.PullRequest) { } if !has { - if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files"); err != nil { + if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { log.Error("Update[%d]: %v", pr.ID, err) } } @@ -243,6 +243,20 @@ func handle(data ...queue.Data) { } } +// CheckPrsForBaseBranch check all pulls with bseBrannch +func CheckPrsForBaseBranch(baseRepo *models.Repository, baseBranchName string) error { + prs, err := models.GetUnmergedPullRequestsByBaseInfo(baseRepo.ID, baseBranchName) + if err != nil { + return err + } + + for _, pr := range prs { + AddToTaskQueue(pr) + } + + return nil +} + // Init runs the task queue to test all the checking status pull requests func Init() error { prQueue = queue.CreateUniqueQueue("pr_patch_checker", handle, "").(queue.UniqueQueue) diff --git a/services/pull/merge.go b/services/pull/merge.go index b8a5c93d7844..ab5b55e66966 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -25,8 +25,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" issue_service "code.gitea.io/gitea/services/issue" - - "github.com/mcuadros/go-version" ) // Merge merges pull request to base repository. @@ -113,9 +111,9 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor // rawMerge perform the merge operation without changing any pull information in database func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.MergeStyle, message string) (string, error) { - binVersion, err := git.BinVersion() + err := git.LoadGitVersion() if err != nil { - log.Error("git.BinVersion: %v", err) + log.Error("git.LoadGitVersion: %v", err) return "", fmt.Errorf("Unable to get git version: %v", err) } @@ -157,7 +155,7 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge } var gitConfigCommand func() *git.Command - if version.Compare(binVersion, "1.8.0", ">=") { + if git.CheckGitVersionAtLeast("1.8.0") == nil { gitConfigCommand = func() *git.Command { return git.NewCommand("config", "--local") } @@ -211,18 +209,23 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge outbuf.Reset() errbuf.Reset() + sig := doer.NewGitSig() + committer := sig + // Determine if we should sign signArg := "" - if version.Compare(binVersion, "1.7.9", ">=") { - sign, keyID, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch) + if git.CheckGitVersionAtLeast("1.7.9") == nil { + sign, keyID, signer, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch) if sign { signArg = "-S" + keyID - } else if version.Compare(binVersion, "2.0.0", ">=") { + if pr.BaseRepo.GetTrustModel() == models.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == models.CollaboratorCommitterTrustModel { + committer = signer + } + } else if git.CheckGitVersionAtLeast("2.0.0") == nil { signArg = "--no-gpg-sign" } } - sig := doer.NewGitSig() commitTimeStr := time.Now().Format(time.RFC3339) // Because this may call hooks we should pass in the environment @@ -230,8 +233,8 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge "GIT_AUTHOR_NAME="+sig.Name, "GIT_AUTHOR_EMAIL="+sig.Email, "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+sig.Name, - "GIT_COMMITTER_EMAIL="+sig.Email, + "GIT_COMMITTER_NAME="+committer.Name, + "GIT_COMMITTER_EMAIL="+committer.Email, "GIT_COMMITTER_DATE="+commitTimeStr, ) @@ -348,6 +351,10 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } } else { + if committer != sig { + // add trailer + message += fmt.Sprintf("\nCo-Authored-By: %s\nCo-Committed-By: %s\n", sig.String(), sig.String()) + } if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -528,7 +535,7 @@ func IsSignedIfRequired(pr *models.PullRequest, doer *models.User) (bool, error) return true, nil } - sign, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) + sign, _, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) return sign, err } @@ -552,7 +559,7 @@ func IsUserAllowedToMerge(pr *models.PullRequest, p models.Permission, user *mod } // CheckPRReadyToMerge checks whether the PR is ready to be merged (reviews and status checks) -func CheckPRReadyToMerge(pr *models.PullRequest) (err error) { +func CheckPRReadyToMerge(pr *models.PullRequest, skipProtectedFilesCheck bool) (err error) { if err = pr.LoadBaseRepo(); err != nil { return fmt.Errorf("LoadBaseRepo: %v", err) } @@ -591,6 +598,16 @@ func CheckPRReadyToMerge(pr *models.PullRequest) (err error) { } } + if skipProtectedFilesCheck { + return nil + } + + if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr) { + return models.ErrNotAllowedToMerge{ + Reason: "Changed protected files", + } + } + return nil } diff --git a/services/pull/patch.go b/services/pull/patch.go index 7c1eaa59046f..edf05146ac87 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -18,6 +18,8 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" + + "github.com/gobwas/glob" ) // DownloadDiffOrPatch will write the patch for the pr to the writer @@ -66,6 +68,7 @@ func TestPatch(pr *models.PullRequest) error { } defer gitRepo.Close() + // 1. update merge base pr.MergeBase, err = git.NewCommand("merge-base", "--", "base", "tracking").RunInDir(tmpBasePath) if err != nil { var err2 error @@ -75,10 +78,32 @@ func TestPatch(pr *models.PullRequest) error { } } pr.MergeBase = strings.TrimSpace(pr.MergeBase) + + // 2. Check for conflicts + if conflicts, err := checkConflicts(pr, gitRepo, tmpBasePath); err != nil || conflicts { + return err + } + + // 3. Check for protected files changes + if err = checkPullFilesProtection(pr, gitRepo); err != nil { + return fmt.Errorf("pr.CheckPullFilesProtection(): %v", err) + } + + if len(pr.ChangedProtectedFiles) > 0 { + log.Trace("Found %d protected files changed", len(pr.ChangedProtectedFiles)) + } + + pr.Status = models.PullRequestStatusMergeable + + return nil +} + +func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath string) (bool, error) { + // 1. Create a plain patch from head to base tmpPatchFile, err := ioutil.TempFile("", "patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) - return fmt.Errorf("Unable to create temporary patch file! Error: %v", err) + return false, fmt.Errorf("Unable to create temporary patch file! Error: %v", err) } defer func() { _ = util.Remove(tmpPatchFile.Name()) @@ -87,38 +112,43 @@ func TestPatch(pr *models.PullRequest) error { if err := gitRepo.GetDiff(pr.MergeBase, "tracking", tmpPatchFile); err != nil { tmpPatchFile.Close() log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) - return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) + return false, fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } stat, err := tmpPatchFile.Stat() if err != nil { tmpPatchFile.Close() - return fmt.Errorf("Unable to stat patch file: %v", err) + return false, fmt.Errorf("Unable to stat patch file: %v", err) } patchPath := tmpPatchFile.Name() tmpPatchFile.Close() + // 1a. if the size of that patch is 0 - there can be no conflicts! if stat.Size() == 0 { log.Debug("PullRequest[%d]: Patch is empty - ignoring", pr.ID) pr.Status = models.PullRequestStatusEmpty pr.ConflictedFiles = []string{} - return nil + return false, nil } log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath) + // 2. preset the pr.Status as checking (this is not save at present) pr.Status = models.PullRequestStatusChecking + // 3. Read the base branch in to the index of the temporary repository _, err = git.NewCommand("read-tree", "base").RunInDir(tmpBasePath) if err != nil { - return fmt.Errorf("git read-tree %s: %v", pr.BaseBranch, err) + return false, fmt.Errorf("git read-tree %s: %v", pr.BaseBranch, err) } + // 4. Now get the pull request configuration to check if we need to ignore whitespace prUnit, err := pr.BaseRepo.GetUnit(models.UnitTypePullRequests) if err != nil { - return err + return false, err } prConfig := prUnit.PullRequestsConfig() + // 5. Prepare the arguments to apply the patch against the index args := []string{"apply", "--check", "--cached"} if prConfig.IgnoreWhitespaceConflicts { args = append(args, "--ignore-whitespace") @@ -126,26 +156,44 @@ func TestPatch(pr *models.PullRequest) error { args = append(args, patchPath) pr.ConflictedFiles = make([]string, 0, 5) + // 6. Prep the pipe: + // - Here we could do the equivalent of: + // `git apply --check --cached patch_file > conflicts` + // Then iterate through the conflicts. However, that means storing all the conflicts + // in memory - which is very wasteful. + // - alternatively we can do the equivalent of: + // `git apply --check ... | grep ...` + // meaning we don't store all of the conflicts unnecessarily. stderrReader, stderrWriter, err := os.Pipe() if err != nil { log.Error("Unable to open stderr pipe: %v", err) - return fmt.Errorf("Unable to open stderr pipe: %v", err) + return false, fmt.Errorf("Unable to open stderr pipe: %v", err) } defer func() { _ = stderrReader.Close() _ = stderrWriter.Close() }() + + // 7. Run the check command conflict := false err = git.NewCommand(args...). RunInDirTimeoutEnvFullPipelineFunc( nil, -1, tmpBasePath, nil, stderrWriter, nil, func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing _ = stderrWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stderrReader.Close() + }() + const prefix = "error: patch failed:" const errorPrefix = "error: " + conflictMap := map[string]bool{} + // Now scan the output from the command scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { line := scanner.Text() @@ -170,25 +218,111 @@ func TestPatch(pr *models.PullRequest) error { break } } + if len(conflictMap) > 0 { pr.ConflictedFiles = make([]string, 0, len(conflictMap)) for key := range conflictMap { pr.ConflictedFiles = append(pr.ConflictedFiles, key) } } - _ = stderrReader.Close() + return nil }) + // 8. If there is a conflict the `git apply` command will return a non-zero error code - so there will be a positive error. if err != nil { if conflict { pr.Status = models.PullRequestStatusConflict log.Trace("Found %d files conflicted: %v", len(pr.ConflictedFiles), pr.ConflictedFiles) - return nil + + return true, nil } - return fmt.Errorf("git apply --check: %v", err) + return false, fmt.Errorf("git apply --check: %v", err) } - pr.Status = models.PullRequestStatusMergeable + return false, nil +} + +// CheckFileProtection check file Protection +func CheckFileProtection(oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string, repo *git.Repository) ([]string, error) { + // 1. If there are no patterns short-circuit and just return nil + if len(patterns) == 0 { + return nil, nil + } + + // 2. Prep the pipe + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create os.Pipe for %s", repo.Path) + return nil, err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + changedProtectedFiles := make([]string, 0, limit) + // 3. Run `git diff --name-only` to get the names of the changed files + err = git.NewCommand("diff", "--name-only", oldCommitID, newCommitID). + RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path, + stdoutWriter, nil, nil, + func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = stdoutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stdoutReader.Close() + }() + + // Now scan the output from the command + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + path := strings.TrimSpace(scanner.Text()) + if len(path) == 0 { + continue + } + lpath := strings.ToLower(path) + for _, pat := range patterns { + if pat.Match(lpath) { + changedProtectedFiles = append(changedProtectedFiles, path) + break + } + } + if len(changedProtectedFiles) >= limit { + break + } + } + + if len(changedProtectedFiles) > 0 { + return models.ErrFilePathProtected{ + Path: changedProtectedFiles[0], + } + } + return scanner.Err() + }) + // 4. log real errors if there are any... + if err != nil && !models.IsErrFilePathProtected(err) { + log.Error("Unable to check file protection for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) + } + + return changedProtectedFiles, err +} + +// checkPullFilesProtection check if pr changed protected files and save results +func checkPullFilesProtection(pr *models.PullRequest, gitRepo *git.Repository) error { + if err := pr.LoadProtectedBranch(); err != nil { + return err + } + + if pr.ProtectedBranch == nil { + pr.ChangedProtectedFiles = nil + return nil + } + + var err error + pr.ChangedProtectedFiles, err = CheckFileProtection(pr.MergeBase, "tracking", pr.ProtectedBranch.GetProtectedFilePatterns(), 10, os.Environ(), gitRepo) + if err != nil && !models.IsErrFilePathProtected(err) { + return err + } return nil } diff --git a/services/pull/pull.go b/services/pull/pull.go index e624b182aa58..61af7fe9a5df 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -10,7 +10,6 @@ import ( "context" "encoding/json" "fmt" - "path" "strings" "time" @@ -20,7 +19,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" "github.com/unknwon/com" @@ -175,7 +173,7 @@ func ChangeTargetBranch(pr *models.PullRequest, doer *models.User, targetBranch pr.CommitsAhead = divergence.Ahead pr.CommitsBehind = divergence.Behind - if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "base_branch", "commits_ahead", "commits_behind"); err != nil { + if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil { return err } @@ -215,18 +213,6 @@ func checkForInvalidation(requests models.PullRequestList, repoID int64, doer *m return nil } -func addHeadRepoTasks(prs []*models.PullRequest) { - for _, pr := range prs { - log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID) - if err := PushToBaseRepo(pr); err != nil { - log.Error("PushToBaseRepo: %v", err) - continue - } - - AddToTaskQueue(pr) - } -} - // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch, // and generate new patch for testing as needed. func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) { @@ -283,8 +269,14 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy } } - addHeadRepoTasks(prs) for _, pr := range prs { + log.Trace("Updating PR[%d]: composing new test task", pr.ID) + if err := PushToBaseRepo(pr); err != nil { + log.Error("PushToBaseRepo: %v", err) + continue + } + + AddToTaskQueue(pr) comment, err := models.CreatePushPullComment(doer, pr, oldCommitID, newCommitID) if err == nil && comment != nil { notification.NotifyPullRequestPushCommits(doer, pr, comment) @@ -389,54 +381,17 @@ func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID st func PushToBaseRepo(pr *models.PullRequest) (err error) { log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName()) - // Clone base repo. - tmpBasePath, err := models.CreateTemporaryPath("pull") - if err != nil { - log.Error("CreateTemporaryPath: %v", err) - return err - } - defer func() { - err := models.RemoveTemporaryPath(tmpBasePath) - if err != nil { - log.Error("Error whilst removing temporary path: %s Error: %v", tmpBasePath, err) - } - }() - if err := pr.LoadHeadRepo(); err != nil { log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err) return err } headRepoPath := pr.HeadRepo.RepoPath() - if err := git.Clone(headRepoPath, tmpBasePath, git.CloneRepoOptions{ - Bare: true, - Shared: true, - Branch: pr.HeadBranch, - Quiet: true, - }); err != nil { - log.Error("git clone tmpBasePath: %v", err) - return err - } - gitRepo, err := git.OpenRepository(tmpBasePath) - if err != nil { - return fmt.Errorf("OpenRepository: %v", err) - } - if err := pr.LoadBaseRepo(); err != nil { log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err) return err } - if err := gitRepo.AddRemote("base", pr.BaseRepo.RepoPath(), false); err != nil { - return fmt.Errorf("tmpGitRepo.AddRemote: %v", err) - } - defer gitRepo.Close() - - headFile := pr.GetGitRefName() - - // Remove head in case there is a conflict. - file := path.Join(pr.BaseRepo.RepoPath(), headFile) - - _ = util.Remove(file) + baseRepoPath := pr.BaseRepo.RepoPath() if err = pr.LoadIssue(); err != nil { return fmt.Errorf("unable to load issue %d for pr %d: %v", pr.IssueID, pr.ID, err) @@ -445,24 +400,26 @@ func PushToBaseRepo(pr *models.PullRequest) (err error) { return fmt.Errorf("unable to load poster %d for pr %d: %v", pr.Issue.PosterID, pr.ID, err) } - if err = git.Push(tmpBasePath, git.PushOptions{ - Remote: "base", - Branch: fmt.Sprintf("%s:%s", pr.HeadBranch, headFile), + gitRefName := pr.GetGitRefName() + + if err := git.Push(headRepoPath, git.PushOptions{ + Remote: baseRepoPath, + Branch: pr.HeadBranch + ":" + gitRefName, Force: true, // Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/... Env: models.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo), }); err != nil { if git.IsErrPushOutOfDate(err) { // This should not happen as we're using force! - log.Error("Unable to push PR head for %s#%d (%-v:%s) due to ErrPushOfDate: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, err) + log.Error("Unable to push PR head for %s#%d (%-v:%s) due to ErrPushOfDate: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err) return err } else if git.IsErrPushRejected(err) { rejectErr := err.(*git.ErrPushRejected) - log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err) + log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err) return err } - log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, headFile, err) - return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), headFile, err) + log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err) + return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err) } return nil diff --git a/services/pull/review.go b/services/pull/review.go index 5a77a4da1684..99afdd73c215 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -8,6 +8,7 @@ package pull import ( "bytes" "fmt" + "regexp" "strings" "code.gitea.io/gitea/models" @@ -67,14 +68,13 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models return nil, err } - review, err = models.CreateReview(models.CreateReviewOptions{ + if review, err = models.CreateReview(models.CreateReviewOptions{ Type: models.ReviewTypePending, Reviewer: doer, Issue: issue, Official: false, CommitID: latestCommitID, - }) - if err != nil { + }); err != nil { return nil, err } } @@ -104,6 +104,8 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models return comment, nil } +var notEnoughLines = regexp.MustCompile(`exit status 128 - fatal: file .* has only \d+ lines?`) + // createCodeComment creates a plain code comment at the specified line / path func createCodeComment(doer *models.User, repo *models.Repository, issue *models.Issue, content, treePath string, line, reviewID int64) (*models.Comment, error) { var commitID, patch string @@ -127,7 +129,7 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line)) if err == nil { commitID = commit.ID.String() - } else if !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") { + } else if !(strings.Contains(err.Error(), "exit status 128 - fatal: no such path") || notEnoughLines.MatchString(err.Error())) { return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err) } } diff --git a/services/release/release.go b/services/release/release.go index c36e2126ed78..af97987a2700 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -58,6 +58,12 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error { if err != nil { return fmt.Errorf("CommitsCount: %v", err) } + + u, err := models.GetUserByEmail(commit.Author.Email) + if err == nil { + rel.PublisherID = u.ID + } + } else { rel.CreatedUnix = timeutil.TimeStampNow() } diff --git a/services/repository/generate.go b/services/repository/generate.go index 95e5cdc6c270..067f8f61d0a0 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -64,7 +64,7 @@ func GenerateRepository(doer, owner *models.User, templateRepo *models.Repositor return nil }); err != nil { - if generateRepo != nil { + if generateRepo != nil && generateRepo.ID > 0 { if errDelete := models.DeleteRepository(doer, owner.ID, generateRepo.ID); errDelete != nil { log.Error("Rollback deleteRepository: %v", errDelete) } diff --git a/services/repository/push.go b/services/repository/push.go new file mode 100644 index 000000000000..762136fd0857 --- /dev/null +++ b/services/repository/push.go @@ -0,0 +1,411 @@ +// 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 repository + +import ( + "container/list" + "encoding/json" + "fmt" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/notification" + "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/modules/repofiles" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + pull_service "code.gitea.io/gitea/services/pull" +) + +// PushUpdateOptions defines the push update options +type PushUpdateOptions struct { + PusherID int64 + PusherName string + RepoUserName string + RepoName string + RefFullName string // branch, tag or other name to push + OldCommitID string + NewCommitID string +} + +// IsNewRef return true if it's a first-time push to a branch, tag or etc. +func (opts PushUpdateOptions) IsNewRef() bool { + return opts.OldCommitID == git.EmptySHA +} + +// IsDelRef return true if it's a deletion to a branch or tag +func (opts PushUpdateOptions) IsDelRef() bool { + return opts.NewCommitID == git.EmptySHA +} + +// IsUpdateRef return true if it's an update operation +func (opts PushUpdateOptions) IsUpdateRef() bool { + return !opts.IsNewRef() && !opts.IsDelRef() +} + +// IsTag return true if it's an operation to a tag +func (opts PushUpdateOptions) IsTag() bool { + return strings.HasPrefix(opts.RefFullName, git.TagPrefix) +} + +// IsNewTag return true if it's a creation to a tag +func (opts PushUpdateOptions) IsNewTag() bool { + return opts.IsTag() && opts.IsNewRef() +} + +// IsDelTag return true if it's a deletion to a tag +func (opts PushUpdateOptions) IsDelTag() bool { + return opts.IsTag() && opts.IsDelRef() +} + +// IsBranch return true if it's a push to branch +func (opts PushUpdateOptions) IsBranch() bool { + return strings.HasPrefix(opts.RefFullName, git.BranchPrefix) +} + +// IsNewBranch return true if it's the first-time push to a branch +func (opts PushUpdateOptions) IsNewBranch() bool { + return opts.IsBranch() && opts.IsNewRef() +} + +// IsUpdateBranch return true if it's not the first push to a branch +func (opts PushUpdateOptions) IsUpdateBranch() bool { + return opts.IsBranch() && opts.IsUpdateRef() +} + +// IsDelBranch return true if it's a deletion to a branch +func (opts PushUpdateOptions) IsDelBranch() bool { + return opts.IsBranch() && opts.IsDelRef() +} + +// TagName returns simple tag name if it's an operation to a tag +func (opts PushUpdateOptions) TagName() string { + return opts.RefFullName[len(git.TagPrefix):] +} + +// BranchName returns simple branch name if it's an operation to branch +func (opts PushUpdateOptions) BranchName() string { + return opts.RefFullName[len(git.BranchPrefix):] +} + +// RefName returns simple name for ref +func (opts PushUpdateOptions) RefName() string { + if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { + return opts.RefFullName[len(git.TagPrefix):] + } else if strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { + return opts.RefFullName[len(git.BranchPrefix):] + } + return "" +} + +// RepoFullName returns repo full name +func (opts PushUpdateOptions) RepoFullName() string { + return opts.RepoUserName + "/" + opts.RepoName +} + +// isForcePush detect if a push is a force push +func isForcePush(repoPath string, opts *PushUpdateOptions) (bool, error) { + if !opts.IsUpdateBranch() { + return false, nil + } + + output, err := git.NewCommand("rev-list", "--max-count=1", opts.OldCommitID, "^"+opts.NewCommitID).RunInDir(repoPath) + if err != nil { + return false, err + } else if len(output) > 0 { + return true, nil + } + return false, nil +} + +// pushQueue represents a queue to handle update pull request tests +var pushQueue queue.Queue + +// handle passed PR IDs and test the PRs +func handle(data ...queue.Data) { + for _, datum := range data { + opts := datum.([]*PushUpdateOptions) + if err := pushUpdates(opts); err != nil { + log.Error("pushUpdate failed: %v", err) + } + } +} + +func initPushQueue() error { + pushQueue = queue.CreateQueue("push_update", handle, []*PushUpdateOptions{}).(queue.Queue) + if pushQueue == nil { + return fmt.Errorf("Unable to create push_update Queue") + } + + go graceful.GetManager().RunWithShutdownFns(pushQueue.Run) + return nil +} + +// PushUpdate is an alias of PushUpdates for single push update options +func PushUpdate(opts *PushUpdateOptions) error { + return PushUpdates([]*PushUpdateOptions{opts}) +} + +// PushUpdates adds a push update to push queue +func PushUpdates(opts []*PushUpdateOptions) error { + if len(opts) == 0 { + return nil + } + + for _, opt := range opts { + if opt.IsNewRef() && opt.IsDelRef() { + return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) + } + } + + return pushQueue.Push(opts) +} + +// pushUpdates generates push action history feeds for push updating multiple refs +func pushUpdates(optsList []*PushUpdateOptions) error { + if len(optsList) == 0 { + return nil + } + + repo, err := models.GetRepositoryByOwnerAndName(optsList[0].RepoUserName, optsList[0].RepoName) + if err != nil { + return fmt.Errorf("GetRepositoryByOwnerAndName failed: %v", err) + } + + repoPath := repo.RepoPath() + gitRepo, err := git.OpenRepository(repoPath) + if err != nil { + return fmt.Errorf("OpenRepository: %v", err) + } + defer gitRepo.Close() + + if err = repo.UpdateSize(models.DefaultDBContext()); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + addTags := make([]string, 0, len(optsList)) + delTags := make([]string, 0, len(optsList)) + actions := make([]*commitRepoActionOptions, 0, len(optsList)) + var pusher *models.User + + for _, opts := range optsList { + if opts.IsNewRef() && opts.IsDelRef() { + return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) + } + var commits = &repo_module.PushCommits{} + if opts.IsTag() { // If is tag reference { + tagName := opts.TagName() + if opts.IsDelRef() { + delTags = append(delTags, tagName) + } else { // is new tag + addTags = append(addTags, tagName) + } + } else if opts.IsBranch() { // If is branch reference + if pusher == nil || pusher.ID != opts.PusherID { + var err error + if pusher, err = models.GetUserByID(opts.PusherID); err != nil { + return err + } + } + + branch := opts.BranchName() + if !opts.IsDelRef() { + log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) + go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID) + + newCommit, err := gitRepo.GetCommit(opts.NewCommitID) + if err != nil { + return fmt.Errorf("gitRepo.GetCommit: %v", err) + } + + // Push new branch. + var l *list.List + if opts.IsNewRef() { + l, err = newCommit.CommitsBeforeLimit(10) + if err != nil { + return fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) + } + } else { + l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) + if err != nil { + return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) + } + + isForce, err := isForcePush(repo.RepoPath(), opts) + if err != nil { + log.Error("isForcePush %s/%s failed: %v", repo.ID, branch, err) + } + + if isForce { + log.Trace("Push %s is a force push", opts.NewCommitID) + + cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true)) + } else { + // TODO: increment update the commit count cache but not remove + cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true)) + } + } + + commits = repo_module.ListToPushCommits(l) + + if err = models.RemoveDeletedBranch(repo.ID, branch); err != nil { + log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) + } + + // Cache for big repository + if err := repo_module.CacheRef(repo, gitRepo, opts.RefFullName); err != nil { + log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err) + } + } else if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil { + // close all related pulls + log.Error("close related pull request failed: %v", err) + } + + // Even if user delete a branch on a repository which he didn't watch, he will be watch that. + if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { + log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) + } + } + actions = append(actions, &commitRepoActionOptions{ + PushUpdateOptions: *opts, + Pusher: pusher, + RepoOwnerID: repo.OwnerID, + Commits: commits, + }) + } + if err := repo_module.PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil { + return fmt.Errorf("PushUpdateAddDeleteTags: %v", err) + } + + if err := commitRepoAction(repo, gitRepo, actions...); err != nil { + return fmt.Errorf("commitRepoAction: %v", err) + } + + return nil +} + +// commitRepoActionOptions represent options of a new commit action. +type commitRepoActionOptions struct { + PushUpdateOptions + + Pusher *models.User + RepoOwnerID int64 + Commits *repo_module.PushCommits +} + +// commitRepoAction adds new commit action to the repository, and prepare +// corresponding webhooks. +func commitRepoAction(repo *models.Repository, gitRepo *git.Repository, optsList ...*commitRepoActionOptions) error { + actions := make([]*models.Action, len(optsList)) + + for i, opts := range optsList { + if opts.Pusher == nil || opts.Pusher.Name != opts.PusherName { + var err error + opts.Pusher, err = models.GetUserByName(opts.PusherName) + if err != nil { + return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) + } + } + + refName := git.RefEndName(opts.RefFullName) + + // Change default branch and empty status only if pushed ref is non-empty branch. + if repo.IsEmpty && opts.IsBranch() && !opts.IsDelRef() { + repo.DefaultBranch = refName + repo.IsEmpty = false + if refName != "master" { + if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if !git.IsErrUnsupportedVersion(err) { + return err + } + } + } + // Update the is empty and default_branch columns + if err := models.UpdateRepositoryCols(repo, "default_branch", "is_empty"); err != nil { + return fmt.Errorf("UpdateRepositoryCols: %v", err) + } + } + + opType := models.ActionCommitRepo + + // Check it's tag push or branch. + if opts.IsTag() { + opType = models.ActionPushTag + if opts.IsDelRef() { + opType = models.ActionDeleteTag + } + opts.Commits = &repo_module.PushCommits{} + } else if opts.IsDelRef() { + opType = models.ActionDeleteBranch + opts.Commits = &repo_module.PushCommits{} + } else { + // if not the first commit, set the compare URL. + if !opts.IsNewRef() { + opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) + } + + if err := repofiles.UpdateIssuesCommit(opts.Pusher, repo, opts.Commits.Commits, refName); err != nil { + log.Error("updateIssuesCommit: %v", err) + } + } + + if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { + opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] + } + + data, err := json.Marshal(opts.Commits) + if err != nil { + return fmt.Errorf("Marshal: %v", err) + } + + actions[i] = &models.Action{ + ActUserID: opts.Pusher.ID, + ActUser: opts.Pusher, + OpType: opType, + Content: string(data), + RepoID: repo.ID, + Repo: repo, + RefName: refName, + IsPrivate: repo.IsPrivate, + } + + var isHookEventPush = true + switch opType { + case models.ActionCommitRepo: // Push + if opts.IsNewBranch() { + notification.NotifyCreateRef(opts.Pusher, repo, "branch", opts.RefFullName) + } + case models.ActionDeleteBranch: // Delete Branch + notification.NotifyDeleteRef(opts.Pusher, repo, "branch", opts.RefFullName) + + case models.ActionPushTag: // Create + notification.NotifyCreateRef(opts.Pusher, repo, "tag", opts.RefFullName) + + case models.ActionDeleteTag: // Delete Tag + notification.NotifyDeleteRef(opts.Pusher, repo, "tag", opts.RefFullName) + default: + isHookEventPush = false + } + + if isHookEventPush { + notification.NotifyPushCommits(opts.Pusher, repo, opts.RefFullName, opts.OldCommitID, opts.NewCommitID, opts.Commits) + } + } + + // Change repository last updated time. + if err := models.UpdateRepositoryUpdatedTime(repo.ID, time.Now()); err != nil { + return fmt.Errorf("UpdateRepositoryUpdatedTime: %v", err) + } + + if err := models.NotifyWatchers(actions...); err != nil { + return fmt.Errorf("NotifyWatchers: %v", err) + } + return nil +} diff --git a/services/repository/push_test.go b/services/repository/push_test.go new file mode 100644 index 000000000000..19ffab45e7f0 --- /dev/null +++ b/services/repository/push_test.go @@ -0,0 +1,139 @@ +// 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 repository + +import ( + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + repo_module "code.gitea.io/gitea/modules/repository" + + "github.com/stretchr/testify/assert" +) + +func testCorrectRepoAction(t *testing.T, repo *models.Repository, gitRepo *git.Repository, opts *commitRepoActionOptions, actionBean *models.Action) { + models.AssertNotExistsBean(t, actionBean) + assert.NoError(t, commitRepoAction(repo, gitRepo, opts)) + models.AssertExistsAndLoadBean(t, actionBean) + models.CheckConsistencyFor(t, &models.Action{}) +} + +func TestCommitRepoAction(t *testing.T) { + samples := []struct { + userID int64 + repositoryID int64 + commitRepoActionOptions commitRepoActionOptions + action models.Action + }{ + { + userID: 2, + repositoryID: 16, + commitRepoActionOptions: commitRepoActionOptions{ + PushUpdateOptions: PushUpdateOptions{ + RefFullName: "refName", + OldCommitID: "oldCommitID", + NewCommitID: "newCommitID", + }, + Commits: &repo_module.PushCommits{ + Commits: []*repo_module.PushCommit{ + { + Sha1: "69554a6", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "not signed commit", + }, + { + Sha1: "27566bd", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "good signed commit (with not yet validated email)", + }, + }, + Len: 2, + }, + }, + action: models.Action{ + OpType: models.ActionCommitRepo, + RefName: "refName", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: commitRepoActionOptions{ + PushUpdateOptions: PushUpdateOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: git.EmptySHA, + NewCommitID: "newCommitID", + }, + Commits: &repo_module.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionPushTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: commitRepoActionOptions{ + PushUpdateOptions: PushUpdateOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + }, + Commits: &repo_module.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: commitRepoActionOptions{ + PushUpdateOptions: PushUpdateOptions{ + RefFullName: git.BranchPrefix + "feature/1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + }, + Commits: &repo_module.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteBranch, + RefName: "feature/1", + }, + }, + } + + for _, s := range samples { + models.PrepareTestEnv(t) + + user := models.AssertExistsAndLoadBean(t, &models.User{ID: s.userID}).(*models.User) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: s.repositoryID, OwnerID: user.ID}).(*models.Repository) + repo.Owner = user + + gitRepo, err := git.OpenRepository(repo.RepoPath()) + assert.NoError(t, err) + + s.commitRepoActionOptions.PusherName = user.Name + s.commitRepoActionOptions.RepoOwnerID = user.ID + s.commitRepoActionOptions.RepoName = repo.Name + + s.action.ActUserID = user.ID + s.action.RepoID = repo.ID + s.action.Repo = repo + s.action.IsPrivate = repo.IsPrivate + + testCorrectRepoAction(t, repo, gitRepo, &s.commitRepoActionOptions, &s.action) + gitRepo.Close() + } +} diff --git a/services/repository/repository.go b/services/repository/repository.go index f50b98b64099..20d630424c63 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" + cfg "code.gitea.io/gitea/modules/setting" pull_service "code.gitea.io/gitea/services/pull" ) @@ -18,11 +19,7 @@ import ( func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { repo, err := repo_module.CreateRepository(doer, owner, opts) if err != nil { - if repo != nil { - if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } + // No need to rollback here we should do this in CreateRepository... return nil, err } @@ -31,15 +28,28 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) ( return repo, nil } +// AdoptRepository adopts pre-existing repository files for the user/organization. +func AdoptRepository(doer, owner *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { + repo, err := repo_module.AdoptRepository(doer, owner, opts) + if err != nil { + // No need to rollback here we should do this in AdoptRepository... + return nil, err + } + + notification.NotifyCreateRepository(doer, owner, repo) + + return repo, nil +} + +// DeleteUnadoptedRepository adopts pre-existing repository files for the user/organization. +func DeleteUnadoptedRepository(doer, owner *models.User, name string) error { + return repo_module.DeleteUnadoptedRepository(doer, owner, name) +} + // ForkRepository forks a repository func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) { repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc) if err != nil { - if repo != nil { - if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } return nil, err } @@ -54,13 +64,11 @@ func DeleteRepository(doer *models.User, repo *models.Repository) error { log.Error("CloseRepoBranchesPulls failed: %v", err) } - if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { - return err - } - + // If the repo itself has webhooks, we need to trigger them before deleting it... notification.NotifyDeleteRepository(doer, repo) - return nil + err := models.DeleteRepository(doer, repo.OwnerID, repo.ID) + return err } // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace @@ -79,7 +87,7 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo repo, err := CreateRepository(authUser, owner, models.CreateRepoOptions{ Name: repoName, - IsPrivate: true, + IsPrivate: cfg.Repository.DefaultPushCreatePrivate, }) if err != nil { return nil, err @@ -87,3 +95,8 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo return repo, nil } + +// NewContext start repository service +func NewContext() error { + return initPushQueue() +} diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 3616823c5d6d..5f7d26a045db 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -37,17 +37,17 @@ func nameAllowed(name string) error { // NameToSubURL converts a wiki name to its corresponding sub-URL. func NameToSubURL(name string) string { - return url.QueryEscape(strings.Replace(name, " ", "-", -1)) + return url.QueryEscape(strings.ReplaceAll(name, " ", "-")) } // NormalizeWikiName normalizes a wiki name func NormalizeWikiName(name string) string { - return strings.Replace(name, "-", " ", -1) + return strings.ReplaceAll(name, "-", " ") } // NameToFilename converts a wiki name to its corresponding filename. func NameToFilename(name string) string { - name = strings.Replace(name, " ", "-", -1) + name = strings.ReplaceAll(name, " ", "-") return url.QueryEscape(name) + ".md" } @@ -185,16 +185,22 @@ func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, new Message: message, } - sign, signingKey, _ := repo.SignWikiCommit(doer) + committer := doer.NewGitSig() + + sign, signingKey, signer, _ := repo.SignWikiCommit(doer) if sign { commitTreeOpts.KeyID = signingKey + if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel { + committer = signer + } } else { commitTreeOpts.NoGPGSign = true } if hasMasterBranch { commitTreeOpts.Parents = []string{"HEAD"} } - commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts) if err != nil { log.Error("%v", err) return err @@ -302,14 +308,19 @@ func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string) Parents: []string{"HEAD"}, } - sign, signingKey, _ := repo.SignWikiCommit(doer) + committer := doer.NewGitSig() + + sign, signingKey, signer, _ := repo.SignWikiCommit(doer) if sign { commitTreeOpts.KeyID = signingKey + if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel { + committer = signer + } } else { commitTreeOpts.NoGPGSign = true } - commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts) if err != nil { return err } diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 15ab2b227bd7..e182bbc8581c 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -99,6 +99,31 @@ +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
{{if .Source.IsLDAP}}
@@ -308,7 +333,7 @@